【问题标题】:Change a field in a Django REST Framework ModelSerializer based on the request type?根据请求类型更改 Django REST Framework ModelSerializer 中的字段?
【发布时间】:2016-11-13 22:54:05
【问题描述】:

考虑这种情况,我有一个 Book 和 Author 模型。

序列化器.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')

对于POST、PUT、PATCH 请求,我的序列化程序看起来像这样

class BookSerializer(serializers.ModelSerializer):
    author = PrimaryKeyRelatedField(queryset=Author.objects.all())

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

我也不想为每种类型的请求创建两个完全独立的序列化程序。我只想修改BookSerializer 中的author 字段。

最后,有没有更好的方法来做这整个事情?

【问题讨论】:

  • 查看django-rest-framework.org/api-guide/routers - 根据您的需要添加装饰器。
  • @dmitryro 我不明白。你能进一步解释一下吗?添加装饰器如何修改序列化器的字段?
  • 您必须创建一个自定义路由器来处理不同的请求方法 - POST、GET、PUT,并根据您要使用的请求方法装饰您的方法 - 文档提供了一些示例。另见stackoverflow.com/questions/28957912/…

标签: django django-rest-framework django-serializer


【解决方案1】:

我知道这有点晚了,但以防其他人需要它。有一些用于 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,并“扩展”GET 响应以包含更多详细信息。

【讨论】:

  • 谢谢,drf-flex-fields 正是我所需要的。
【解决方案2】:

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

负责任地享受和使用。

【讨论】:

    【解决方案3】:

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

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

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

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

    在DRF: Allow all fields in GET request but restrict POST to just one field查看我的类似问题和解决方案

    【讨论】:

      【解决方案4】:

      您正在寻找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
      

      【讨论】:

      • 这是一种可行的方法,但我不希望声明两个完全独立的序列化程序,因为那将是多余的。我想知道在一个序列化程序中是否有更好的方法。
      【解决方案5】:

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

      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就可以得到相关的对象了。

      【讨论】:

      • 做到了。不工作。我做错什么了吗? class AuthorRelatedSerializer(AuthorSerializer): def to_internal_value(self, data): return self.Meta.model.objects.get(id=data['id']) class BookSerializer(serializers.ModelSerializer): author = AuthorRelatedSerializer()
      • 我的例子需要 {id: 6, ...},如果你只是传递一个整数,它应该是def to_internal_value(self, data): return self.Meta.model.objects.get(id=data)
      猜你喜欢
      • 2020-06-27
      • 1970-01-01
      • 2013-08-26
      • 2017-03-05
      • 2017-03-21
      • 2021-08-08
      • 1970-01-01
      • 2021-05-11
      相关资源
      最近更新 更多