Django Rest Framework: Tự động trả về tập hợp con các trường


100

Vấn đề

Theo đề xuất trong bài đăng blog Các phương pháp hay nhất để thiết kế một API RESTful thực dụng , tôi muốn thêm một fieldstham số truy vấn vào một API dựa trên Django Rest Framework cho phép người dùng chỉ chọn một tập hợp con các trường trên mỗi tài nguyên.

Thí dụ

Bộ nối tiếp:

class IdentitySerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = models.Identity
        fields = ('id', 'url', 'type', 'data')

Một truy vấn thông thường sẽ trả về tất cả các trường.

GET /identities/

[
  {
    "id": 1,
    "url": "http://localhost:8000/api/identities/1/",
    "type": 5,
    "data": "John Doe"
  },
  ...
]

Một truy vấn có fieldstham số sẽ chỉ trả về một tập hợp con của các trường:

GET /identities/?fields=id,data

[
  {
    "id": 1,
    "data": "John Doe"
  },
  ...
]

Một truy vấn có các trường không hợp lệ sẽ bỏ qua các trường không hợp lệ hoặc gây ra lỗi máy khách.

Mục tiêu

Điều này có thể ra khỏi hộp bằng cách nào đó? Nếu không, cách đơn giản nhất để thực hiện điều này là gì? Đã có gói bên thứ 3 nào làm được điều này chưa?

Câu trả lời:


121

Bạn có thể ghi đè __init__phương thức tuần tự hóa và đặt fieldsthuộc tính động, dựa trên các tham số truy vấn. Bạn có thể truy cập requestđối tượng trong suốt ngữ cảnh, được chuyển cho bộ tuần tự.

Đây là bản sao và dán từ ví dụ tài liệu của Django Rest Framework về vấn đề này:

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
Cuối cùng tôi đã đến xung quanh để thực hiện điều này, và nó hoạt động hoàn hảo! Cảm ơn. Tôi đã kết thúc viết một mixin cho điều này, thành phần là một chút linh hoạt hơn subclassing :) gist.github.com/dbrgn/4e6fc1fe5922598592d6
Danilo Bargen

8
Bạn sẽ cần phải thay đổi QUERY_PARAMSthành query_paramstrong các phiên bản gần đây của Django, nhưng khác với điều này, điều này hoạt động như một sự quyến rũ.
Myk Willis

3
Bạn có thể nên kiểm tra xem có requeststồn tại như một thành viên của context. Trong khi nó hoạt động trong quá trình sản xuất, nó không chạy khi chạy các bài kiểm tra đơn vị tạo các đối tượng theo cách thủ công.
smitec

21
FYI: Ví dụ này là một bản sao đúng nguyên văn của tài liệu DRF tìm thấy ở đây: django-rest-framework.org/api-guide/serializers/#example Đó là một hình thức xấu để không cung cấp liên kết đến các tác giả gốc
Alex Bausk

3
Các tài liệu DRF , từ đó câu trả lời này đã được sao chép, đã được cải thiện kể từ khi câu trả lời này đã được đăng.
Chris

51

Chức năng này có sẵn từ gói của bên thứ ba .

pip install djangorestframework-queryfields

Khai báo bộ nối tiếp của bạn như thế này:

from rest_framework.serializers import ModelSerializer
from drf_queryfields import QueryFieldsMixin

class MyModelSerializer(QueryFieldsMixin, ModelSerializer):
    ...

Sau đó, các trường bây giờ có thể được chỉ định (phía máy khách) bằng cách sử dụng các đối số truy vấn:

GET /identities/?fields=id,data

Lọc loại trừ cũng có thể, ví dụ: trả về mọi trường ngoại trừ id:

GET /identities/?fields!=id

tuyên bố từ chối trách nhiệm: Tôi là tác giả / người duy trì.


1
Chào. Sự khác biệt giữa điều này và github.com/dbrgn/drf-dynamic-fields (như được liên kết trong các nhận xét của câu trả lời đã chọn)?
Danilo Bargen

