Python DRF:带有嵌套序列化程序的简单外键分配?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29950956/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
DRF: Simple foreign key assignment with nested serializers?
提问by John Rork
With Django REST Framework, a standard ModelSerializer will allow ForeignKey model relationships to be assigned or changed by POSTing an ID as an Integer.
使用 Django REST 框架,标准的 ModelSerializer 将允许通过将 ID 作为整数发布来分配或更改外键模型关系。
What's the simplestway to get this behavior out of a nested serializer?
从嵌套序列化程序中获取此行为的最简单方法是什么?
Note, I am only talking about assigning existing database objects, notnested creation.
注意,我只是在谈论分配现有的数据库对象,而不是嵌套创建。
I have hacked away around this in the past with additional 'id' fields in the serializer and with custom create
and update
methods, but this is such a seemingly simple and frequent issue for me that I'm curious to know the best way.
过去,我在序列化程序中使用额外的“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
采纳答案by Kevin Brown
The best solution here is to use two different fields: one for reading and the other for writing. Without doing some heavylifting, it is difficult to get what you are looking for in a single field.
这里最好的解决方案是使用两个不同的字段:一个用于阅读,另一个用于写作。如果不做一些繁重的工作,就很难在单个领域中获得您想要的东西。
The read-only field would be your nested serializer (ChildSerializer
in this case) and it will allow you to get the same nested representation that you are expecting. Most people define this as just child
, because they already have their front-end written by this point and changing it would cause problems.
只读字段将是您的嵌套序列化程序(ChildSerializer
在本例中),它将允许您获得与您期望的相同的嵌套表示。大多数人将其定义为 just child
,因为此时他们已经编写了前端,更改它会导致问题。
The write-only field would be a PrimaryKeyRelatedField
, which is what you would typically use for assigning objects based on their primary key. This does not have to be write-only, especially if you are trying to go for symmetry between what is received and what is sent, but it sounds like that might suit you best. This field should have a source
set to the foreign key field (child
in this example) so it assigns it properly on creation and updating.
只写字段将是 a PrimaryKeyRelatedField
,这通常用于根据对象的主键分配对象。这不一定是只写,特别是如果您试图在接收的内容和发送的内容之间寻求对称,但听起来这可能最适合您。该字段应该有一个source
设置为外键字段(child
在这个例子中),因此适当分配给它的创建和更新。
This has been brought up on the discussion group a few times, and I think this is still the best solution. Thanks to Sven Maurer for pointing it out.
这已经在讨论组中提出了几次,我认为这仍然是最好的解决方案。感谢Sven Maurer 指出。
回答by Jay Nielsen
I think the approach outlined by Kevin probably would be the best solution, but I couldn't ever get it to work. DRF kept throwing errors when I had both a nested serializer and a primary key field set. Removing one or the other would function, but obviously didn't give me the result I needed. The best I could come up with is creating two different serializers for reading and writing, Like so...
我认为凯文概述的方法可能是最好的解决方案,但我无法让它发挥作用。当我同时设置嵌套序列化程序和主键字段时,DRF 不断抛出错误。删除一个或另一个会起作用,但显然没有给我我需要的结果。我能想到的最好的方法是创建两个不同的序列化器来读取和写入,就像这样......
serializers.py:
序列化程序.py:
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.py
视图.py
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
回答by joslarson
Here's an example of what Kevin's answer is talking about, if you want to take that approach and use 2 separate fields.
如果您想采用这种方法并使用 2 个单独的字段,这是 Kevin 的答案所谈论的示例。
In your models.py...
在你的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)
then serializers.py...
然后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
Setting source=child
lets child_id
act as child would by default had it not be overridden (our desired behavior). write_only=True
makes child_id
available to write to, but keeps it from showing up in the response since the id already shows up in the ChildSerializer
.
如果没有被覆盖(我们期望的行为),则设置source=child
让child_id
默认情况下充当子项。write_only=True
使其child_id
可用于写入,但防止它出现在响应中,因为 id 已经出现在ChildSerializer
.
回答by Gaurav Butola
Here's how I've solved this problem.
这是我解决这个问题的方法。
serializers.py
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)
You'll just pass your nested child serializer just as you get it from the serializer ie child as a json/dictionary. in to_internal_value
we instantiate the child object if it has a valid ID so that DRF can further work with the object.
您只需传递嵌套的子序列化程序,就像您从序列化程序中获取它一样,即作为 json/dictionary 的子程序。在to_internal_value
我们实例子对象,如果它有一个有效的ID,以便DRF可与该对象的进一步工作。
回答by Anton Dmitrievsky
There is a way to substitute a field on create/update operation:
有一种方法可以替换创建/更新操作中的字段:
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
回答by JPG
Using two different fields would be ok(as @Kevin Brownand @joslarsonmentioned), but I think it's not perfect(to me). Because getting data from one key (child
) and sending data to another key (child_id
) might be a little bit ambiguous for front-enddevelopers. (no offense at all)
So, what I suggest here is, override the to_representation()
method of ParentSerializer
will do the job.
使用两个不同的领域将是确定(如@Kevin布朗和@joslarson提到的),但我认为它不是完美的(对我来说)。因为从一个键 ( child
)获取数据并将数据发送到另一个键 ( child_id
) 对于前端开发人员来说可能有点模棱两可。(完全没有冒犯)
所以,我在这里建议的是,覆盖will 完成这项工作的to_representation()
方法ParentSerializer
。
def to_representation(self, instance):
response = super().to_representation(instance)
response['child'] = ChildSerializer(instance.child).data
return response
Complete representation of Serializer
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
Advantage of this method?
这种方法的优点?
By using this method, we don't need two separate fields for creation and reading. Here both creation and reading can be done by using child
key.
Sample payload to create parent
instance
通过使用这种方法,我们不需要创建和读取两个单独的字段。这里的创建和读取都可以通过使用child
key来完成。
创建parent
实例的示例负载
{
"name": "TestPOSTMAN_name",
"phone_number": 1,
"child": 1
}
回答by Bono
A few people here have placed a way to keep one field but still be able to get the details when retrieving the object and create it with only the ID. I made a little more generic implementation if people are interested:
这里的一些人已经放置了一种方法来保留一个字段,但在检索对象并仅使用 ID 创建它时仍然能够获取详细信息。如果人们感兴趣,我做了一些更通用的实现:
First off the tests:
首先是测试:
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)
Then the class itself:
然后是类本身:
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
The usage is like so, if you have a serializer somewhere:
用法是这样的,如果你在某个地方有一个序列化器:
class YourSerializer(ModelSerializer):
something_else = ModelRepresentationPrimaryKeyRelatedField(queryset=SomethingElse.objects.all(), model_serializer_class=SomethingElseSerializer)
This will allow you to create an object with a foreign key still only with the PK, but will return the full serialized nested model when retrieving the object you created (or whenever really).
这将允许您创建一个带有外键的对象,但仍然只使用 PK,但在检索您创建的对象时(或任何时候)将返回完整的序列化嵌套模型。
回答by Yi?it Güler
There is a package for that! Check out PresentablePrimaryKeyRelatedField in Drf Extra Fields package.
有一个包!查看 Drf Extra Fields 包中的 PresentablePrimaryKeyRelatedField。