Django Rest Framework : 동적으로 필드 하위 집합 반환


100

문제

블로그 게시물 Best Practices for Designing a Pragmatic 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"
  },
  ...
]

유효하지 않은 필드가있는 쿼리는 유효하지 않은 필드를 무시하거나 클라이언트 오류를 ​​발생시켜야합니다.

이것은 어떻게 든 상자에서 가능합니까? 그렇지 않다면 이것을 구현하는 가장 간단한 방법은 무엇입니까? 이미 이것을 수행하는 타사 패키지가 있습니까?

답변:


121

serializer __init__메서드를 재정의 fields하고 쿼리 매개 변수를 기반으로 속성을 동적으로 설정할 수 있습니다 . requestserializer에 전달 된 컨텍스트 전체 에서 개체에 액세스 할 수 있습니다 .

다음은 문제에 대한 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
당신은 변경해야합니다 QUERY_PARAMSquery_params장고의 최신 버전으로,하지만이 마법처럼 작동 이외.
Myk Willis 2015 년

3
requests의 구성원으로 존재 하는지 확인해야 할 것 입니다 context. 프로덕션에서는 수행되지만 개체를 ​​수동으로 만드는 단위 테스트를 실행할 때는 그렇지 않습니다.
smitec 2015 년

21
참고 :이 예제는 DRF 문서의 축 어적 사본입니다. django-rest-framework.org/api-guide/serializers/#example 원본 작성자에 대한 링크를 제공하지 않는 것은 잘못된 형식입니다
Alex Bausk

3
DRF 문서 이 답변이 게시 된 이후이 답변이 복사 된에서이 향상되었습니다.
Chris

51

이 기능은 타사 패키지 에서 사용할 수 있습니다 .

pip install djangorestframework-queryfields

다음과 같이 serializer를 선언하십시오.

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. 제외를 지원하지 않습니다 fields!=key1,key2. 2. 또한 GET 요청 컨텍스트 외부에서 serializer를 수정하여 일부 PUT / POST 요청을 중단 할 수 있습니다. 3. 예를 들어 필드를 축적하지 않습니다 fields=key1&fields=key2. 이는 ajax 앱에 유용합니다. 또한 OSS에서 다소 드문 테스트 커버리지가 없습니다.
WIM

1
@wim 라이브러리에서 지원하는 DRF 및 Django 버전은 무엇입니까? 문서에서 아무것도 찾지 못했습니다.
pawelswiecki

1
Django 1.7-1.11 +, 기본적으로 DRF가 지원하는 모든 구성. 이 주석은 오래되었을 수 있으므로 여기에서 CI에 대한 테스트 매트릭스를 확인 하십시오 .
wim

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

마지막으로 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):
    # [...]

의뢰

이제 리소스를 요청할 때 fieldsurl에 지정된 필드 만 표시 하는 매개 변수 를 추가 할 수 있습니다 . /?fields=field1,field2

여기에서 알림을 찾을 수 있습니다 : https://gist.github.com/Kmaschta/e28cf21fb3f0b90c597a


2

동적 필드 (포함, 제외), 포함 / 사이드로드 된 개체, 필터링, 순서 지정, 페이지 매김 등을 지원하는 Dynamic REST를 사용해 볼 수 있습니다.



1

중첩 데이터의 경우 docs , drf-flexfields 에서 권장하는 패키지와 함께 Django Rest Framework를 사용하고 있습니다.

이를 통해 부모 및 자식 개체 모두에서 반환되는 필드를 제한 할 수 있습니다. Readme의 지침은 훌륭하며주의해야 할 몇 가지 사항입니다.

URL에 readme '/ 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"
            }
        ]
      },
      ...
    ]
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.