django-rest-framework、多表模型继承、ModelSerializers 和嵌套序列化器

Posted

技术标签:

【中文标题】django-rest-framework、多表模型继承、ModelSerializers 和嵌套序列化器【英文标题】:django-rest-framework, multitable model inheritance, ModelSerializers and nested serializers 【发布时间】:2014-07-25 18:17:22 【问题描述】:

我在文档或互联网上找不到此信息。 最新的 django-rest-framework,django 1.6.5

如何创建一个可以处理嵌套序列化器的 ModelSerializer,其中嵌套模型是使用多表继承实现的?

例如

######## MODELS
class OtherModel(models.Model):
    stuff = models.CharField(max_length=255)

class MyBaseModel(models.Model):
    whaddup = models.CharField(max_length=255)
    other_model = models.ForeignKey(OtherModel)

class ModelA(MyBaseModel):
    attr_a = models.CharField(max_length=255)

class ModelB(MyBaseModel):
    attr_b = models.CharField(max_length=255)


####### SERIALIZERS
class MyBaseModelSerializer(serializers.ModelSerializer):
    class Meta:
        model=MyBaseModel

class OtherModelSerializer(serializer.ModelSerializer):
    mybasemodel_set = MyBaseModelSerializer(many=True)

    class Meta:
        model = OtherModel

这显然不起作用,但说明了我在这里尝试做的事情。 在 OtherModelSerializer 中,我希望 mybasemodel_set 根据我们拥有的内容序列化 ModelA 或 ModelB 的特定表示。

如果重要的话,我还使用 django.model_utils 和inheritencemanager,所以我可以检索一个查询集,其中每个实例已经是适当子类的一个实例。

谢谢

【问题讨论】:

【参考方案1】:

我以稍微不同的方式解决了这个问题。

使用:

DRF 3.5.x django-model-utils 2.5.x

我的models.py 看起来像这样:

class Person(models.Model):
    first_name = models.CharField(max_length=40, blank=False, null=False)
    middle_name = models.CharField(max_length=80, blank=True, null=True)
    last_name = models.CharField(max_length=80, blank=False, null=False)
    family = models.ForeignKey(Family, blank=True, null=True)


class Clergy(Person):
    category = models.IntegerField(choices=CATEGORY, blank=True, null=True)
    external = models.NullBooleanField(default=False, null=True)
    clergy_status = models.ForeignKey(ClergyStatus, related_name="%(class)s_status", blank=True, null=True)


class Religious(Person):
    religious_order = models.ForeignKey(ReligiousOrder, blank=True, null=True)
    major_superior = models.ForeignKey(Person, blank=True, null=True, related_name="%(class)s_superior")


class ReligiousOrder(models.Model):
    name = models.CharField(max_length=255, blank=False, null=False)
    initials = models.CharField(max_length=20, blank=False, null=False)


class ClergyStatus(models.Model):
    display_name = models.CharField(max_length=255, blank=True, null=True)
    description = models.CharField(max_length=255, blank=True, null=True)

基本上 - 基本模型是“人”模型 - 一个人可以是神职人员、宗教人士,或者两者都不是,而只是一个“人”。而继承Person的模型也有特殊关系。

在我的 views.py 中,我使用 mixin 将子类“注入”到查询集中,如下所示:

class PersonSubClassFieldsMixin(object):

    def get_queryset(self):
        return Person.objects.select_subclasses()

class RetrievePersonAPIView(PersonSubClassFieldsMixin, generics.RetrieveDestroyAPIView):
    serializer_class = PersonListSerializer
    ...

然后真正的“unDRY”部分出现在 serializers.py 中,我在其中声明了“base”PersonListSerializer,但覆盖了 to_representation 方法以根据实例类型返回特殊的 serailzer,如下所示:

class PersonListSerializer(serializers.ModelSerializer):

    def to_representation(self, instance):
        if isinstance(instance, Clergy):
            return ClergySerializer(instance=instance).data
        elif isinstance(instance, Religious):
            return ReligiousSerializer(instance=instance).data
        else:
            return LaySerializer(instance=instance).data

    class Meta:
        model = Person
        fields = '__all__'


class ReligiousSerializer(serializers.ModelSerializer):
    class Meta:
        model = Religious
        fields = '__all__'
        depth = 2


class LaySerializer(serializers.ModelSerializer):
    class Meta:
        model = Person
        fields = '__all__'


class ClergySerializer(serializers.ModelSerializer):
    class Meta:
        model = Clergy
        fields = '__all__'
        depth = 2

