简体   繁体   中英

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM