Django-Rest-Framework和Django-Polymorphic ModelSerialization

28
我想知道是否有Pythonic的解决方案将Django REST框架与django-polymorphic结合起来。
给定:
class GalleryItem(PolymorphicModel):
    gallery_item_field = models.CharField()

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

class Video(GalleryItem):
    custom_image_field = models.CharField()
如果我想要Django Rest Framework中所有GalleryItems的列表,它将只给出GalleryItem(父模型)的字段,即:id、gallery_item_field和polymorphic_ctype。这不是我想要的。如果是一个照片实例,我想要custom_photo_field,如果是视频,则想要custom_image_field。
3个回答

31

到目前为止,我只测试了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)

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


1
我找不到to_native方法的任何文档。它是在什么时候被调用的? - int_ua
http://www.django-rest-framework.org/topics/3.0-announcement/#changes-to-the-custom-field-api - int_ua
5
我猜想POST和PUT可能会稍微难一些(但仍可行),因为你需要在验证之前确定用户意图提交的内容,如果有字段缺失,可能无法确定。在我看来,使用单独的端点进行写入请求更加清晰。 - WhyNotHugo
to_native 更改为 to_representation - zie1ony

9

这里是一个通用且可重复使用的解决方案。它适用于通用序列化器(Serializer),但将其修改为使用模型序列化器(ModelSerializer)也并不困难。它还没有处理序列化父类(在我的情况下,我更多地将父类用作接口)。

from typing import Dict, Type

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, 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)

使用方法如下:

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,
        }

1
非常棒的解决方案!你能将其与DRF的可浏览文档集成吗? - michael_j_ward
谢谢,我不熟悉可浏览文档,所以没有使用。 - Syas
1
感谢您提供的解决方案。快速提醒:您的序列化器映射类型应该是 Dict[str, Type[serializers.Serializer]] 而不是 Dict[str, serializers.Serializer] - qcoumes

7

为了完整起见,我将添加to_internal_value()实现,因为我最近的项目需要这个。

如何确定类型

有可能区分不同“类”的可能性很方便。因此,我已经在基础多态模型中添加了类型属性以便于实现此目的:

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

    @property
    def type(self):
        return self.__class__.__name__

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

type将包含Python类名。

在序列化器中添加type

您可以将type添加到“字段”和“只读字段”中 (如果要在所有子模型中使用它们,则需要在所有序列化器中指定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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接