简体   繁体   English

django-rest-framework,多表 model inheritance,模型序列化器和嵌套序列化器

[英]django-rest-framework, multitable model inheritance, ModelSerializers and nested serializers

I can't find this info in the docs or on the interwebs.我在文档或互联网上找不到此信息。
latest django-rest-framework, django 1.6.5最新 django-rest-framework, django 1.6.5

How does one create a ModelSerializer that can handle a nested serializers where the nested model is implemented using multitable inheritance?如何创建一个可以处理嵌套序列化器的 ModelSerializer,其中嵌套的 model 是使用多表 inheritance 实现的?

eg例如

######## 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

This obviously doesn't work but illustrates what i'm trying to do here.这显然不起作用,但说明了我在这里尝试做的事情。
In OtherModelSerializer, I'd like mybasemodel_set to serialize specific represenntations of either ModelA or ModelB depending on what we have.在 OtherModelSerializer 中,我希望 mybasemodel_set 根据我们拥有的内容序列化 ModelA 或 ModelB 的特定表示。

If it matters, I'm also using django.model_utils and inheritencemanager so i can retrieve a queryset where each instance is already an instance of appropriate subclass.如果它很重要,我还使用 django.model_utils 和inheritencemanager,所以我可以检索一个查询集,其中每个实例已经是适当子类的一个实例。

Thanks谢谢

I've solved this issue a slightly different way.我以稍微不同的方式解决了这个问题。

Using:使用:

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

My models.py look like this:我的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)

Basically - The base model is the "Person" model - and a person can either be Clergy, Religious, or neither and simply be a "Person".基本上 - 基本模型是“人”模型 - 一个人可以是神职人员,宗教人士,或者两者都不是,只是一个“人”。 While the models that inherit Person have special relationships as well.而继承Person的模型也有特殊的关系。

In my views.py I utilize a mixin to "inject" the subclasses into the queryset like so:在我的views.py我使用 mixin 将子类“注入”到查询集中,如下所示:

class PersonSubClassFieldsMixin(object):

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

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

And then real "unDRY" part comes in serializers.py where I declare the "base" PersonListSerializer, but override the to_representation method to return special serailzers based on the instance type like so:然后真正的“unDRY”部分出现在serializers.py ,我在其中声明了“基本”PersonListSerializer,但覆盖了to_representation方法以返回基于实例类型的特殊 serailzers,如下所示:

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

The "switch" happens in the to_representation method of the main serializer ( PersonListSerializer ). “切换”发生在主序列化程序 ( PersonListSerializer ) 的to_representation方法中。 It looks at the instance type, and then "injects" the needed serializer.它查看实例类型,然后“注入”所需的序列化程序。 Since Clergy , Religious are all inherited from Person getting back a Person that is also a Clergy member, returns all the Person fields and all the Clergy fields.由于ClergyReligious都是从继承Person取回一个Person ,这也是一个Clergy成员,返回所有的Person域和所有的Clergy领域。 Same goes for Religious . Religious And if the Person is neither Clergy or Religious - the base model fields are only returned.如果Person既不是Clergy也不是Religious - 仅返回基本模型字段。

Not sure if this is the proper approach - but it seems very flexible, and fits my usecase.不确定这是否是正确的方法 - 但它似乎非常灵活,适合我的用例。 Note that I save/update/create Person thru different views/serializers - so I don't have to worry about that with this type of setup.请注意,我通过不同的视图/序列化程序保存/更新/创建Person - 所以我不必担心这种类型的设置。

I was able to do this by creating a custom relatedfield我能够通过创建自定义相关字段来做到这一点

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

I do have concerns that instanting a Serializer for each object is the reverse relation queryset is expensive so I'm wondering if there is a better way to do this.我确实担心为每个对象实例化一个 Serializer 是反向关系查询集是昂贵的,所以我想知道是否有更好的方法来做到这一点。

Another approach i tried was dynamically changing the model field on MyBaseModelSerializer inside of __init__ but I ran into the issue described here:我尝试的另一种方法是动态更改 __init__ 内 MyBaseModelSerializer 上的模型字段,但我遇到了此处描述的问题:
django rest framework nested modelserializer django rest 框架嵌套模型序列化器

