Consider this case where I have a Book
and Author
model.
serializers.py
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = models.Author
fields = ('id', 'name')
class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
class Meta:
model = models.Book
fields = ('id', 'title', 'author')
viewsets.py
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
This works great if I send a GET
request for a book. I get an output with a nested serializer containing the book details and the nested author details, which is what I want.
However, when I want to create/update a book, I have to send a POST
/PUT
/PATCH
with the nested details of the author instead of just their id. I want to be able to create/update a book object by specifying a author id and not the entire author object.
So, something where my serializer looks like this for a GET
request
class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
class Meta:
model = models.Book
fields = ('id', 'title', 'author')
and my serializer looks like this for a POST
, PUT
, PATCH
request
class BookSerializer(serializers.ModelSerializer):
author = PrimaryKeyRelatedField(queryset=Author.objects.all())
class Meta:
model = models.Book
fields = ('id', 'title', 'author')
I also do not want to create two entirely separate serializers for each type of request. I'd like to just modify the author
field in the BookSerializer
.
Lastly, is there a better way of doing this entire thing?
答案 0 :(得分:6)
相反,我更喜欢以下解决方案:
我认为这是最干净的方法。
在DRF: Allow all fields in GET request but restrict POST to just one field
查看我的类似问题和解决方案答案 1 :(得分:5)
DRF的一项功能是您可以动态更改序列化程序http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields上的字段
我的用例:在GET上使用slug字段,这样我们就可以看到很好的关系代表,但是在POST / PUT上切换回经典的主键更新。将序列化器调整为:
class FooSerializer(serializers.ModelSerializer):
bar = serializers.SlugRelatedField(slug_field='baz', queryset=models.Bar.objects.all())
class Meta:
model = models.Foo
fields = '__all__'
def __init__(self, *args, **kwargs):
super(FooSerializer, self).__init__(*args, **kwargs)
try:
if self.context['request'].method in ['POST', 'PUT']:
self.fields['bar'] = serializers.PrimaryKeyRelatedField(queryset=models.Bar.objects.all())
except KeyError:
pass
KeyError有时会在没有请求的情况下抛出代码初始化,可能是单元测试。
以负责任的方式享受和使用。
答案 2 :(得分:3)
You are looking for the get_serializer_class
method on the ViewSet
. This allows you to switch on request type for which serializer that you want to use.
from rest_framework import viewsets
class MyModelViewSet(viewsets.ModelViewSet):
model = MyModel
queryset = MyModel.objects.all()
def get_serializer_class(self):
if self.action in ('create', 'update', 'partial_update'):
return MySerializerWithPrimaryKeysForCreatingOrUpdating
else:
return MySerializerWithNestedData
答案 3 :(得分:0)
The way I ended up dealing with this problem was having another serializer for when it's a related field.
class HumanSerializer(PersonSerializer):
class Meta:
model = Human
fields = PersonSerializer.Meta.fields + (
'firstname',
'middlename',
'lastname',
'sex',
'date_of_birth',
'balance'
)
read_only_fields = ('name',)
class HumanRelatedSerializer(HumanSerializer):
def to_internal_value(self, data):
return self.Meta.model.objects.get(id=data['id'])
class PhoneNumberSerializer(serializers.ModelSerializer):
contact = HumanRelatedSerializer()
class Meta:
model = PhoneNumber
fields = (
'id',
'contact',
'phone',
'extension'
)
You could do something like this, but for the RelatedSerializer do:
def to_internal_value(self, data):
return self.Meta.model.objects.get(id=data)
Thus, when serializing, you serialize the related object, and when de-serializing, you only need the id to get the related object.
答案 4 :(得分:0)
我知道这有点晚了,但是以防万一有人需要它。有一些drf第三方软件包允许通过请求查询参数动态设置包括的序列化器字段(在官方文档中列出:https://www.django-rest-framework.org/api-guide/serializers/#third-party-packages)。
IMO最完整的是:
其中(1)比(2)具有更多功能(可能太多,取决于您要执行的操作)。
使用(2),您可以执行以下操作(从回购协议的自述文件中摘录):
class CountrySerializer(FlexFieldsModelSerializer):
class Meta:
model = Country
fields = ['name', 'population']
class PersonSerializer(FlexFieldsModelSerializer):
country = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Person
fields = ['id', 'name', 'country', 'occupation']
expandable_fields = {
'country': (CountrySerializer, {'source': 'country', 'fields': ['name']})
}
默认响应:
{
"id" : 13322,
"name" : "John Doe",
"country" : 12,
"occupation" : "Programmer"
}
执行GET / person / 13322?expand = country时,响应将更改为:
{
"id" : 13322,
"name" : "John Doe",
"country" : {
"name" : "United States"
},
"occupation" : "Programmer",
}
请注意,如何从嵌套的国家/地区对象中忽略人口。这是因为将字段传递给嵌入式CountrySerializer时将其设置为['name']。
通过这种方式,您可以保留POST请求(仅包括一个ID)和“扩展” GET响应以包含更多详细信息。