Django RestFramework: 动态返回字段的子集

问题

正如博客文章 设计实用 RESTful API 的最佳实践中所推荐的,我想在基于 Django RestFramework 的 API 中添加一个 fields查询参数,该 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"
},
...
]

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

进球了

有没有可能从盒子里拿出来?如果没有,最简单的实现方法是什么?有没有第三方包裹可以做到这一点?

74623 次浏览

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

下面是从 Django RestFramework 文档示例上复制粘贴的内容:

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

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

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):
# [...]

最后,使用一个家庭混合为您的 APIView

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

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

视野,视野

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

此功能可从 第三方包裹获得。

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

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

我们在 Drf _ tweaks/control-over-Series-fields中提供了这样的功能。

如果您使用我们的序列化器,您所需要的只是在查询中传递 ?fields=x,y,z参数。

您可以尝试使用 动态休息,它支持动态字段(包含、排除)、嵌入/副加载对象、筛选、排序、分页等。

对于嵌套数据,我使用 Django 的休息框架与包建议在 医生DRF-Flexfields

这允许您限制在父对象和子对象上返回的字段。自述文件中的说明很好,只是需要注意以下几点:

这个 URL 似乎需要/like 这个“/person/? expo = country & fields = id,name,country”,而不是像在自述文件中写的那样“/person? expo = country & fields = id,name,country”

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

如果您有“ many”,例如一个国家可以有多个州,那么您需要设置“ many”: 如文档中所述,在 Serializer 中为 True。

如果你想要像 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"
}
]
},
...
]

[ DRF-Document ][1]中提出的解决方案对我很有用,但是当我从 View 中调用序列化程序时:

class SomeView(ListAPIView):
def get(self, request, *args, **kwargs):
qry=table.objects.filter(column_value=self.kwargs['urlparameter'])
fields=['DBcol1','DBcol2','DBcol3']
serializer=SomeSerializer(qry,many=True,fields=fields)

我不得不添加 many=True,否则它不工作。

  [1]: https://www.django-rest-framework.org/api-guide/serializers/#example

另一种选择是使用 GraphWrap: https://github.com/PaulGilmartin/graph_wrap

通过在 urlpattern 中添加/GraphQL,您可以使用完全兼容的 GraphQL 可查询 API 添加 REST API 层。