Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django-rest-framework + django-polymorphic ModelSerialization

I was wondering if anyone had a Pythonic solution of combining Django REST framework with django-polymorphic.

Given:

class GalleryItem(PolymorphicModel):     gallery_item_field = models.CharField()  class Photo(GalleryItem):     custom_photo_field = models.CharField()  class Video(GalleryItem):     custom_image_field = models.CharField() 

If I want a list of all GalleryItems in django-rest-framework it would only give me the fields of GalleryItem (the parent model), hence: id, gallery_item_field, and polymorphic_ctype. That's not what I want. I want the custom_photo_field if it's a Photo instance and custom_image_field if it's a Video.

like image 861
Michael van de Waeter Avatar asked Nov 14 '13 11:11

Michael van de Waeter


People also ask

What is polymorphic Django?

Django-polymorphic builds on top of the standard Django model inheritance. It makes using inherited models easier. When a query is made at the base model, the inherited model classes are returned. When we store models that inherit from a Project model…

Is Django REST framework the same as Django?

Django is the web development framework in python whereas the Django Rest Framework is the library used in Django to build Rest APIs. Django Rest Framework is especially designed to make the CRUD operations easier to design in Django. Django Rest Framework makes it easy to use your Django Server as an REST API.

What is the difference between ModelSerializer and HyperlinkedModelSerializer?

The HyperlinkedModelSerializer class is similar to the ModelSerializer class except that it uses hyperlinks to represent relationships, rather than primary keys. By default the serializer will include a url field instead of a primary key field.

Can I use Django REST framework without Django?

REST framework requires the following: Python (3.6, 3.7, 3.8, 3.9, 3.10) Django (2.2, 3.0, 3.1, 3.2, 4.0, 4.1)


2 Answers

So far I only tested this for GET request, and this works:

class PhotoSerializer(serializers.ModelSerializer):      class Meta:         model = models.Photo   class VideoSerializer(serializers.ModelSerializer):      class Meta:         model = models.Video   class GalleryItemModuleSerializer(serializers.ModelSerializer):      class Meta:         model = models.GalleryItem      def to_representation(self, obj):         """         Because GalleryItem is Polymorphic         """         if isinstance(obj, models.Photo):             return PhotoSerializer(obj, context=self.context).to_representation(obj)         elif isinstance(obj, models.Video):            return VideoSerializer(obj, context=self.context).to_representation(obj)         return super(GalleryItemModuleSerializer, self).to_representation(obj) 

For POST and PUT requests you might want to do something similiar as overriding the to_representation definition with the to_internal_value def.

like image 196
Michael van de Waeter Avatar answered Oct 08 '22 05:10

Michael van de Waeter


Here's a general and reusable solution. It's for a generic Serializer but it wouldn't be difficult to modify it to use ModelSerializer. It also doesn't handle serializing the parent class (in my case I use the parent class more as an interface).

from typing import Dict, Type  from rest_framework import serializers   class PolymorphicSerializer(serializers.Serializer):     """     Serializer to handle multiple subclasses of another class      - For serialized dict representations, a 'type' key with the class name as       the value is expected: ex. {'type': 'Decimal', ... }     - This type information is used in tandem with get_serializer_map(...) to       manage serializers for multiple subclasses     """     def get_serializer_map(self) -> Dict[str, Type[serializers.Serializer]]:         """         Return a dict to map class names to their respective serializer classes          To be implemented by all PolymorphicSerializer subclasses         """         raise NotImplementedError      def to_representation(self, obj):         """         Translate object to internal data representation          Override to allow polymorphism         """         type_str = obj.__class__.__name__          try:             serializer = self.get_serializer_map()[type_str]         except KeyError:             raise ValueError(                 'Serializer for "{}" does not exist'.format(type_str),             )          data = serializer(obj, context=self.context).to_representation(obj)         data['type'] = type_str         return data      def to_internal_value(self, data):         """         Validate data and initialize primitive types          Override to allow polymorphism         """         try:             type_str = data['type']         except KeyError:             raise serializers.ValidationError({                 'type': 'This field is required',             })          try:             serializer = self.get_serializer_map()[type_str]         except KeyError:             raise serializers.ValidationError({                 'type': 'Serializer for "{}" does not exist'.format(type_str),             })          validated_data = serializer(context=self.context) \             .to_internal_value(data)         validated_data['type'] = type_str         return validated_data      def create(self, validated_data):         """         Translate validated data representation to object          Override to allow polymorphism         """         serializer = self.get_serializer_map()[validated_data['type']]         return serializer(context=self.context).create(validated_data) 

And to use it:

class ParentClassSerializer(PolymorphicSerializer):     """     Serializer for ParentClass objects     """     def get_serializer_map(self) -> Dict[str, Type[serializers.Serializer]]:         """         Return serializer map         """         return {             ChildClass1.__name__: ChildClass1Serializer,             ChildClass2.__name__: ChildClass2Serializer,         } 
like image 26
Syas Avatar answered Oct 08 '22 06:10

Syas