django-rest-framework + django-polymorphic ModelSerialization django-rest-framework + django-polymorphic ModelSerialization django django

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)