django-rest-framework + django-polymorphic ModelSerialization

Mic*_*ter 22 python django django-rest-framework

我想知道是否有人有一个将Django REST框架与django-polymorphic相结合的Pythonic解决方案.

鉴于:

class GalleryItem(PolymorphicModel):
    gallery_item_field = models.CharField()

class Photo(GalleryItem):
    custom_photo_field = models.CharField()

class Video(GalleryItem):
    custom_image_field = models.CharField()
Run Code Online (Sandbox Code Playgroud)

如果我想要一个django-rest-framework中所有GalleryItem的列表,它只会给我GalleryItem(父模型)的字段,因此:id,gallery_item_field和polymorphic_ctype.那不是我想要的.我想要custom_photo_field,如果它是一个Photo实例,我想要custom_image_field,如果它是一个视频.

Mic*_*ter 26

到目前为止,我只测试了这个GET请求,这有效:

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)
Run Code Online (Sandbox Code Playgroud)

对于POST和PUT请求,您可能希望执行类似于使用to_internal_value def覆盖to_representation定义的操作.

  • 我猜测POST和PUT可能稍微困难(尽管仍然可行),因为你需要确定用户想要提交的内容,在验证之前(如果字段丢失,则可能无法确定).恕我直言,使用单独的端点进行写请求更清晰. (4认同)

dar*_*ess 5

为了完善起见,我添加了to_internal_value()实现,因为我在最近的项目中需要它。

如何确定类型

方便地区分不同的“阶级”;为此,我已将type属性添加到基本多态模型中:

class GalleryItem(PolymorphicModel):
    gallery_item_field = models.CharField()

    @property
    def type(self):
        return self.__class__.__name__
Run Code Online (Sandbox Code Playgroud)

这允许将typeas称为“字段”和“只读字段”。

type 将包含python类名。

将类型添加到序列化器

您可以将type字段添加到“字段”和“只读字段”中(但是,如果要在所有子模型中使用它们,则需要在所有序列化器中指定类型字段)

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)
Run Code Online (Sandbox Code Playgroud)


Mic*_*sen 5

这是一个通用且可重用的解决方案。它是针对通用的,Serializer但对其进行修改并不难ModelSerializer。它也不能处理父类的序列化(在我的情况下,我更多地使用父类作为接口)。

from typing import Dict

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, 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)
Run Code Online (Sandbox Code Playgroud)

并使用它:

class ParentClassSerializer(PolymorphicSerializer):
    """
    Serializer for ParentClass objects
    """
    def get_serializer_map(self) -> Dict[str, serializers.Serializer]:
        """
        Return serializer map
        """
        return {
            ChildClass1.__name__: ChildClass1Serializer,
            ChildClass2.__name__: ChildClass2Serializer,
        }
Run Code Online (Sandbox Code Playgroud)