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

Change a field in a Django REST Framework ModelSerializer based on the request type?

考虑这种情况,我有一个 BookAuthor 模型。

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

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

但是,当我想要 create/update 一本书时,我必须发送一个 POST/PUT/PATCH 来代替作者的嵌套详细信息只是他们的身份证。我希望能够通过指定作者 ID 而不是整个作者对象来 create/update 一本书对象。

因此,对于 GET 请求

,我的序列化程序看起来像这样
class BookSerializer(serializers.ModelSerializer):
    author = AuthorSerializer(read_only=True)

    class Meta:
        model = models.Book
        fields = ('id', 'title', 'author')

对于 POSTPUTPATCH 请求

,我的序列化程序看起来像这样
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就可以得到相关对象

您正在 ViewSet 上查找 get_serializer_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

恕我直言,多个序列化程序只会造成越来越多的混乱。

我更喜欢下面的解决方案:

  1. 不要更改您的视图集(保留默认设置)
  2. 在序列化程序中添加 .validate() 方法;以及其他所需的 .create 或 .update() 等。在这里,真正的逻辑将进入 验证()方法。我们将根据请求类型在哪里创建 validated_data 按照我们的序列化程序的要求进行字典。

我认为这是最干净的方法。

查看我的类似问题和解决方案

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 有时会在没有请求的情况下在代码初始化时抛出,可能是单元测试。

享受并负责任地使用。

我知道有点晚了,但以防万一其他人需要它。 drf 有一些第三方包允许 通过请求查询参数 动态设置包含的序列化器字段(在官方文档中列出:https://www.django-rest-framework.org/api-guide/serializers/#third-party-packages)。

IMO 最完整的是:

  1. https://github.com/AltSchool/dynamic-rest
  2. https://github.com/rsinger86/drf-flex-fields

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

使用 (2) 您可以执行以下操作(摘自 repo 的自述文件):

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,并 "expand" GET 响应包含更多详细信息。