Django Rest Framework: динамически возвращаемое подмножество полей
как рекомендовано в блоге лучшие практики для разработки прагматичного RESTful API, Я хотел бы добавить fields
параметр запроса к API на основе Django Rest Framework, который позволяет пользователю выбирать только подмножество полей на ресурс.
пример
сериализатор:
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"
},
...
]
запрос с недопустимыми полями должен либо игнорировать недопустимые поля, либо выдавать ошибку клиента.
цель
это возможно из коробки как-то? Если нет, то каков самый простой способ реализовать это? Есть ли сторонний пакет, который уже это делает?
5 ответов:
вы можете переопределить сериализатор
__init__
методfields
атрибут dynamicaly, основанный на параметрах запроса. Вы можете получить доступ кrequest
объект trouth контекст, переданный сериализатору.здесь я создал многоразовый миксин, который делает динамический
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')
эта функция доступна с 3-й партии пакет.
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
отказ от ответственности: я автора/разработчика.
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
Настройка нового класса сериализатора разбиения на страницы
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): # [...]
наконец, используйте homemage mixin для ваших APIViews
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
такую возможность мы предоставили в drf_tweaks / control-over-serialized-fields.
Если вы используете наш сериализаторы, все, что вам нужно, это пройти
?fields=x,y,z
параметр в запросе.