根据请求类型更改Django REST Framework ModelSerializer中的字段?

根据请求类型更改Django REST Framework ModelSerializer中的字段?,django,django-rest-framework,django-serializer,Django,Django Rest Framework,Django Serializer,考虑一下这个例子,我有一本书和作者模型 序列化程序.py class AuthorSerializer(serializers.ModelSerializer): class Meta: model = models.Author fields = ('id', 'name') class BookSerializer(serializers.ModelSerializer): author = AuthorSerializer(read_on

考虑一下这个例子,我有一本
作者
模型

序列化程序.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
如果我发送
GET
图书请求,这将非常有效。我得到一个包含书籍详细信息和嵌套作者详细信息的嵌套序列化程序的输出,这正是我想要的

但是,当我想创建/更新一本书时,我必须发送一个
POST
/
PUT
/
PATCH
,其中包含作者的嵌套详细信息,而不仅仅是他们的id。我希望能够通过指定作者id而不是整个作者对象来创建/更新图书对象

因此,对于
GET
请求,我的序列化程序看起来像这样

class BookSerializer(serializers.ModelSerializer):
    author = AuthorSerializer(read_only=True)

    class Meta:
        model = models.Book
        fields = ('id', 'title', 'author')
class BookSerializer(serializers.ModelSerializer):
    author = PrimaryKeyRelatedField(queryset=Author.objects.all())

    class Meta:
        model = models.Book
        fields = ('id', 'title', 'author')
我的序列化程序对于
POST
PUT
PATCH
请求是这样的

class BookSerializer(serializers.ModelSerializer):
    author = AuthorSerializer(read_only=True)

    class Meta:
        model = models.Book
        fields = ('id', 'title', 'author')
class BookSerializer(serializers.ModelSerializer):
    author = PrimaryKeyRelatedField(queryset=Author.objects.all())

    class Meta:
        model = models.Book
        fields = ('id', 'title', 'author')
我也不想为每种类型的请求创建两个完全独立的序列化程序。我只想修改
BookSerializer
中的
author
字段


最后,有没有更好的方法来处理整个问题?

我最终处理这个问题的方法是在相关字段中使用另一个序列化程序

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'
        )
您可以这样做,但对于RelatedSerializer,请执行以下操作:

 def to_internal_value(self, data):
     return self.Meta.model.objects.get(id=data)

因此,序列化时,序列化相关对象,反序列化时,只需id即可获取相关对象。

您正在查找
视图集上的
get\u serializer\u class
方法。这允许您打开要使用的序列化程序的请求类型

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

依我看,多个序列化程序只会造成越来越多的混乱

相反,我更喜欢以下解决方案:

  • 不更改视图集(保留默认值)
  • 在序列化程序中添加.validate()方法;与其他必需的.create或.update()等一起,这里将加入真正的逻辑 validate()方法。根据我们将要创建的请求类型 按照我们的序列化程序的要求验证\u数据dict
  • 我认为这是最干净的方法


    请参阅我在

    上的类似问题和解决方案。DRF有一个功能,您可以动态更改序列化程序上的字段

    我的用例是:在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有时在没有请求(可能是单元测试)的情况下在代码初始化时抛出


    享受并负责任地使用。

    我知道现在有点晚了,但只是以防万一,其他人需要它。drf有一些第三方软件包,允许通过请求查询参数动态设置包含的序列化程序字段(在官方文档中列出:)

    国际海事组织最完整的是:

  • 其中(1)的功能比(2)多(可能太多,取决于您想做什么)

    使用(2)您可以做以下事情(摘自回购协议自述):

    默认响应:

    当您执行GET/person/13322?expand=country时,响应将更改为:

    注意人口是如何从嵌套的country对象中删除的。这是因为字段在传递给嵌入式CountrySerializer时被设置为['name']


    通过这种方式,您可以保留仅包含id的POST请求,并“扩展”GET响应以包含更多详细信息。

    查看-根据您的需要添加装饰器。@dmitryro我不明白。你能进一步解释一下吗?添加装饰器将如何修改序列化器的字段?您必须创建一个自定义路由器,该路由器将处理不同的请求方法—POST、GET、PUT,并根据您希望使用的请求方法装饰您的方法—文档提供了一些示例。也看这个做这个。不起作用。我做错什么了吗<代码>类AuthorRelatedSerializer(AuthorSerializer):def to_internal_value(self,data):返回self.Meta.model.objects.get(id=data['id'])类BookSerializer(serializer.ModelSerializer):author=AuthorRelatedSerializer()
    我的示例需要{id:6,},如果您只是传递一个整数,它应该是
    def to_internal_value(self,data):返回self.Meta.model.objects.get(id=data)
    这是一种可能的工作方式,但我不希望声明两个完全独立的序列化程序,因为这是多余的。我想知道是否有更好的方法在一个序列化程序中实现这一点。谢谢,
    drf flex fields
    正是我所需要的。
    {
      "id" : 13322,
      "name" : "John Doe",
      "country" : {
        "name" : "United States"
      },
      "occupation" : "Programmer",
    }