Django Rest Framework: Alanların alt kümesini dinamik olarak döndür


104

Sorun

Blog yayını En İyi Pragmatik RESTful API Tasarlama Yöntemlerinde önerildiği gibi, fieldsDjango Rest Framework tabanlı API'ye, kullanıcının kaynak başına yalnızca bir alan alt kümesi seçmesine olanak tanıyan bir sorgu parametresi eklemek istiyorum .

Misal

Serileştirici:

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

Normal bir sorgu tüm alanları döndürür.

GET /identities/

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

fieldsParametreye sahip bir sorgu , alanların yalnızca bir alt kümesini döndürmelidir:

GET /identities/?fields=id,data

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

Geçersiz alanlara sahip bir sorgu, geçersiz alanları yok saymalı veya bir istemci hatası atmalıdır.

Hedef

Bu bir şekilde kutunun dışında mümkün mü? Değilse, bunu uygulamanın en basit yolu nedir? Etrafta bunu zaten yapan bir 3. parti paket var mı?

Yanıtlar:


126

Sorgu parametrelerine göre serileştirici __init__yöntemini geçersiz kılabilir ve fieldsniteliği dinamik olarak ayarlayabilirsiniz . requestNesneye serileştiriciye iletilen bağlam boyunca erişebilirsiniz .

İşte konuyla ilgili Django Rest Framework dokümantasyon örneğinden bir kopyala ve yapıştır :

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
Sonunda bunu uygulamaya geldim ve mükemmel çalışıyor! Teşekkürler. Bunun için bir mixin yazmayı bıraktım, kompozisyon alt sınıflamadan biraz daha esnek :) gist.github.com/dbrgn/4e6fc1fe5922598592d6
Danilo Bargen

8
Sen değiştirmeniz gerekir QUERY_PARAMSiçin query_paramsDjango son sürümlerinde, ama bu bir cazibe gibi çalışır bunun dışında.
Myk Willis

3
requestsBir üye olarak var olup olmadığını kontrol etmelisiniz context. Üretimde olsa da, nesneleri manuel olarak oluşturan birim testlerini çalıştırırken bunu yapmaz.
smitec

21
Bilginize: Bu örnek, şurada bulunan DRF belgelerinin birebir kopyasıdır: django-rest-framework.org/api-guide/serializers/#example Orijinal yazarlara bağlantı sağlamamak kötü bir biçim
Alex Bausk

4
DRF dokümantasyon Bu cevap yayınlanmıştır beri bu cevap kopyalandığı, iyileştirilmiştir.
Chris

51

Bu işlevsellik, bir 3. taraf paketinde mevcuttur .

pip install djangorestframework-queryfields

Serileştiricinizi şu şekilde bildirin:

from rest_framework.serializers import ModelSerializer
from drf_queryfields import QueryFieldsMixin

class MyModelSerializer(QueryFieldsMixin, ModelSerializer):
    ...

Daha sonra alanlar artık sorgu argümanları kullanılarak belirtilebilir (istemci tarafı):

GET /identities/?fields=id,data

Dışlama filtrelemesi de mümkündür, örneğin id dışındaki her alanı döndürmek için :

GET /identities/?fields!=id

feragatname: Ben yazar / geliştiriciyim.


1
Selam. Bu ve github.com/dbrgn/drf-dynamic-fields arasındaki fark nedir (seçilen cevabın yorumlarında bağlantılı olduğu gibi)?
Danilo Bargen

5
Teşekkürler, bu uygulamaya bir göz attım ve görünüşe göre aynı temel fikir. Ancak dbrgnuygulamanın bazı farklılıkları vardır: 1. ile dışlamayı desteklemiyor fields!=key1,key2. 2. ayrıca bazı PUT / POST isteklerini bozabilecek ve bozacak GET istek bağlamı dışındaki serileştiricileri de değiştirir. 3. Örneğin fields=key1&fields=key2, ajax uygulamaları için sahip olması güzel bir alan olan alanları biriktirmez . Ayrıca, OSS'de biraz sıra dışı olan sıfır test kapsamına sahiptir.
wim

1
@wim Kitaplığınız DRF ve Django'nun hangi sürümlerini destekliyor? Dokümanlarda hiçbir şey bulamadım.
pawelswiecki

1
Django 1.7-1.11 +, temelde DRF'nin desteklediği herhangi bir yapılandırma. Bu yorum güncel olmayabilir, bu nedenle CI için test matrisini buradan kontrol edin .
wim

1
Benim için harika çalışıyor: 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

Yeni bir sayfalandırma serileştirici sınıfı yapılandırma

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

Dinamik serileştirici yapın

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

Son olarak, APIV Görünümleriniz için bir homemage karışımı kullanı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):
    # [...]

İstek

Şimdi, bir kaynak talep ettiğinizde, fieldsurl'de yalnızca belirtilen alanları göstermek için bir parametre ekleyebilirsiniz . /?fields=field1,field2

Burada bir hatırlatıcı bulabilirsiniz: https://gist.github.com/Kmaschta/e28cf21fb3f0b90c597a



2

Dinamik alanlar (dahil etme, hariç tutma), katıştırılmış / dışarıdan yüklenmiş nesneler, filtreleme, sıralama, sayfalama ve daha fazlasını destekleyen Dinamik REST'i deneyebilirsiniz .


2

İç içe geçmiş veriler için, Django Rest Framework'ü docs , drf-flexfields'de önerilen paketle kullanıyorum

Bu, hem üst hem de alt nesnelerde döndürülen alanları kısıtlamanıza olanak tanır. Benioku dosyasındaki talimatlar iyidir, dikkat etmeniz gereken birkaç nokta:

URL'nin benioku dosyasında yazılanlar yerine / kişi /?

İç içe yerleştirilmiş nesnenin adı ve ilgili adının tamamen tutarlı olması gerekir, aksi takdirde bu gerekli değildir.

Eğer 'çok' varsa, örneğin bir ülke birçok eyalete sahipse, 'çok' ayarlamanız gerekir: Dokümanlarda açıklandığı gibi Serializer'da True.


1

GraphQL gibi esnek bir şey istiyorsanız, django-restql'i kullanabilirsiniz . Yuvalanmış verileri (hem düz hem de yinelenebilir) destekler.

Misal

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

Normal bir istek tüm alanları döndürür.

GET /users

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

queryÖte yandan parametreye sahip bir istek , alanların yalnızca bir alt kümesini döndürür:

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

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

İle django-restql herhangi seviyesinin iç içe alanlarını erişebilirsiniz. Örneğin

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

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

Yinelenebilir iç içe geçmiş alanlar için, örneğin kullanıcılar üzerinde gruplar.

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

    [
      {
        "id": 1,
        "username": "yezyilomo",
        "groups": [
            {
                "id": 2,
                "name": "Auth_User"
            }
        ]
      },
      ...
    ]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.