Commits

David Larlet  committed 7bdc4ba

Add support for m2m relations

  • Participants
  • Parent commits d71a157
  • Tags 1.0

Comments (0)

Files changed (6)

 django-roa changelog
 ====================
 
+Version 1.0, 23 January 2008:
+-----------------------------
+
+* Add support for many-to-many relations.
+* Warning: Many-to-many relations depends on Django' issue #10109, apply the 
+  attached patch if you need it.
+* Update py-restclient to 1.1.4.
+
+
 Version 0.9, 9 January 2008:
-------------------------------
+----------------------------
 
 * Ease subclassing of MethodDispatcher with custom slugs.
 
 
 Version 0.8, 7 January 2008:
-------------------------------
+----------------------------
 
 * Add support for admin options except search_fields because of advanced
   querysets based on Q objects.
 
 
 Version 0.7, 2 January 2008:
-------------------------------
+----------------------------
 
 * Add support for most useful fields (see specifications).
 * Warning: there are some Django bugs (hopefully with patches) which are
 django-roa todo
 ===============
 
-Must-have
----------
-
-* Handle remote ContentTypes in admin, very hard without rewriting it.
-* Handle ManyToMany relations, raw SQL executed without any backend call.
-
-
 Maybe
 -----
 
-* Handle Q filters
+* Handle remote ContentTypes in admin, very hard without rewriting it
 * Cascading changes/deletions
-* Use ETags (performances)

File django_roa/db/query.py

 from django.core import serializers
 from django.db.models.sql.constants import LOOKUP_SEP
 
-from restclient import Resource, ResourceNotFound
+from restclient import Resource, ResourceNotFound, RequestFailed
+from django_roa.db.exceptions import ROAException
 
 ROA_FORMAT = getattr(settings, "ROA_FORMAT", 'json')
 
         self.where = False
         self.select_related = False
         self.max_depth = None
+        self.m2m_add = False
+        self.m2m_remove = False
+        self.m2m_clear = False
         self.extra_select = {}
     
     def can_filter(self):
         self.select_related = field_dict
         self.related_select_cols = []
         self.related_select_fields = []
+
+    def _add_items(self, field, *objs):
+        self.m2m_add = True
+        self.m2m_ids = [str(obj.id) for obj in objs]
+        self.m2m_field_name = field.name
+    
+    def _remove_items(self, field, *objs):
+        self.m2m_remove = True
+        self.m2m_ids = [str(obj.id) for obj in objs]
+        self.m2m_field_name = field.name
+    
+    def _clear_items(self, field):
+        self.m2m_clear = True
+        self.m2m_field_name = field.name
     
     @property
     def parameters(self):
+        """
+        Returns useful parameters as a dictionary.
+        """
         parameters = {}
         # Counting
         if self.count:
         # Format
         parameters['format'] = ROA_FORMAT
         
+        # M2M relations
+        if self.m2m_add:
+            parameters['m2m_add'] = 1
+            parameters['m2m_ids'] = ','.join(self.m2m_ids)
+            parameters['m2m_field_name'] = self.m2m_field_name
+        if self.m2m_remove:
+            parameters['m2m_remove'] = 1
+            parameters['m2m_ids'] = ','.join(self.m2m_ids)
+            parameters['m2m_field_name'] = self.m2m_field_name
+        if self.m2m_clear:
+            parameters['m2m_clear'] = 1
+            parameters['m2m_field_name'] = self.m2m_field_name
+        
         #print parameters
         return parameters
     
         if depth:
             obj.query.max_depth = depth
         return obj
+
+    #################################
+    # METHODS THAT DO M2M RELATIONS #
+    #################################
+
+    def _add_items(self, source_col_name=None, target_col_name=None, 
+                   join_table=None, pk_val=None, instance=None, field=None, 
+                   *objs):
+        """
+        Adds m2m relations between ``instance`` object and ``objs``.
+        """
+        self.query._add_items(field, *objs)
+        
+        resource = Resource(instance.get_resource_url_detail())
+        
+        try:
+            response = resource.put(**self.query.parameters)
+        except RequestFailed, e:
+            raise ROAException(e)
+
+    def _remove_items(self, source_col_name=None, target_col_name=None, 
+                      join_table=None, pk_val=None, instance=None, field=None, 
+                      *objs):
+        """
+        Removes m2m relations between ``instance`` object and ``objs``.
+        """
+        self.query._remove_items(field, *objs)
+        
+        resource = Resource(instance.get_resource_url_detail())
+        
+        try:
+            response = resource.put(**self.query.parameters)
+        except RequestFailed, e:
+            raise ROAException(e)
+
+    def _clear_items(self, source_col_name=None, join_table=None, pk_val=None, 
+                     instance=None, field=None):
+        """
+        Clears m2m relations related to ``instance`` object.
+        """
+        self.query._clear_items(field)
+        
+        resource = Resource(instance.get_resource_url_detail())
+        
+        try:
+            response = resource.put(**self.query.parameters)
+        except RequestFailed, e:
+            raise ROAException(e)

