[英]django-rest-framework, multitable model inheritance, ModelSerializers and nested serializers
我在文档或互联网上找不到此信息。
最新 django-rest-framework, django 1.6.5
如何创建一个可以处理嵌套序列化器的 ModelSerializer,其中嵌套的 model 是使用多表 inheritance 实现的?
例如
######## 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,所以我可以检索一个查询集,其中每个实例已经是适当子类的一个实例。
谢谢
我以稍微不同的方式解决了这个问题。
使用:
我的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
,我在其中声明了“基本”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
“切换”发生在主序列化程序 ( PersonListSerializer
) 的to_representation
方法中。 它查看实例类型,然后“注入”所需的序列化程序。 由于Clergy
, Religious
都是从继承Person
取回一个Person
,这也是一个Clergy
成员,返回所有的Person
域和所有的Clergy
领域。 Religious
。 如果Person
既不是Clergy
也不是Religious
- 仅返回基本模型字段。
不确定这是否是正确的方法 - 但它似乎非常灵活,适合我的用例。 请注意,我通过不同的视图/序列化程序保存/更新/创建Person
- 所以我不必担心这种类型的设置。
我能够通过创建自定义相关字段来做到这一点
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, )
我确实担心为每个对象实例化一个 Serializer 是反向关系查询集是昂贵的,所以我想知道是否有更好的方法来做到这一点。
我尝试的另一种方法是动态更改 __init__ 内 MyBaseModelSerializer 上的模型字段,但我遇到了此处描述的问题:
django rest 框架嵌套模型序列化器
我正在尝试使用涉及不同模型子类的不同序列化器子类的解决方案:
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
类型的对象时,你实际上最终得到了一个子类的对象,它正确地序列化(对我来说至关重要的是反序列化)。
我刚刚开始使用它,所以可能存在我还没有遇到的问题。
使用 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
我通过谷歌发现这篇文章试图弄清楚如何处理多个表 inheritance 而不必检查 model 实例类型。 我实施了自己的解决方案。
在 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.