根据blogpost Best Practices for Designing a Pragmatic RESTful API的建议,我想在基于Django Rest Framework的API中添加fields
查询参数,使用户只能选择每个资源的字段子集。
串行:
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"
},
...
]
包含无效字段的查询应忽略无效字段或抛出客户端错误。
这是否可以开箱即用?如果没有,那么实现这个的最简单方法是什么?是否有第三方包已经这样做了?
答案 0 :(得分:87)
您可以覆盖序列化程序__init__
方法,并根据查询参数动态设置fields
属性。您可以在整个上下文中访问request
对象,并传递给序列化程序。
在这里,我创建了一个可重用的mixin,它可以进行动态fields
修改。
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')
答案 1 :(得分:41)
此功能可从3rd-party package获得。
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
免责声明:我是作者/维护者。
答案 2 :(得分:6)
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
答案 3 :(得分: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):
# [...]
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
以仅显示网址中的指定字段。
/?fields=field1,field2
您可以在此处找到提醒:https://gist.github.com/Kmaschta/e28cf21fb3f0b90c597a
答案 4 :(得分:1)
我们在drf_tweaks / control-over-serialized-fields中提供的此类功能。
如果您使用我们的序列化程序,您只需在查询中传递function checkInputs(){
return !$('.input-required').filter(function() {
return !this.value;
}).length;
}
参数。
答案 5 :(得分:0)
您可以尝试Dynamic REST,它支持动态字段(包含,排除),嵌入式/侧载对象,过滤,排序,分页等等。
答案 6 :(得分:0)
对于嵌套数据,我将Django Rest Framework与docs,drf-flexfields
中推荐的软件包一起使用这允许您限制在父对象和子对象上返回的字段。自述文件中的说明很好,请注意以下几点:
URL似乎需要/这样的'/ person /?expand = country&fields = id,name,country',而不是自述文件'/ person?expand = country&fields = id,name,country'所写>
嵌套对象及其相关名称的命名必须完全一致,否则不需要这样做。
例如,如果您有“很多”一个国家可以有很多州,您需要设置“ many”:如文档中所述,在Serializer中为True。
答案 7 :(得分:0)
如果您想要类似于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"
}
]
},
...
]
答案 8 :(得分:0)
[DRF-Documentation] [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
答案 9 :(得分:0)
另一种选择是使用 GraphWrap:https://github.com/PaulGilmartin/graph_wrap
通过将 /graphql 添加到您的 urlpatterns,您可以使用完全兼容的 GraphQL 可查询 API 添加您的 REST API 层。