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


100

问题

如博客文章“设计实用的RESTful API的最佳实践”中的建议,我想向fields基于Django Rest Framework的API 添加查询参数,该参数使用户只能为每个资源选择字段的子集。

序列化器:

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

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

GET /identities/

[
  {
    "id": 1,
    "url": "http://localhost:8000/api/identities/1/",
    "type": 5,
    "data": "John Doe"
  },
  ...
]

具有fields参数的查询应仅返回字段的子集:

GET /identities/?fields=id,data

[
  {
    "id": 1,
    "data": "John Doe"
  },
  ...
]

具有无效字段的查询应忽略无效字段或引发客户端错误。

目标

是否有可能以某种方式开箱即用?如果没有,最简单的实现方法是什么?周围是否有第三方包装?

Answers:


121

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

这是此问题的Django Rest Framework文档示例的复制和粘贴:

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')

4
我终于来实现了它,并且效果很好!谢谢。我为此写了一个mixin,组成比子类化稍微灵活一些:) gist.github.com/dbrgn/4e6fc1fe5922598592d6
Danilo Bargen

8
您需要在Django的最新版本中更改QUERY_PARAMSquery_params,但是除此之外,它的工作原理很吸引人。
Myk Willis 2015年

3
您可能应该检查requests作为的成员存在context。尽管它在生产中运行,但在运行手动创建对象的单元测试时却没有。
smitec

21
仅供参考:此示例是DRF文档的完整副本,可在以下位置找到:django-rest-framework.org/api-guide/serializers/#example不提供原始作者链接的格式很糟糕
Alex Bausk,2016年

3
自发布此答案以来,已从中复制了此答案的DRF文档得到了改进。
克里斯,

51

此功能可从第三方包装中获得

pip install djangorestframework-queryfields

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

from rest_framework.serializers import ModelSerializer
from drf_queryfields import QueryFieldsMixin

class MyModelSerializer(QueryFieldsMixin, ModelSerializer):
    ...

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

GET /identities/?fields=id,data

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

GET /identities/?fields!=id

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


1
你好 这和github.com/dbrgn/drf-dynamic-fields(在所选答案的注释中链接)之间有什么区别?
Danilo Bargen

5
谢谢,我看了看那个实现,看起来好像是相同的基本想法。但是dbrgn实现有一些区别:1.不支持with fields!=key1,key2。2.还修改了GET请求上下文之外的序列化程序,这可能并且将破坏某些PUT / POST请求。3.不会使用来累积字段fields=key1&fields=key2,这对于ajax应用程序来说是一个不错的选择。它还具有零测试覆盖率,这在OSS中有点不寻常。
2016年

1
@wim库支持哪些DRF和Django版本?我没有在文档中找到任何东西。
pawelswiecki

1
Django 1.7-1.11 +,基本上是DRF支持的任何配置。该注释可能已过时,因此请在此处检查CI测试矩阵
wim 2015年

1
最适合我的作品:Django == 2.2.7,djangorestframework == 3.10.3,djangorestframework-queryfields == 1.0.0
Neeraj Kashyap

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

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

3

配置新的分页序列化程序类

from rest_framework import pagination, serializers

class DynamicFieldsPaginationSerializer(pagination.BasePaginationSerializer):
    """
    A dynamic fields implementation of a pagination serializer.
    """
    count = serializers.Field(source='paginator.count')
    next = pagination.NextPageField(source='*')
    previous = pagination.PreviousPageField(source='*')

    def __init__(self, *args, **kwargs):
        """
        Override init to add in the object serializer field on-the-fly.
        """
        fields = kwargs.pop('fields', None)
        super(pagination.BasePaginationSerializer, self).__init__(*args, **kwargs)
        results_field = self.results_field
        object_serializer = self.opts.object_serializer_class

        if 'context' in kwargs:
            context_kwarg = {'context': kwargs['context']}
        else:
            context_kwarg = {}

        if fields:
            context_kwarg.update({'fields': fields})

        self.fields[results_field] = object_serializer(source='object_list',
                                                       many=True,
                                                       **context_kwarg)


