Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DRF How to serialize models inheritance ? (read/write)

I have some models

class RootModel(models.Model):
    # Some fields

class ElementModel(models.Model):
    root = models.ForeignKey(RootModel, related_name='elements', on_delete=models.CASCADE)

class TextModel(ElementModel):
    text = models.TextField()

class BooleanModel(ElementModel):
    value = models.BooleanField()

a viewset

class RootViewSet(viewsets.ModelViewSet):
    queryset = RootModel.objects.all()
    serializer_class = RootSerializer

and serializers

class TextSerializer(serializers.ModelSerializer):
    type = serializers.SerializerMethodField()

    class Meta:
        model = TextModel
        fields = '__all__'

    def get_type(self, obj):
        return 'TEXT'


class BooleanSerializer(serializers.ModelSerializer):
    type = serializers.SerializerMethodField()

    class Meta:
        model = BooleanModel
        fields = '__all__'

    def get_type(self, obj):
        return 'BOOL'


class RootSerializer(WritableNestedModelSerializer):
    elements = ...
    class Meta:
        model = RootModel
        fields = '__all__'

WritableNestedModelSerializer comes from drf_writable_nested extension.

I want to GET/POST/PUT a root containing all data

example with GET (same data for POST/PUT)

{
    elements: [
        {
            type: "TEXT",
            text: "my awesome text"
        },
        {
            type: "BOOL",
            value: true
        }
    ],
    ...
    root fields
    ...
}

What is the best way for elements field in RootSerializer ?

I also want to have information with OPTIONS method, how can I have it ?

Thanks

like image 481
Etienne Avatar asked Feb 21 '18 16:02

Etienne


People also ask

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.

What does serializer Is_valid () do?

The .is_valid() method takes an optional raise_exception flag that will cause it to raise a serializers.ValidationError exception if there are validation errors.

How do I pass Queryset to serializer?

How do I pass Queryset to serializer? To serialize a queryset or list of objects instead of a single object instance, you should pass the many=True flag when instantiating the serializer. You can then pass a queryset or list of objects to be serialized.

How does serializer work in DRF?

Serializers in Django REST Framework are responsible for converting objects into data types understandable by javascript and front-end frameworks. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.


1 Answers

Finally I found a solution.

First we need a PolymorphicSerializer class :

from enum import Enum

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):
        """
        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
        """
        if hasattr(obj, 'get_type'):
            type_str = obj.get_type()
            if isinstance(type_str, Enum):
                type_str = type_str.value
        else:
            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']]
        validated_data.pop('type')
        return serializer(context=self.context).create(validated_data)

    def update(self, instance, validated_data):
        serializer = self.get_serializer_map()[validated_data['type']]
        validated_data.pop('type')
        return serializer(context=self.context).update(instance, validated_data)

and now :

class ElementSerializer(PolymorphicSerializer):

    class Meta:
        model = ElementModel

    def get_serializer_map(self):
        return {
            BooleanSerializer.__class__: BooleanSerializer,
            TextSerializer.__class__: TextSerializer,
        }

class RootSerializer(WritableNestedModelSerializer):
    elements = ElementSerializer(many=True)
    class Meta:
        model = RootModel
        fields = '__all__'

Reference link: https://stackoverflow.com/a/44727343/5367584

like image 117
Etienne Avatar answered Sep 23 '22 20:09

Etienne