ブログ投稿で推奨されているように、 実用的な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"
},
...
]
無効なフィールドを持つクエリは、無効なフィールドを無視するか、クライアントエラーをスローする必要があります。
これは何とかしてすぐに可能ですか?そうでない場合、これを実装する最も簡単な方法は何ですか?これを既に行っているサードパーティのパッケージはありますか?
シリアライザの__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')
この機能は、 サードパーティパッケージ から利用できます。
pip install djangorestframework-queryfields
次のようにシリアライザーを宣言します。
from rest_framework.serializers import ModelSerializer
from drf_queryfields import QueryFieldsMixin
class MyModelSerializer(QueryFieldsMixin, ModelSerializer):
...
次に、クエリ引数を使用してフィールドを指定できます(クライアント側)。
GET /identities/?fields=id,data
除外フィルタリングも可能です。すべてのフィールドを返すexceptid:
GET /identities/?fields!=id
免責事項:私は著者/維持者です。
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
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
を追加して、URLで指定されたフィールドのみを表示できます。 /?fields=field1,field2
ここでリマインダーを見つけることができます: https://Gist.github.com/Kmaschta/e28cf21fb3f0b90c597a
動的REST を試すことができます。これは、動的フィールド(包含、除外)、埋め込み/サイドロードオブジェクト、フィルタリング、順序付け、ページネーションなどをサポートします。
drf_tweaks/control-over-serialized-fields で提供したこのような機能。
シリアライザーを使用する場合、必要なのはクエリで?fields=x,y,z
パラメーターを渡すことだけです。
ネストされたデータの場合、Django Rest Frameworkと docs 、 drf-flexfields で推奨されているパッケージを使用しています
これにより、親オブジェクトと子オブジェクトの両方で返されるフィールドを制限できます。 readmeの指示は適切であり、注意すべき点がいくつかあります。
URLには/のような「/ person /?expand = country&fields = id、name、country」が必要なようです。readmeの「/ person?expand = country&fields = id、name、country」に記載されている
ネストされたオブジェクトの名前と関連する名前は完全に一貫している必要がありますが、それ以外の場合は必要ありません。
「たくさん」ある場合国には多くの州がある場合、ドキュメントで説明されているように、シリアライザで「多く」: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": "[email protected]",
"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"
}
]
},
...
]