# Set the pagination serializer setting
REST_FRAMEWORK = {
    # [...]
    'DEFAULT_PAGINATION_SERIALIZER_CLASS': 'DynamicFieldsPaginationSerializer',
}

制作动态序列化器

from rest_framework import serializers

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

    See:
        http://tomchristie.github.io/rest-framework-2-docs/api-guide/serializers
    """

    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(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

        if fields:
            # 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)
# Use it
class MyPonySerializer(DynamicFieldsModelSerializer):
    # [...]

最后,为您的APIViews使用homemage mixin

class DynamicFields(object):
    """A mixins that allows the query builder to display certain fields"""

    def get_fields_to_display(self):
        fields = self.request.GET.get('fields', None)
        return fields.split(',') if fields else None

    def get_serializer(self, instance=None, data=None, files=None, many=False,
                       partial=False, allow_add_remove=False):
        """
        Return the serializer instance that should be used for validating and
        deserializing input, and for serializing output.
        """
        serializer_class = self.get_serializer_class()
        context = self.get_serializer_context()
        fields = self.get_fields_to_display()
        return serializer_class(instance, data=data, files=files,
                                many=many, partial=partial,
                                allow_add_remove=allow_add_remove,
                                context=context, fields=fields)

    def get_pagination_serializer(self, page):
        """
        Return a serializer instance to use with paginated data.
        """
        class SerializerClass(self.pagination_serializer_class):
            class Meta:
                object_serializer_class = self.get_serializer_class()

        pagination_serializer_class = SerializerClass
        context = self.get_serializer_context()
        fields = self.get_fields_to_display()
        return pagination_serializer_class(instance=page, context=context, fields=fields)

class MyPonyList(DynamicFields, generics.ListAPIView):
    # [...]

请求

现在,当您请求资源时,可以添加一个参数fields以仅显示url中的指定字段。 /?fields=field1,field2

您可以在这里找到提醒:https : //gist.github.com/Kmaschta/e28cf21fb3f0b90c597a


2

您可以尝试Dynamic REST,它支持动态字段(包含,排除),嵌入式/侧载对象,过滤,排序,分页等。



1

对于嵌套数据,我将Django Rest Framework与docsdrf-flexfields中推荐的软件包一起使用

这使您可以限制在父对象和子对象上返回的字段。自述文件中的说明很好,请注意以下几点:

URL似乎需要/这样的'/ person /?expand = country&fields = id,name,country'而不是自述文件'/ person?expand = country&fields = id,name,country'所写

嵌套对象及其相关名称的命名必须完全一致,否则不需要这样做。

如果您有“很多”,例如一个国家可以有很多州,则需要按照文档中的说明在“序列化器”中设置“很多”:True。


1

如果您想要诸如GraphQL之类的灵活功能,可以使用django-restql。它支持嵌套数据(平面数据和可迭代数据)。

from rest_framework import serializers
from django.contrib.auth.models import User
from django_restql.mixins import DynamicFieldsMixin

class UserSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'username', 'email', 'groups')

常规请求将返回所有字段。

GET /users

    [
      {
        "id": 1,
        "username": "yezyilomo",
        "email": "yezileliilomo@hotmail.com",
        "groups": [1,2]
      },
      ...
    ]

query另一方面,带有参数的请求仅返回字段的一部分:

GET /users/?query={id, username}

    [
      {
        "id": 1,
        "username": "yezyilomo"
      },
      ...
    ]

使用django-restql,您可以访问任何级别的嵌套字段。例如

GET /users/?query={id, username, date_joined{year}}

    [
      {
        "id": 1,
        "username": "yezyilomo",
        "date_joined": {
            "year": 2018
        }
      },
      ...
    ]

对于可迭代的嵌套字段,例如,对用户进行分组。

GET /users/?query={id, username, groups{id, name}}

    [
      {
        "id": 1,
        "username": "yezyilomo",
        "groups": [
            {
                "id": 2,
                "name": "Auth_User"
            }
        ]
      },
      ...
    ]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.