Django Rest Framework抽象类序列化器

28

我有一些像这样的模型:

class TypeBase(models.Model):
    name = models.CharField(max_length=20)
    class Meta:
        abstract=True

class PersonType(TypeBase):
    pass

class CompanyType(TypeBase):
    pass

有了这个,我想创建一个仅包含所有这些字段类型(序列化、反序列化、更新和保存)的序列化程序。

更具体地说,我希望只有一个序列化程序(TypeBaseSerializer),它在UI上打印下拉菜单,对JSON响应进行序列化、反序列化post并保存所有基础类型。

就像这样:

class TypeBaseSerializer(serializers.Serializer):
    class Meta:
        model = TypeBase
        fields = ('id', 'name')

能行吗?


这个讨论对于子类化序列化器也很有用:https://github.com/tomchristie/django-rest-framework/issues/1926 - PhoebeB
4个回答

21

我认为以下方法更加清晰。您可以为基础序列化器设置"abstract"字段为true,并为所有子序列化器添加通用逻辑。

class TypeBaseSerializer(serializers.ModelSerializer):
    class Meta:
        model = TypeBase
        fields = ('id', 'name')
        abstract = True

    def func(...):
    # ... some logic

然后创建子序列化器并使用它们进行数据操作。

class PersonTypeSerializer(TypeBaseSerializer):
    class Meta:
        model = PersonType
        fields = ('id', 'name')


class CompanyTypeSerializer(TypeBaseSerializer):
    class Meta:
        model = CompanyType
        fields = ('id', 'name')

现在你可以为每个模型正常使用这两个序列化器。

但是如果你真的想要一个序列化器适用于这两个模型,那么就创建一个容器模型和它的序列化器。这样更加简洁 :)


谢谢你的回答,它给了我一个提示,让我解决了一个问题。 - lmiguelvargasf
abstract = True 设置在 TypeBaseSerializer 上,因为不存在抽象序列化器这样的东西。如果我错了,请纠正我。 - GunnerFan
2
@GunnerFan,你是正确的,“abstract”不是可设置的属性。正确的方法是只需使用一个serializer.Serializer作为基类,它实际上最终将成为一个mixin,并使用serializers.ModelSerializer作为派生类。 - jenks

18

正如在Sebastian Wozny的回答中已经提到的那样,您无法使用ModelSerializer与抽象基本模型。

另外,没有所谓的抽象序列化程序,正如其他一些答案所建议的那样。因此,在一个序列化器的元类上设置abstract = True不起作用

但是,您不需要使用ModelSerializer作为基本/父序列化器。您可以使用Serializer,然后利用Django的多重继承。以下是它的工作原理:

class TypeBaseSerializer(serializers.Serializer):
    # Need to re-declare fields since this is not a ModelSerializer
    name = serializers.CharField()
    id = serializers.CharField()

    class Meta:
        fields = ['id', 'name']

    def someFunction(self):
        #... will be available on child classes ...
        pass

class PersonTypeSerializer(TypeBaseSerializer, serializers.ModelSerializer):

    class Meta:
        model = PersonType
        fields = TypeBaseSerializer.Meta.fields + ['another_field']


class CompanyTypeSerializer(TypeBaseSerializer, serializers.ModelSerializer):

    class Meta:
        model = CompanyType
        fields = TypeBaseSerializer.Meta.fields + ['some_other_field']

现在由于父类(TypeBaseSerializer)上声明了字段nameid,它们将在PersonTypeSerializer上可用,并且由于这是ModelSerializer的子类,这些字段将从模型实例中填充。

你也可以在TypeBaseSerializer上使用SerializerMethodField,即使它不是一个ModelSerializer。

class TypeBaseSerializer(serializers.Serializer):
    # you will have to re-declare fields here since this is not a ModelSerializer
    name = serializers.CharField()
    id = serializers.CharField()
    other_field = serializers.SerializerMethodField()

    class Meta:
        fields = ['id', 'name', 'other_field']

    def get_other_field(self, instance):
        # will be available on child classes, which are children of ModelSerializers
        return instance.other_field

1
谢谢,这是一个很大的头疼问题,你的解决方案起作用了。 - cgitosh
不确定为什么这不是被批准的答案。它完美地解决了我的问题。 - trubliphone

17

不能使用ModelSerializer来处理抽象基模型。 来自restframework.serializers:

if model_meta.is_abstract_model(self.Meta.model):
        raise ValueError(
            'Cannot use ModelSerializer with Abstract Models.'
        )

我为类似问题编写了一个serializer_factory函数:

from collections import OrderedDict
from restframework.serializers import ModelSerializer
def serializer_factory(mdl, fields=None, **kwargss):
""" Generalized serializer factory to increase DRYness of code.

:param mdl: The model class that should be instanciated
:param fields: the fields that should be exclusively present on the serializer
:param kwargss: optional additional field specifications
:return: An awesome serializer
"""

    def _get_declared_fields(attrs):
        fields = [(field_name, attrs.pop(field_name))
                  for field_name, obj in list(attrs.items())
                  if isinstance(obj, Field)]
        fields.sort(key=lambda x: x[1]._creation_counter)
        return OrderedDict(fields)

    # Create an object that will look like a base serializer
    class Base(object):
        pass

    Base._declared_fields = _get_declared_fields(kwargss)

    class MySerializer(Base, ModelSerializer):
        class Meta:
            model = mdl

        if fields:
            setattr(Meta, "fields", fields)

    return MySerializer

你可以使用工厂根据需要生成序列化程序:
def typebase_serializer_factory(mdl):
    myserializer = serializer_factory(
        mdl,fields=["id","name"],
        #owner=HiddenField(default=CurrentUserDefault()),#Optional additional configuration for subclasses 
      )
    return myserializer

现在实例化不同的子类序列化器:

persontypeserializer = typebase_serializer_factory(PersonType)
companytypeserializer = typebase_serializer_factory(CompanyType)

这个解决方案似乎适用于我的问题,但有没有更少通用的方法?我认为在我的情况下,我总是会知道我的类型的字段,我只需要动态实例化它。 - Tiago Framesqui
您可以为子类创建两个序列化程序。我曾经遇到过7个子类和不同的序列化模式,因此通用性非常重要。 - Sebastian Wozny
_get_declared_fields函数中的Field变量是从哪里来的? - Austin Hallett

9

稍微解释一下@adki的回答:

  1. 可以跳过TypeBaseSerializer的模型;
  2. 派生的序列化器可以引用TypeBaseSerializer.Meta,这样你只需要在一个地方更改它们。
class TypeBaseSerializer(serializers.Serializer):
    class Meta:
        fields = ('id', 'name', 'created')
        abstract = True

    def func(...):
    # ... some logic

class PersonTypeSerializer(TypeBaseSerializer):
    class Meta:
        model = PersonType
        fields = TypeBaseSerializer.Meta.fields + ('age', 'date_of_birth')

class CompanyTypeSerializer(TypeBaseSerializer):
    class Meta:
        model = CompanyType
        fields = TypeBaseSerializer.Meta.fields

5
据我所见,类serializers.Serializer没有属性meta。但是它有一个名为Meta的属性。因此,我认为应该使用TypeBaseSerializer.Meta.fields而不是TypeBaseSerializer.meta.fields - cezar
1
您在 PersonTypeSerializer.Meta 上设置了一个 model = .. 属性。但是 PersonTypeSerializerserializers.Serializer 的子类,因此我认为该模型属性在那里不起作用。 - GunnerFan

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