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

时间:2016-07-11 21:13:33

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

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?

5 个答案:

答案 0 :(得分:6)

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

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

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

    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. https://github.com/AltSchool/dynamic-rest
  2. https://github.com/rsinger86/drf-flex-fields

其中(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响应以包含更多详细信息。