[英]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:使用:
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.由于Clergy
, Religious
都是从继承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.