Package: rest-framework-generic-relations
Rest Framework Generic RelationsThis library implementsDjango REST Frameworkserializers to handle generic foreign keys.RequirementsAny currently-supported combination of Django REST Framework, Python, and Django.InstallationInstall usingpip...pipinstallrest-framework-generic-relationsAdd'generic_relations'to yourINSTALLED_APPSsetting.INSTALLED_APPS=(...'generic_relations',)API ReferenceGenericRelatedFieldThis field serializes generic foreign keys. For a primer on generic foreign keys, first see:https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/Let's assume aTaggedItemmodel which has a generic relationship with other arbitrary models:classTaggedItem(models.Model):tag_name=models.SlugField()content_type=models.ForeignKey(ContentType,on_delete=models.CASCADE)object_id=models.PositiveIntegerField()tagged_object=GenericForeignKey('content_type','object_id')And the following two models, which may have associated tags:classBookmark(models.Model):"""A bookmark consists of a URL, and 0 or more descriptive tags."""url=models.URLField()tags=GenericRelation(TaggedItem)classNote(models.Model):"""A note consists of some text, and 0 or more descriptive tags."""text=models.CharField(max_length=1000)tags=GenericRelation(TaggedItem)Now we define serializers for each model that may get associated with tags.classBookmarkSerializer(serializers.ModelSerializer):classMeta:model=Bookmarkfields=('url',)classNoteSerializer(serializers.ModelSerializer):classMeta:model=Notefields=('text',)The model serializer for theTaggedItemmodel could look like this:fromgeneric_relations.relationsimportGenericRelatedFieldclassTagSerializer(serializers.ModelSerializer):"""A `TaggedItem` serializer with a `GenericRelatedField` mapping all possiblemodels to their respective serializers."""tagged_object=GenericRelatedField({Bookmark:BookmarkSerializer(),Note:NoteSerializer()})classMeta:model=TaggedItemfields=('tag_name','tagged_object')The JSON representation of aTaggedItemobject withname='django'and its generic foreign key pointing at aBookmarkobject withurl='https://www.djangoproject.com/'would look like this:{"tagged_object":{"url":"https://www.djangoproject.com/"},"tag_name":"django"}If you want to have your generic foreign key represented as hyperlink, simply useHyperlinkedRelatedFieldobjects:classTagSerializer(serializers.ModelSerializer):"""A `Tag` serializer with a `GenericRelatedField` mapping all possiblemodels to properly set up `HyperlinkedRelatedField`s."""tagged_object=GenericRelatedField({Bookmark:serializers.HyperlinkedRelatedField(queryset=Bookmark.objects.all(),view_name='bookmark-detail',),Note:serializers.HyperlinkedRelatedField(queryset=Note.objects.all(),view_name='note-detail',),})classMeta:model=TaggedItemfields=('tag_name','tagged_object')The JSON representation of the sameTaggedItemexample object could now look something like this:{"tagged_object":"/bookmark/1/","tag_name":"django"}Writing to generic foreign keysThe aboveTagSerializeris also writable. By default, aGenericRelatedFielditerates over its nested serializers and returns the value of the first serializer that is actually able to performto_internal_value()without any errors.
Note, that (at the moment) onlyHyperlinkedRelatedFieldis able to serialize model objects out of the box.The following operations would create aTaggedItemobject with it'stagged_objectproperty pointing at theBookmarkobject found at the given detail end point.tag_serializer=TagSerializer(data={'tag_name':'python','tagged_object':'/bookmark/1/'})tag_serializer.is_valid()tag_serializer.save()If you feel that this default behavior doesn't suit your needs, you can subclassGenericRelatedFieldand override itsget_serializer_for_instanceorget_deserializer_for_datarespectively to implement your own way of decision-making.GenericModelSerializerSometimes you may want to serialize a single list of different top-level things. For instance, suppose I have an API view that returns what items are on my bookshelf. Let's define some models:fromdjango.core.validatorsimportMaxValueValidatorclassBook(models.Model):title=models.CharField(max_length=255)author=models.CharField(max_length=255)classBluray(models.Model):title=models.CharField(max_length=255)rating=models.PositiveSmallIntegerField(validators=[MaxValueValidator(5)],)Then we could have a serializer for each type of object:classBookSerializer(serializers.ModelSerializer):classMeta:model=Bookfields=('title','author')classBluraySerializer(serializers.ModelSerializer):classMeta:model=Blurayfields=('title','rating')Now we can create a generic list serializer, which delegates to the above serializers based on the type of model it's serializing:bookshelf_item_serializer=GenericModelSerializer({Book:BookSerializer(),Bluray:BluraySerializer(),},many=True,)Then we can serialize a mixed list of items:>>>bookshelf_item_serializer.to_representation([Book.objects.get(title='War and Peace'),Bluray.objects.get(title='Die Hard'),Bluray.objects.get(title='Shawshank Redemption'),Book.objects.get(title='To Kill a Mockingbird'),])[{'title':'War and Peace','author':'Leo Tolstoy'},{'title':'Die Hard','rating':5},{'title':'Shawshank Redemption','rating':5},{'title':'To Kill a Mockingbird','author':'Harper Lee'}]A few things you should note:AlthoughGenericForeignKeyfields can be set to any model object, theGenericRelatedFieldonly handles models explicitly defined in its configuration dictionary.Reverse generic keys, expressed using theGenericRelationfield, can be serialized using the regular relational field types, since the type of the target in the relationship is always known.The order in which you register serializers matters as far as write operations are concerned.Unless you provide a customget_deserializer_for_data()method, onlyHyperlinkedRelatedFieldprovides write access to generic model relations.