Django Rest Framework:动态返回字段子集

Dan*_*gen 85 django django-rest-framework

问题

根据blogpost 设计实用RESTful API的最佳实践中的建议,我想在fields基于Django Rest Framework的API中添加一个查询参数,使用户只能为每个资源选择一个字段子集.

串行:

class IdentitySerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = models.Identity
        fields = ('id', 'url', 'type', 'data')
Run Code Online (Sandbox Code Playgroud)

常规查询将返回所有字段.

GET /identities/

[
  {
    "id": 1,
    "url": "http://localhost:8000/api/identities/1/",
    "type": 5,
    "data": "John Doe"
  },
  ...
]
Run Code Online (Sandbox Code Playgroud)

fields参数的查询应该只返回字段的子集:

GET /identities/?fields=id,data

[
  {
    "id": 1,
    "data": "John Doe"
  },
  ...
]
Run Code Online (Sandbox Code Playgroud)

包含无效字段的查询应忽略无效字段或抛出客户端错误.

目标

这有可能开箱即用吗?如果没有,实现这个的最简单方法是什么?是否有第三方包已经这样做了吗?

小智 100

您可以根据查询参数覆盖序列化程序__init__方法并fields动态设置属性.您可以访问request上下文的对象,传递给序列化程序.

在这里,我创建了一个可重用的mixin,它可以进行动态__init__修改.

from rest_framework import serializers

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
    """
    A ModelSerializer that takes an additional `fields` argument that
    controls which fields should be displayed.
    """

    def __init__(self, *args, **kwargs):
        # Instantiate the superclass normally
        super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

        fields = self.context['request'].query_params.get('fields')
        if fields:
            fields = fields.split(',')
            # Drop any fields that are not specified in the `fields` argument.
            allowed = set(fields)
            existing = set(self.fields.keys())
            for field_name in existing - allowed:
                self.fields.pop(field_name)


class UserSerializer(DynamicFieldsModelSerializer, serializers.HyperlinkedModelSerializer):

    class Meta:
        model = User
        fields = ('url', 'username', 'email')
Run Code Online (Sandbox Code Playgroud)

  • 仅供参考:此示例是此处的DRF文档的逐字副本:http://www.django-rest-framework.org/api-guide/serializers/#example不提供原始作者链接的错误表单 (21认同)
  • 在最近的Django版本中,您需要将`QUERY_PARAMS`更改为`query_params`,但除此之外,这就像魅力一样. (7认同)
  • 我终于来实现这个,它完美无缺!谢谢.我最后为此编写了一个mixin,组合比子类化更灵活:) https://gist.github.com/dbrgn/4e6fc1fe5922598592d6 (3认同)
  • 您可能应该检查`requests`是否作为`context`的成员存在.虽然它在生产中不会在运行单元测试时手动创建对象. (3认同)
  • 自发布此答案以来,从中复制了此答案的[DRF文档](http://www.django-rest-framework.org/api-guide/serializers/#example)已得到改进。 (2认同)

wim*_*wim 46

此功能可从第三方软件包中获得.

pip install djangorestframework-queryfields
Run Code Online (Sandbox Code Playgroud)

像这样声明你的序列化器:

from rest_framework.serializers import ModelSerializer
from drf_queryfields import QueryFieldsMixin

class MyModelSerializer(QueryFieldsMixin, ModelSerializer):
    ...
Run Code Online (Sandbox Code Playgroud)

然后,现在可以使用查询参数指定字段(客户端):

GET /identities/?fields=id,data
Run Code Online (Sandbox Code Playgroud)

排除过滤也是可能的,例如,返回 id 之外的每个字段:

GET /identities/?fields!=id
Run Code Online (Sandbox Code Playgroud)

免责声明: 我是作者/维护者.

  • 谢谢,我看了一下这个实现,看起来它是一样的基本想法.但是`dbrgn`实现有一些不同之处:1.不支持用`fields!= key1,key2`排除.2.还修改GET请求上下文之外的序列化程序,这可以并且将破坏一些PUT/POST请求.3.不会累积字段,例如`fields = key1&fields = key2`,这对于ajax应用来说是一个不错的选择.它也没有测试覆盖率,这在OSS中有点不寻常. (4认同)

Aus*_*rba 7

serializers.py

class DynamicFieldsSerializerMixin(object):

    def __init__(self, *args, **kwargs):
        # Don't pass the 'fields' arg up to the superclass
        fields = kwargs.pop('fields', None)

        # Instantiate the superclass normally
        super(DynamicFieldsSerializerMixin, self).__init__(*args, **kwargs)

        if fields is not None:
            # Drop any fields that are not specified in the `fields` argument.
            allowed = set(fields)
            existing = set(self.fields.keys())
            for field_name in existing - allowed:
                self.fields.pop(field_name)


class UserSerializer(DynamicFieldsSerializerMixin, serializers.HyperlinkedModelSerializer):

    password = serializers.CharField(
        style={'input_type': 'password'}, write_only=True
    )

    class Meta:
        model = User
        fields = ('id', 'username', 'password', 'email', 'first_name', 'last_name')


    def create(self, validated_data):
        user = User.objects.create(
            username=validated_data['username'],
            email=validated_data['email'],
            first_name=validated_data['first_name'],
            last_name=validated_data['last_name']
        )

        user.set_password(validated_data['password'])
        user.save()

        return user
Run Code Online (Sandbox Code Playgroud)

views.py

class DynamicFieldsViewMixin(object):

 def get_serializer(self, *args, **kwargs):

    serializer_class = self.get_serializer_class()

    fields = None
    if self.request.method == 'GET':
        query_fields = self.request.QUERY_PARAMS.get("fields", None)

        if query_fields:
            fields = tuple(query_fields.split(','))


    kwargs['context'] = self.get_serializer_context()
    kwargs['fields'] = fields

    return serializer_class(*args, **kwargs)



class UserList(DynamicFieldsViewMixin, ListCreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
Run Code Online (Sandbox Code Playgroud)