“切换”发生在主序列化程序 (PersonListSerializer) 的 to_representation 方法中。它查看实例类型,然后“注入”所需的序列化程序。由于ClergyReligious 都是从Person 继承的,返回一个Person 也是Clergy 成员,返回所有Person 字段和所有Clergy 字段。 Religious 也是如此。如果Person 既不是Clergy 也不是Religious - 仅返回基本模型字段。

不确定这是否是正确的方法 - 但它看起来非常灵活,适合我的用例。请注意,我通过不同的视图/序列化程序保存/更新/创建 Person - 所以我不必担心这种类型的设置。

【讨论】:

你在哪里使用 django-model-utils 2.5.? 在django-model-utils documentation 中,我们可以看到Person.objects.select_subclasses() 是由Jeff 在他的示例中可能忘记使用的InheritanceManager 添加的。所以,在Person 类中,我想你可能会发现一行包含objects = InheritanceManager() 理想情况下,您还需要处理to_internal_value()【参考方案2】:

我可以通过创建自定义相关字段来做到这一点

class MyBaseModelField(serializers.RelatedField):
    def to_native(self, value):
        if isinstance(value, ModelA):
            a_s = ModelASerializer(instance=value)
            return a_s.data
        if isinstance(value, ModelB):
            b_s = ModelBSerializer(instance=value)
            return b_s.data

        raise NotImplementedError


class OtherModelSerializer(serializer.ModelSerializer):
    mybasemodel_set = MyBaseModelField(many=True)

    class Meta:
        model = OtherModel
        fields = # make sure we manually include the reverse relation (mybasemodel_set, )

我确实担心为每个对象实例化一个序列化器是反向关系查询集很昂贵,所以我想知道是否有更好的方法来做到这一点。

我尝试的另一种方法是在 __init__ 内动态更改 MyBaseModelSerializer 上的模型字段,但我遇到了此处描述的问题:django rest framework nested modelserializer

【讨论】:

django-rest-framework.org/topics/3.0-announcement/… 您找到更好的解决方案了吗? 更新了评论 #1 中断开链接的链接:DRF3.0 - changes to the custom field API【参考方案3】:

使用 Django 3.1,我发现可以覆盖 get_serializer 而不是 get_serializer_class,在这种情况下,您可以访问实例以及 self.action 等等。

默认情况下get_serializer 将调用get_serializer_class,但可以根据您的需要调整此行为。

这比上面提出的解决方案更干净、更容易,所以我将它添加到线程中。

例子:

class MySubclassViewSet(viewsets.ModelViewSet):
    # add your normal fields and methods ...

    def get_serializer(self, *args, **kwargs):
        if self.action in ('list', 'destroy'):
            return MyListSerializer(args[0], **kwargs)
        if self.action in ('retrieve', ):
            instance = args[0]
            if instance.name.contains("really?"):  # or check if instance of a certain Model...
                return MyReallyCoolSerializer(instance)
            else return MyNotCoolSerializer(instance)
        # ... 
        return MyListSerializer(*args, **kwargs)  # default

【讨论】:

【参考方案4】:

我正在尝试使用涉及不同模型子类的不同序列化程序子类的解决方案:

class MyBaseModelSerializer(serializers.ModelSerializer):

    @staticmethod
    def _get_alt_class(cls, args, kwargs):
        if (cls != MyBaseModel):
            # we're instantiating a subclass already, use that class
            return cls

        # < logic to choose an alternative class to use >
        # in my case, I'm inspecting kwargs["data"] to make a decision
        # alt_cls = SomeSubClass

        return alt_cls

    def __new__(cls, *args, **kwargs):
        alt_cls = MyBaseModel.get_alt_class(cls, args, kwargs)
        return super(MyBaseModel, alt_cls).__new__(alt_cls, *args, **kwargs)

    class Meta:
        model=MyBaseModel

class ModelASerializer(MyBaseModelSerializer):
    class Meta:
        model=ModelA

class ModelBSerializer(MyBaseModelSerializer):
    class Meta:
        model=ModelB

也就是说,当您尝试实例化 MyBaseModelSerializer 类型的对象时,您实际上最终得到了一个子类的对象,该子类正确地序列化(对我来说至关重要的是反序列化)。

我刚开始使用它,所以可能有我还没有遇到的问题。

【讨论】:

为什么让 get_alt_class 成为你最终传递给它的静态方法 cls ?有什么原因吗?

以上是关于django-rest-framework、多表模型继承、ModelSerializers 和嵌套序列化器的主要内容,如果未能解决你的问题,请参考以下文章

drf 多表

django-rest-framework:如何序列化已经包含 JSON 的字段?

django-rest-framework 是不是提供管理站点来管理模型?

断言错误:Django-rest-Framework

记录对 django-rest-framework 的请求

django-rest-framework 按日期过滤=无