5
Cảm ơn, tôi đã xem qua cách triển khai đó và có vẻ như đó là ý tưởng cơ bản giống nhau. Nhưng việc dbrgntriển khai có một số khác biệt: 1. không hỗ trợ loại trừ với fields!=key1,key2. 2. cũng sửa đổi bộ tuần tự bên ngoài ngữ cảnh yêu cầu GET, có thể và sẽ phá vỡ một số yêu cầu PUT / POST. 3. không tích lũy các trường với ví dụ fields=key1&fields=key2, đó là một điều tốt cần có cho các ứng dụng ajax. Nó cũng có phạm vi kiểm tra bằng không, điều này hơi bất thường trong OSS.
wim

1
@wim Thư viện của bạn hỗ trợ phiên bản DRF và Django nào? Tôi không tìm thấy bất cứ thứ gì trong tài liệu.
pawelswiecki

1
Django 1.7-1.11 +, về cơ bản là bất kỳ cấu hình nào mà DRF hỗ trợ. Nhận xét này có thể đã lỗi thời, vì vậy hãy kiểm tra ma trận kiểm tra cho CI, tại đây .
wim

1
Hoạt động tuyệt vời cho tôi: 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

Định cấu hình một lớp tuần tự phân trang mới

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',
}

Tạo bộ nối tiếp động

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

Cuối cùng, sử dụng hỗn hợp homemage cho APIViews của bạn

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

Yêu cầu

Bây giờ, khi bạn yêu cầu một tài nguyên, bạn có thể thêm một tham số fieldsđể chỉ hiển thị các trường được chỉ định trong url. /?fields=field1,field2

Bạn có thể tìm thấy lời nhắc tại đây: https://gist.github.com/Kmaschta/e28cf21fb3f0b90c597a


2

Bạn có thể thử Dynamic REST , có hỗ trợ các trường động (bao gồm, loại trừ), các đối tượng được nhúng / truyền tải, lọc, sắp xếp, phân trang và hơn thế nữa.



1

Đối với dữ liệu lồng nhau, tôi đang sử dụng Django Rest Framework với gói được đề xuất trong tài liệu , drf-flexfields

Điều này cho phép bạn hạn chế các trường được trả về trên cả đối tượng cha và con. Các hướng dẫn trong readme là tốt, chỉ cần lưu ý một số điều sau:

URL dường như cần ký hiệu / like this '/ person /? Expand = country & fields = id, name, country' thay vì được viết trong readme '/ person? Expand = country & fields = id, name, country'

Việc đặt tên của đối tượng lồng nhau và tên liên quan của nó cần phải hoàn toàn nhất quán, không bắt buộc phải có.

Nếu bạn có 'nhiều', ví dụ: một quốc gia có thể có nhiều tiểu bang, bạn sẽ cần đặt 'nhiều': Đúng trong Trình sắp xếp như được mô tả trong tài liệu.


1

Nếu bạn muốn thứ gì đó linh hoạt như GraphQL, bạn có thể sử dụng django-restql . Nó hỗ trợ dữ liệu lồng nhau (cả phẳng và có thể lặp lại).

Thí dụ

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

Một yêu cầu thông thường trả về tất cả các trường.

GET /users

    [
      {
        "id": 1,
        "username": "yezyilomo",
        "email": "yezileliilomo@hotmail.com",
        "groups": [1,2]
      },
      ...
    ]

Mặt khác, một yêu cầu với querytham số chỉ trả về một tập hợp con của các trường:

GET /users/?query={id, username}

    [
      {
        "id": 1,
        "username": "yezyilomo"
      },
      ...
    ]

Với django-restql, bạn có thể truy cập các trường lồng nhau ở bất kỳ cấp độ nào. Ví dụ

GET /users/?query={id, username, date_joined{year}}

    [
      {
        "id": 1,
        "username": "yezyilomo",
        "date_joined": {
            "year": 2018
        }
      },
      ...
    ]

Đối với các trường lồng nhau có thể lặp lại, Ví dụ: nhóm trên người dùng.

GET /users/?query={id, username, groups{id, name}}

    [
      {
        "id": 1,
        "username": "yezyilomo",
        "groups": [
            {
                "id": 2,
                "name": "Auth_User"
            }
        ]
      },
      ...
    ]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.