django-rest-framework + django-polymorphic ModelSerialization
So far I only tested this for GET request, and this works:
class PhotoSerializer(serializers.ModelSerializer): class Meta: model = models.Photoclass VideoSerializer(serializers.ModelSerializer): class Meta: model = models.Videoclass 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.
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, Typefrom rest_framework import serializersclass 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, }
For sake of completion, I'm adding to_internal_value()
implementation, since I needed this in my recent project.
How to determine the type
Its handy to have possibility to distinguish between different "classes"; So I've added the type property into the base polymorphic model for this purpose:
class GalleryItem(PolymorphicModel): gallery_item_field = models.CharField() @property def type(self): return self.__class__.__name__
This allows to call the type
as "field" and "read only field".
type
will contain python class name.
Adding type to Serializer
You can add the type
into "fields" and "read only fields"(you need to specify type field in all the Serializers though if you want to use them in all Child models)
class PhotoSerializer(serializers.ModelSerializer): class Meta: model = models.Photo fields = ( ..., 'type', ) read_only_fields = ( ..., 'type', )class VideoSerializer(serializers.ModelSerializer): class Meta: model = models.Video fields = ( ..., 'type', ) read_only_fields = ( ..., 'type', )class GalleryItemModuleSerializer(serializers.ModelSerializer): class Meta: model = models.GalleryItem fields = ( ..., 'type', ) read_only_fields = ( ..., 'type', ) def to_representation(self, obj): pass # see the other comment def to_internal_value(self, data): """ Because GalleryItem is Polymorphic """ if data.get('type') == "Photo": self.Meta.model = models.Photo return PhotoSerializer(context=self.context).to_internal_value(data) elif data.get('type') == "Video": self.Meta.model = models.Video return VideoSerializer(context=self.context).to_internal_value(data) self.Meta.model = models.GalleryItem return super(GalleryItemModuleSerializer, self).to_internal_value(data)