File django_roa/tests.py

     >>> page = RemotePageWithRelations.objects.create(title=u"A remote relation page")
     >>> page.remote_page_fields.add(remote_page)
     >>> page.remote_page_fields.all()
-    Implementation in progress
+    [<RemotePageWithManyFields: RemotePageWithManyFields (1)>]
     >>> page = RemotePageWithRelations.objects.get(id=page.id)
     >>> page.remote_page_fields.all()
-    Implementation in progress
+    [<RemotePageWithManyFields: RemotePageWithManyFields (1)>]
     >>> page.remote_page_fields.add(another_remote_page)
     >>> page = RemotePageWithRelations.objects.get(id=page.id)
     >>> page.remote_page_fields.all()
-    Implementation in progress
+    [<RemotePageWithManyFields: RemotePageWithManyFields (1)>, <RemotePageWithManyFields: RemotePageWithManyFields (2)>]
+    >>> remote_page.remotepagewithrelations_set.all()
+    [<RemotePageWithRelations: A remote relation page (2)>]
+    >>> page.remote_page_fields.remove(remote_page)
+    >>> page.remote_page_fields.all()
+    [<RemotePageWithManyFields: RemotePageWithManyFields (2)>]
+    >>> page.remote_page_fields.clear()
+    >>> page.remote_page_fields.all()
+    []
     >>> page.delete()
     >>> remote_page.delete()
     >>> another_remote_page.delete()

File docs/specifications.rst

 Available relations:
 
     * ForeignKey
-    * ManyToMany in progress
+    * ManyToMany
 
 
 Admin
 Parameters
 ----------
 
-Optionnal parameters:
+Optional parameters:
 
-    * format: json, xml will be supported as soon as possible
     * count: returns the number of elements
     * filter_* and exclude_*: * is the string used by Django to filter/exclude
     * order_by: order the results given this field
     * limit_start and limit_stop: slice the results given those integers
+    * format: json or xml
+
+Optional M2M parameters:
+
+    * m2m_add: declare that you will add many-to-many relations
+    * m2m_remove: declare that you will remove many-to-many relations
+    * m2m_clear: declare that you will clear many-to-many relations
+    * m2m_field_name: name of the related field, required for add, remove and
+      clear types of relations
+    * m2m_ids: ids of the related objects, required for add and remove types
+      of relations
 
 Note: take a look at tests and test_projects to see all actual possibilities.
 
       should be all lower-case, regardless of the case of the actual model 
       class name. Same behaviour as ``ABSOLUTE_URL_OVERRIDES`` setting, see
       examples.
-

File test_projects/django_roa_server/views.py

         Dispatch the request given the method and object_id argument.
         """
         model = models.get_model(app_label, model_name)
+        if model is None:
+            logger.debug(u'Model not found with: "%s" app label and "%s" model name' % (app_label, model_name))
+            raise Http404()
         method = request.method
         logger.debug(u"Request: %s %s %s" % (method, model.__name__, args))
         if len([value for value in args.values() if value is not None]):
         Modifies an object given request args, returned as a list.
         """
         data = request.REQUEST.copy()
+        
+        # Add M2M relations
+        if 'm2m_add' in data:
+            obj_ids_str = data['m2m_ids']
+            obj_ids = [int(obj_id) for obj_id in data['m2m_ids']]
+            m2m_field = getattr(object, data['m2m_field_name'])
+            m2m_field.add(*obj_ids)
+            
+            response = [model.objects.get(id=object.id)]
+            #response = [object]
+            logger.debug(u'Object "%s" added M2M relations with ids %s' % (object, obj_ids_str))
+            return response
+        
+        # Remove M2M relations
+        if 'm2m_remove' in data:
+            obj_ids_str = data['m2m_ids']
+            obj_ids = [int(obj_id) for obj_id in data['m2m_ids']]
+            m2m_field = getattr(object, data['m2m_field_name'])
+            m2m_field.remove(*obj_ids)
+            
+            response = [model.objects.get(id=object.id)]
+            #response = [object]
+            logger.debug(u'Object "%s" removed M2M relations with ids %s' % (object, obj_ids_str))
+            return response
+        
+        # Remove M2M relations
+        if 'm2m_clear' in data:
+            m2m_field = getattr(object, data['m2m_field_name'])
+            m2m_field.clear()
+            
+            response = [model.objects.get(id=object.id)]
+            #response = [object]
+            logger.debug(u'Object "%s" cleared M2M relations' % (object, ))
+            return response
+        
         m2m_data = {}
         for field in model._meta.local_fields:
             field_name = field.name