I'm attempting to use a solution that involves different serializer subclasses for the different model subclasses:我正在尝试使用涉及不同模型子类的不同序列化器子类的解决方案:

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

That is, when you try and instantiate an object of type MyBaseModelSerializer , you actually end up with an object of one of the subclasses, which serialize (and crucially for me, deserialize) correctly.也就是说,当你尝试实例化一个MyBaseModelSerializer类型的对象时,你实际上最终得到了一个子类的对象,它正确地序列化(对我来说至关重要的是反序列化)。

I've just started using this, so it's possible that there are problems I've not run into yet.我刚刚开始使用它,所以可能存在我还没有遇到的问题。

Using Django 3.1, I found that it is possible to override get_serializer instead of get_serializer_class , in which case you can access the instance as well as self.action and more.使用 Django 3.1,我发现可以覆盖get_serializer而不是get_serializer_class ,在这种情况下,您可以访问实例以及self.action等。

By default get_serializer will call get_serializer_class , but this behavior can be adjusted to your needs.默认情况下get_serializer将调用get_serializer_class ,但可以根据您的需要调整此行为。

This is cleaner and easier than the solutions proposed above, so I'm adding it to the thread.这比上面提出的解决方案更清晰、更容易,所以我将它添加到线程中。

Example:例子:

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

I found this post via Google trying to figure out how to handle multiple table inheritance without having to check the model instance type.我通过谷歌发现这篇文章试图弄清楚如何处理多个表 inheritance 而不必检查 model 实例类型。 I implemented my own solution.我实施了自己的解决方案。

I created a class factory and a mixin to generate the serializers for the child classes with the help of InheritanceManger from django-model-utils.在 django-model-utils 的InheritanceManger的帮助下,我创建了一个 class 工厂和一个 mixin 来为子类生成序列化程序。

models.py

from django.db import models
from model_utils import InheritanceManager


class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

    # Use the InheritanceManager for select_subclasses()
    objects = InheritanceManager()  

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

serializers.py

from rest_framework import serializers

from .models import Location

def modelserializer_factory(model, class_name='ModelFactorySerializer',
                            meta_cls=None, **kwargs):
    """Generate a ModelSerializer based on Model"""
  
    if meta_cls is None:
        # Create a Meta class with the model passed
        meta_cls = type('Meta', (object,), dict(model=model))
    elif not hasattr(meta_cls, 'model'):
        # If a meta_cls is provided but did not include a model,
        # set it to the model passed into this function
        meta_cls.model = model

    # Create the ModelSerializer class with the Meta subclass
    # we created above; also pass in any additional keyword
    # arguments via kwargs
    ModelFactorySerializer = type(class_name, (serializers.ModelSerializer,),
                                  dict(Meta=meta_cls, **kwargs))
    ModelFactorySerializer.__class__.__name__ = class_name
    return ModelFactorySerializer


class InheritedModelSerializerMixin:
    def to_representation(self, instance):
        # Get the model of the instance
        model = instance._meta.model
        
        # Override the model with the inherited model
        self.Meta.model = model
           
        # Create the serializer via the modelserializer_factory
        # This will use the name of the class this is mixed with.
       
        serializer = modelserializer_factory(model, self.__class__.__name__,
                                             meta_cls=self.Meta)
        # Instantiate the Serializer class with the instance
        # and return the data
        return serializer(instance=instance).data


# Mix in the InheritedModelSerializerMixin
class LocationSerializer(InheritedModelSerializerMixin, serializers.ModelSerializer):
    class Meta:
        model = Location   # 'model' is optional since it will use
                           # the instance's model

        exclude = ('serves_pizza',)  # everything else works as well

views.py

from .models import Location
from .serializers import LocationSerializer


# Any view should work.
# This is an example using viewsets.ReadOnlyModelViewSet
# Everything else works as usual. You will need to chain
# ".select_subclasses()" to the queryset to select the
# child classes.

class LocationViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Location.objects.all().select_subclasses() 
    serializer_class = LocationSerializer

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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