Snippets

Paul Atkin Django rest serializer for django-gm2m fields

Updated by Paul Atkin

File serializers.py Modified

  • Ignore whitespace
  • Hide word diff
 from urllib.parse import urlparse
 from django.core.urlresolvers import resolve
 from rest_framework import serializers
-from cbe.serializer_fields import TypeField,GenericRelatedField
+from rest-gm2m-field import TypeField,GenericRelatedField
 
 class GenericHyperlinkedSerializer(serializers.HyperlinkedModelSerializer):
     """
Updated by Paul Atkin

File rest-gm2m-field.py Modified

  • Ignore whitespace
  • Hide word diff
 from urllib.parse import urlparse
 from django.core.urlresolvers import resolve
+from django.utils import six
 from rest_framework import serializers
 
 
         return object
         
         
+        
 class TypeField(serializers.Field):
+    """
+        Read only Field which displays the object type from the class name
+    """
     def __init__(self, *args, **kwargs):
         
         kwargs['source'] = '__class__.__name__'
         super(TypeField, self).__init__(*args, **kwargs)
         
     def to_representation(self, value):
-        return value        
+        return value
+        
+        
+        
+       
+
+

File serializers.py Modified

  • Ignore whitespace
  • Hide word diff
+from urllib.parse import urlparse
+from django.core.urlresolvers import resolve
 from rest_framework import serializers
+from cbe.serializer_fields import TypeField,GenericRelatedField
 
-from rest-gm2m-field import TypeField,GenericRelatedField
-from myapp.models import MyMainModel, MyLinkedModel1, MyLinkedModel2
-from myapp.serializers import MyLinkedModel1Serializer, MyLinkedModel1Serializer
+class GenericHyperlinkedSerializer(serializers.HyperlinkedModelSerializer):
+    """
+        Serializer for models with generic relations.
+        Includes a type field to allow GenericRelatedFields specified in the serializer to know how to serialize correctly.
+        Derived from hyperlinked serializer and the url field must be present on the serializer
+    """
+    type = TypeField()
 
+    def create(self, validated_data):        
+        # Remove generic fields from validated data and add to separate dict
+        related = {}
+        for field in self._fields.keys():
+            if type(self._fields[field]) == serializers.ManyRelatedField:
+                if type( self._fields[field].child_relation) == GenericRelatedField:
+                    related[field] = validated_data.pop(field)
+                    
+        # Create instance of serializers Meta.model
+        instance = self.Meta.model.objects.create(**validated_data)
+        
+        # For all related fields attach the listed objects
+        for field in related.keys():
+            for object in related[field]:
+                attr = getattr( instance, field )
+                attr.add(object)
 
-class MyMainModelSerializer(serializers.HyperlinkedModelSerializer):
-    type = TypeField()
-    mygm2m_field = GenericRelatedField(many=True, serializer_dict={MyLinkedModel1:MyLinkedModel1Serializer(),MyLinkedModel2:MyLinkedModel2Serializer(),})
-    
-    class Meta:
-        model = MyMainModel
-        fields = ('type', 'url', 'mygm2m_field' )
+        return instance
+
+        
+    def update(self, instance, validated_data):
+        
+        # Create a dict of updatable fields 
+        fields = {}
+        generics = {}
+        for field in self._fields.keys():
+            # Exclude read only fields
+            if not self._fields[field].read_only:
+                if type(self._fields[field]) != serializers.ManyRelatedField:
+                    fields[field] = self._fields[field]
+                else:
+                    # Exclude generics but add to separate dict
+                    if type( self._fields[field].child_relation) != GenericRelatedField:
+                        fields[field] = self._fields[field]
+                    else:
+                        generics[field] = self._fields[field]
 
-    def create(self, validated_data):
-        related = validated_data.pop('mygm2m_field')
-        mainobject = MyMainModel.objects.create(**validated_data)
+        # Set all valid attributes of the instance to the validated data
+        for field in fields.keys():
+            setattr(instance, field, validated_data.get(field, getattr(instance,field))) 
         
-        for object in related:
-            mainobject.mygm2m_field.add(object)
+        # Add any new generic relations
+        for generic_attr in generics.keys():
+            attr = getattr( instance, generic_attr )
+            attr_objects = list(attr.all())
+            for object in validated_data.get(generic_attr, getattr(instance,generic_attr)):
+                if object not in attr_objects:
+                    # If the object is not in the list of existing generic relations then add it
+                    attr.add(object)
+                else:
+                    # If the object is already related then remove it from the list so we end up with a list of missing generic relations
+                    attr_objects.remove(object)
+            
+            # Remove any missing generic relations if not a partial update (PUT but not PATCH)
+            if not self.partial:
+                for object in attr_objects:
+                    attr.remove( object )
         
-        return mainobject
+        instance.save()
+        return instance   
Created by Paul Atkin

File rest-gm2m-field.py Added

  • Ignore whitespace
  • Hide word diff
+from urllib.parse import urlparse
+from django.core.urlresolvers import resolve
+from rest_framework import serializers
+
+
+class GenericRelatedField(serializers.StringRelatedField):
+    """
+    A custom field to use for serializing generic relationships.
+    """
+    def __init__(self, serializer_dict, *args, **kwargs):
+        super(GenericRelatedField, self).__init__(*args, **kwargs)
+
+        self.serializer_dict = serializer_dict
+        for s in self.serializer_dict.values():
+            s.bind('',self)
+
+            
+    def to_representation(self, instance):
+        # find a serializer correspoding to the instance class
+        for key in self.serializer_dict.keys():
+            if isinstance(instance, key):
+                # Return the result of the classes serializer
+                return self.serializer_dict[key].to_representation(instance=instance)
+        return '{}'.format(instance)
+
+        
+    def to_internal_value(self, data):
+        # If provided as string, must be url to resource. Create dict containing just url
+        if type(data) == str:
+            data = {'url':data}
+                
+        # Existing resource can be specified as url
+        if 'url' in data:
+            # Extract details from the url and grab real object
+            resolved_func, unused_args, resolved_kwargs = resolve(urlparse(data['url']).path)
+            object=resolved_func.cls.queryset.get(pk=resolved_kwargs['pk'])
+        else:
+            # If url is not specified then object is new and must have a 'type' field to allow us to create correct object from list of serializers
+            for key in self.serializer_dict.keys():
+                if data['type'] == key.__name__:
+                    object = key()
+        
+        # Deserialize data into attributes of object and apply
+        obj_internal_value = self.serializer_dict[object.__class__].to_internal_value(data)
+        for k,v in obj_internal_value.items():
+            setattr(object,k,v)
+        
+        # Save object to store new or any updated attributes
+        object.save()
+        return object
+        
+        
+class TypeField(serializers.Field):
+    def __init__(self, *args, **kwargs):
+        
+        kwargs['source'] = '__class__.__name__'
+        kwargs['read_only'] = True
+        super(TypeField, self).__init__(*args, **kwargs)
+        
+    def to_representation(self, value):
+        return value        

File serializers.py Added

  • Ignore whitespace
  • Hide word diff
+from rest_framework import serializers
+
+from rest-gm2m-field import TypeField,GenericRelatedField
+from myapp.models import MyMainModel, MyLinkedModel1, MyLinkedModel2
+from myapp.serializers import MyLinkedModel1Serializer, MyLinkedModel1Serializer
+
+
+class MyMainModelSerializer(serializers.HyperlinkedModelSerializer):
+    type = TypeField()
+    mygm2m_field = GenericRelatedField(many=True, serializer_dict={MyLinkedModel1:MyLinkedModel1Serializer(),MyLinkedModel2:MyLinkedModel2Serializer(),})
+    
+    class Meta:
+        model = MyMainModel
+        fields = ('type', 'url', 'mygm2m_field' )
+
+    def create(self, validated_data):
+        related = validated_data.pop('mygm2m_field')
+        mainobject = MyMainModel.objects.create(**validated_data)
+        
+        for object in related:
+            mainobject.mygm2m_field.add(object)
+        
+        return mainobject
HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.