web-dev-qa-db-de.com

Django Rest Framework: Dynamische Teilmenge von Feldern zurückgeben

Problem

Wie im Blogpost Best Practices zum Entwerfen einer pragmatischen RESTful-API empfohlen, möchte ich einen fields-Abfrageparameter zu einer auf Django Rest Framework basierenden API hinzufügen, mit der der Benutzer nur eine Teilmenge von Feldern pro Ressource auswählen kann.

Beispiel

Serializer:

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

Eine regelmäßige Abfrage würde alle Felder zurückgeben.

GET /identities/

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

Eine Abfrage mit dem Parameter fields sollte nur eine Teilmenge der Felder zurückgeben:

GET /identities/?fields=id,data

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

Eine Abfrage mit ungültigen Feldern sollte entweder die ungültigen Felder ignorieren oder einen Clientfehler auslösen.

Tor

Ist das irgendwie aus dem Kasten heraus möglich? Wenn nicht, wie kann dies am einfachsten umgesetzt werden? Gibt es ein 3rd-Party-Paket, das dies bereits tut?

71
Danilo Bargen

Sie können die __init__-Methode des Serialisierers überschreiben und das fields-Attribut basierend auf den Abfrageparametern dynamisch festlegen. Sie können auf das request-Objekt aus dem Kontext zugreifen, der an den Serializer übergeben wird.

Hier habe ich ein wiederverwendbares Mixin erstellt, das die dynamische fields-Modifikation durchführt.

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

Diese Funktionalität ist in einem 3rd-Party-Paket verfügbar.

pip install djangorestframework-queryfields

Deklarieren Sie Ihren Serializer folgendermaßen:

from rest_framework.serializers import ModelSerializer
from drf_queryfields import QueryFieldsMixin

class MyModelSerializer(QueryFieldsMixin, ModelSerializer):
    ...

Dann können die Felder jetzt (clientseitig) mithilfe von Abfrageargumenten angegeben werden:

GET /identities/?fields=id,data

Ausschlussfilterung ist auch möglich, z. jedes Feld zurückgeben außer id:

GET /identities/?fields!=id

disclaimer: Ich bin der Autor/Betreuer. 

37
wim

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
6
Austin Malerba

Konfigurieren Sie eine neue Paginierungs-Serialisierungsklasse

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

Machen Sie einen dynamischen Serializer

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

Als letztes verwenden Sie ein Homemage-Mixin für Ihre 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):
    # [...]

Anfordern

Wenn Sie jetzt eine Ressource anfordern, können Sie einen Parameter fields hinzufügen, um nur die angegebenen Felder in der URL anzuzeigen ./?fields=field1,field2

Eine Erinnerung finden Sie hier: https://Gist.github.com/Kmaschta/e28cf21fb3f0b90c597a

3
Kmaschta

Diese Funktionalität haben wir in drf_tweaks/control-over-serialized-fields bereitgestellt.

Wenn Sie unsere Serialisierer verwenden, müssen Sie lediglich den Parameter ?fields=x,y,z in der Abfrage übergeben.

1

Für verschachtelte Daten verwende ich Django Rest Framework mit dem in docs , drf-flexfields empfohlenen Paket.

Auf diese Weise können Sie die Felder einschränken, die für das übergeordnete und das untergeordnete Objekt zurückgegeben werden. Die Anweisungen in der Readme-Datei sind gut, nur ein paar Dinge, auf die Sie achten sollten:

Die URL scheint das/wie dieses '/ person /? Expand = country & fields = id, name, country' zu benötigen, anstatt in der Readme-Datei '/ person?

Die Benennung des verschachtelten Objekts und der zugehörige Name müssen vollständig konsistent sein, was sonst nicht erforderlich ist.

Wenn Sie "viele" haben, z. Ein Land kann viele Staaten haben, Sie müssen "viele" einstellen: "True" im Serializer, wie in den Dokumenten beschrieben.

0
Little Brain

Wenn Sie etwas Ähnliches wie GraphQL wollen, versuchen Sie Django-restql , es ist sehr flexibel und unterstützt verschachtelte Daten (sowohl flach als auch iterierbar).

Beispiel

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

Eine reguläre Anforderung gibt alle Felder zurück.

GET /users

    [
      {
        "id": 1,
        "username": "yezyilomo",
        "email": "[email protected]",
        "groups": [1,2]
      },
      ...
    ]

Eine Anfrage mit dem Parameter query gibt dagegen nur eine Teilmenge der Felder zurück:

GET /users/?query=["id", "username"]

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

Mit Django-restql können Sie auf verschachtelte Felder jeder Ebene zugreifen. Z.B

GET /users/?query=["id", "username" {"date_joined": ["year"]}]

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

Bei iterierbaren verschachtelten Feldern gruppieren sich z. B. Benutzer.

GET /users/?query=["id", "username" {"groups": [[ "id", "name" ]]}]

    [
      {
        "id": 1,
        "username": "yezyilomo",
        "groups": [
            {
                "id": 2,
                "name": "Auth_User"
            }
        ]
      },
      ...
    ]
0
Yezy Ilomo

Sie können versuchen, Dynamic REST , das dynamische Felder (Einschluss, Ausschluss), eingebettete/seitlich geladene Objekte, Filter, Reihenfolge, Seitenumbruch usw. unterstützt.

0
dangonfast