使用Django REST Framework,标准的ModelSerializer将允许通过将ID作为整数进行POST来分配或更改ForeignKey模型关系。
从嵌套的序列化器中获取此行为的最简单方法是什么?
注意,我只讨论分配现有数据库对象,而不是嵌套创建。
我曾经在序列化程序中使用额外的'id'字段以及自定义的create
和update
方法来破解这个问题,但这对我来说是一个看似简单且常见的问题,我很想知道最好的方法。
class Child(models.Model):
name = CharField(max_length=20)
class Parent(models.Model):
name = CharField(max_length=20)
phone_number = models.ForeignKey(PhoneNumber)
child = models.ForeignKey(Child)
class ChildSerializer(ModelSerializer):
class Meta:
model = Child
class ParentSerializer(ModelSerializer):
# phone_number relation is automatic and will accept ID integers
children = ChildSerializer() # this one will not
class Meta:
model = Parent
这里最好的解决方案是使用两个不同的字段:一个用于阅读,另一个用于写入。如果不做一些繁重的工作,很难在一个领域找到你想要的东西。
只读字段将是您的嵌套序列化程序(在本例中为ChildSerializer
),它将允许您获得您期望的相同嵌套表示。大多数人将此定义为child
,因为他们已经通过这一点编写了前端并且更改它会导致问题。
只写字段将是PrimaryKeyRelatedField
,这是您通常用于根据主键分配对象的内容。这不一定是只写的,特别是如果你试图在接收的内容和发送的内容之间保持对称,但听起来这可能最适合你。此字段应将a source
设置为外键字段(在此示例中为child
),以便在创建和更新时正确分配它。
这已经在讨论组中提出过几次了,我认为这仍然是最好的解决方案。感谢Sven Maurer for pointing it out。
以下是Kevin的答案所讨论的一个例子,如果你想采用这种方法并使用2个单独的字段。
在你的models.py中......
class Child(models.Model):
name = CharField(max_length=20)
class Parent(models.Model):
name = CharField(max_length=20)
phone_number = models.ForeignKey(PhoneNumber)
child = models.ForeignKey(Child)
然后serializers.py ...
class ChildSerializer(ModelSerializer):
class Meta:
model = Child
class ParentSerializer(ModelSerializer):
# if child is required
child = ChildSerializer(read_only=True)
# if child is a required field and you want write to child properties through parent
# child = ChildSerializer(required=False)
# otherwise the following should work (untested)
# child = ChildSerializer()
child_id = serializers.PrimaryKeyRelatedField(
queryset=Child.objects.all(), source='child', write_only=True)
class Meta:
model = Parent
设置source=child
让child_id
作为孩子将默认为没有被覆盖(我们期望的行为)。 write_only=True
使child_id
可以写入,但由于id已经出现在ChildSerializer
中,因此不会出现在响应中。
使用两个不同的字段就可以了(如@Kevin Brown和@joslarson提到的那样),但我认为它并不完美(对我来说)。因为从一个密钥(child
)获取数据并将数据发送到另一个密钥(child_id
)可能对前端开发人员来说有点模棱两可。 (完全没有冒犯)
所以,我在这里建议的是,覆盖to_representation()
的ParentSerializer
方法将完成这项工作。
def to_representation(self, instance):
response = super().to_representation(instance)
response['child'] = ChildSerializer(instance.child).data
return response
完整的Serializer表示
class ChildSerializer(ModelSerializer):
class Meta:
model = Child
fields = '__all__'
class ParentSerializer(ModelSerializer):
class Meta:
model = Parent
fields = '__all__'
def to_representation(self, instance):
response = super().to_representation(instance)
response['child'] = ChildSerializer(instance.child).data
return response
这种方法的优点?
通过使用此方法,我们不需要两个单独的字段来创建和读取。在这里,创建和阅读都可以使用child
键完成。
示例有效负载以创建parent
实例
{
"name": "TestPOSTMAN_name",
"phone_number": 1,
"child": 1
}
有一种方法可以在创建/更新操作上替换字段:
class ChildSerializer(ModelSerializer):
class Meta:
model = Child
class ParentSerializer(ModelSerializer):
child = ChildSerializer()
# called on create/update operations
def to_internal_value(self, data):
self.fields['child'] = serializers.PrimaryKeyRelatedField(
queryset=Child.objects.all())
return super(ParentSerializer, self).to_internal_value(data)
class Meta:
model = Parent
我认为Kevin概述的方法可能是最好的解决方案,但我无法让它发挥作用。当我同时拥有嵌套的序列化程序和主键字段集时,DRF不断抛出错误。删除一个或另一个将起作用,但显然没有给我我需要的结果。我能想到的最好的是创建两个不同的读取和写入序列化器,就像这样......
serialize认识.朋友:
class ChildSerializer(serializers.ModelSerializer):
class Meta:
model = Child
class ParentSerializer(serializers.ModelSerializer):
class Meta:
abstract = True
model = Parent
fields = ('id', 'child', 'foo', 'bar', 'etc')
class ParentReadSerializer(ParentSerializer):
child = ChildSerializer()
views.朋友
class ParentViewSet(viewsets.ModelViewSet):
serializer_class = ParentSerializer
queryset = Parent.objects.all()
def get_serializer_class(self):
if self.request.method == 'GET':
return ParentReadSerializer
else:
return self.serializer_class
这是我如何解决这个问题。
serializers.py
class ChildSerializer(ModelSerializer):
def to_internal_value(self, data):
if data.get('id'):
return get_object_or_404(Child.objects.all(), pk=data.get('id'))
return super(ChildSerializer, self).to_internal_value(data)
你只需要传递你的嵌套子序列化器,就像你从序列化器中获取它一样,即作为json / dictionary的child。在to_internal_value
中,如果子对象具有有效ID,则我们实例化该子对象,以便DRF可以进一步使用该对象。
这里的一些人已经设置了一种方法来保留一个字段,但仍然能够在检索对象时获取细节并仅使用ID创建它。如果人们感兴趣,我做了一个更通用的实现:
首先是测试:
from rest_framework.relations import PrimaryKeyRelatedField
from django.test import TestCase
from .serializers import ModelRepresentationPrimaryKeyRelatedField, ProductSerializer
from .factories import SomethingElseFactory
from .models import SomethingElse
class TestModelRepresentationPrimaryKeyRelatedField(TestCase):
def setUp(self):
self.serializer = ModelRepresentationPrimaryKeyRelatedField(
model_serializer_class=SomethingElseSerializer,
queryset=SomethingElse.objects.all(),
)
def test_inherits_from_primary_key_related_field(self):
assert issubclass(ModelRepresentationPrimaryKeyRelatedField, PrimaryKeyRelatedField)
def test_use_pk_only_optimization_returns_false(self):
self.assertFalse(self.serializer.use_pk_only_optimization())
def test_to_representation_returns_serialized_object(self):
obj = SomethingElseFactory()
ret = self.serializer.to_representation(obj)
self.assertEqual(ret, SomethingElseSerializer(instance=obj).data)
然后是班级本身:
from rest_framework.relations import PrimaryKeyRelatedField
class ModelRepresentationPrimaryKeyRelatedField(PrimaryKeyRelatedField):
def __init__(self, **kwargs):
self.model_serializer_class = kwargs.pop('model_serializer_class')
super().__init__(**kwargs)
def use_pk_only_optimization(self):
return False
def to_representation(self, value):
return self.model_serializer_class(instance=value).data
如果你在某个地方有一个序列化器,用法是这样的:
class YourSerializer(ModelSerializer):
something_else = ModelRepresentationPrimaryKeyRelatedField(queryset=SomethingElse.objects.all(), model_serializer_class=SomethingElseSerializer)
这将允许您仅使用PK创建具有外键的对象,但在检索您创建的对象时(或者无论何时真实地)将返回完整的序列化嵌套模型。
有一个包!查看Drf Extra Fields包中的PresentablePrimaryKeyRelatedField。