wake-up-neo.net

Django Rest Framework POST verschachtelte Objekte

Ich habe gerade ein kleines Problem mit Django Rest Framework. Ich versuche, ein Objekt mit darin verschachtelten Objekten zu posten.

Hier sind mein serializers.py:

class ClassSerializer(serializers.ModelSerializer):
    class Meta:
        model = Class
        fields = ('number', 'letter')


class SubjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = Subject
        fields = ('title',)


class ExamSerializer(serializers.ModelSerializer):
    subject = SubjectSerializer()
    clazz = ClassSerializer()

    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')
        depth = 1

    def create(self, validated_data):
        return Exam.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.__dict__.update(**validated_data)
        instance.save()

        return instance

Und create() von views.py:

def create(self, request):
    serializer = self.serializer_class(data=request.data)
    serializer.is_valid(raise_exception=True)
    self.perform_create(serializer)

    return Response(serializer.validated_data, status=status.HTTP_201_CREATED)

Und hier ist die Antwort von Postman:  Postman response

Ich habe hier einige Beiträge zu diesem Problem gelesen, aber ich bleibe dabei. Ich habe versucht, das Problem auf verschiedene Weise zu beheben, aber es wird immer noch "This field is required." zurückgegeben.

13
wencakisa

Sie haben es mit dem Problem von verschachtelte Serialisierung zu tun. Bitte lesen Sie die verknüpfte Dokumentation, bevor Sie fortfahren.

Ihre Frage bezieht sich auf einen komplexen Bereich von Problemen in DRF und erfordert daher einige Erklärungen und Diskussionen, um zu verstehen, wie Serializer und Viewsets funktionieren.

Ich werde das Problem der Darstellung Ihrer Subject- und Class -Daten über denselben Endpunkt diskutieren, indem eine unterschiedliche Darstellung von Daten für verschiedene HTTP-Methoden verwendet wird, da dies häufig das Problem ist, wenn Benutzer ihre Daten darstellen möchten Daten in verschachtelten Formaten; Sie möchten ihren Benutzerschnittstellen genügend Informationen für eine saubere Verwendung bereitstellen, z. durch die Dropdown-Selektoren.

Standardmäßig beziehen sich Django und Django REST Framework (DRF)) auf verwandte Objekte (Ihre Subject und Class) durch ihre Primärschlüssel Dies sind standardmäßig automatisch inkrementierende Ganzzahlschlüssel mit Django.Wenn Sie auf andere Weise auf sie verweisen möchten, müssen Sie Überschreibungen schreiben Hierfür gibt es verschiedene Möglichkeiten.

  1. Die erste Möglichkeit besteht darin, Ihre Erstellungs- und Aktualisierungslogik zu spezialisieren: Wenden Sie sich über andere Attribute an Ihre Klasse, und schreiben Sie die Lookups für die Erstellung manuell, oder legen Sie den Schlüssel fest, auf den Sie sich beziehen durch als Primärschlüssel Ihrer Klasse. Sie können den Namen Ihrer Klasse, die UUID oder ein anderes Attribut als Primärdatenbankschlüssel festlegen, sofern es sich um ein eindeutiges einzelnes Feld (das Ich erwähne dies, weil Sie im Moment Ihre Class Modelle mit einer zusammengesetzten Suche durchsuchen, die aus einem zusammengesetzten Suchbegriff (Zahl, Buchstabe) besteht. Sie können verwandte Objekt-Lookups beispielsweise in Ihrer View-Methode create (für POST) überschreiben, müssen dann aber ähnliche Lookups auch in Ihrer View-Methode update (für PUT und PATCH) behandeln ).
  2. Zweitens ist meiner Meinung nach die bevorzugte Option, Ihre Objektdarstellungen zu spezialisieren: Beziehen Sie sich auf Ihre Klassen normalerweise über den Primärschlüssel und erstellen Sie einen Serialisierer zum Lesen = das Objekt und eines zum Erstellen und Aktualisieren es. Dies kann auf einfache Weise durch Vererbung von Serializer-Klassen und Überschreiben Ihrer Darstellungen erreicht werden. Verwenden Sie den Primärschlüssel in Ihren POST-, PUT-, PATCH- usw. Anforderungen, um Ihre Klassenreferenzen und Fremdschlüssel zu aktualisieren.

Option 1: Klasse und Betreff mit einem beliebigen Attribut in create and update nachschlagen:

Legen Sie die verschachtelten Klassenserialisierer als schreibgeschützt fest:

class ExamSerializer(serializers.ModelSerializer):
    subject = SubjectSerializer(read_only=True)
    clazz = ClassSerializer(read_only=True)

Überschreiben Sie die Erstellung Ihrer Ansicht, um die zugehörigen Klassen in Freiformattributen nachzuschlagen. Lesen Sie auch wie DRF dies mit Mixins implementiert. Sie müssen auch Ihre update -Methode überschreiben, um diese korrekt zu behandeln, und die PATCH -Unterstützung (Teilaktualisierung) zusätzlich zu PUT (Aktualisierung) berücksichtigen, wenn Sie dies annehmen Route:

def create(self, request):
    # Look up objects by arbitrary attributes.
    # You can check here if your students are participating
    # the classes and have taken the subjects they sign up for.
    subject = get_object_or_404(Subject, title=request.data.get('subject'))
    clazz = get_object_or_404(
        Class, 
        number=request.data.get('clazz_number')
        letter=request.data.get('clazz_letter')
    )

    serializer = self.get_serializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    serializer.save(clazz=clazz, subject=subject)
    headers = self.get_success_headers(serializer.data)

    return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

Option 2: Spezialisieren Sie Ihre Serializer auf Lesen und Schreiben und verwenden Sie Primärschlüssel. Dies ist der idiomatische Ansatz:

Definieren Sie zunächst einen Standard-ModelSerializer, den Sie für den normalen Betrieb verwenden möchten (POST, PUT, PATCH):

class ExamSerializer(serializers.ModelSerializer)
    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')

Überschreiben Sie dann die erforderlichen Felder mit der Art der Darstellung, die Sie ihnen zum Lesen der Daten (GET) geben möchten:

class ExamReadSerializer(ExamSerializer):
     subject = SubjectSerializer(read_only=True)
     clazz = ClassSerializer(read_only=True)

Dann geben Sie den Serializer an, den Sie für verschiedene Operationen verwenden möchten für Ihr ViewSet. Hier geben wir die verschachtelten Betreff- und Klassendaten für Leseoperationen zurück, verwenden jedoch nur deren Primärschlüssel für Aktualisierungsoperationen (viel einfacher):

class ExamViewSet(viewsets.ModelViewSet):
     queryset = Exam.objects.all()

     def get_serializer_class(self):
         # Define your HTTP method-to-serializer mapping freely.
         # This also works with CoreAPI and Swagger documentation,
         # which produces clean and readable API documentation,
         # so I have chosen to believe this is the way the
         # Django REST Framework author intended things to work:
         if self.request.method in ['GET']:
             # Since the ReadSerializer does nested lookups
             # in multiple tables, only use it when necessary
             return ExamReadSerializer
         return ExamSerializer

Wie Sie sehen, scheint Option 2 weniger komplex und fehleranfällig zu sein und enthält nur 3 Zeilen handgeschriebenen Code über DRF (die Implementierung von get_serializer_class). Lassen Sie sich einfach von der Logik des Frameworks die Darstellungen sowie die Erstellung und Aktualisierung von Objekten erklären.

Ich habe viele andere Ansätze gesehen, aber bisher waren dies diejenigen, die den für mich am wenigsten zu wartenden Code hervorgebracht haben und die Vorteile des DRF-Designs auf saubere Weise nutzen.

42
Aleksi Häkli

Ein einfacher Ansatz ohne zusätzliche Klassen ist die Serialisierung:

class ExamSerializer(serializers.ModelSerializer):
    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')

    def to_representation(self, instance):
        data = super().to_representation(instance)
        data['subject'] = SubjectSerializer(
            Subject.objects.get(pk=data['subject'])).data
        data['clazz'] = ClassSerializer(
            Class.objects.get(pk=data['clazz'])).data
        return data
2
validname

Ich hatte das gleiche Problem, als ich versuchte, ein verschachteltes JSON-Objekt in DRF (Django Rest Framework) zu veröffentlichen.

Wenn Sie das Schreiben von geschachtelten Serialisierern (siehe Dokumente unter schreibbare geschachtelte Serialisierer ) ordnungsgemäß eingerichtet haben, können Sie mithilfe der browsable API testen, ob sie funktionieren und Daten dort posten. Wenn dies funktioniert und Sie immer noch "Dieses Feld ist erforderlich" -Fehlern in Ihren verschachtelten Modellen beim Posten von JSON-Objekten erhalten, müssen Sie möglicherweise den Inhaltstyp Ihrer Anforderung festlegen.

Diese Antwort lieferte die Lösung, die ich brauchte, und sie ist unten zusammengefasst.

$.ajax ({
  // Other parameters e.g. url, type
  data: JSON.stringify(data),
  dataType: "json",
  contentType: "application/json; charset=utf-8",
});

Ich musste den "contentType" sowie mein js-Objekt "stringify" setzen.

0
Keoni Mahelona

Um Ihr Problem zu lösen, können Sie dieses Paket verwenden drf-rw-serializers

Sie müssen lediglich zwei Serializer verwenden (einen zum Lesen und einen zum Schreiben):

serializers.py

class ClassSerializer(serializers.ModelSerializer):
    class Meta:
        model = Class
        fields = ('number', 'letter')


class SubjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = Subject
        fields = ('title',)


class ExamSerializer(serializers.ModelSerializer):
    subject = SubjectSerializer()
    clazz = ClassSerializer()

    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')

class WriteExamSerializer(serializers.ModelSerializer):
    subject = SubjectSerializer()
    clazz = ClassSerializer()

    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')

    def create(self, validated_data):
        subject = validated_data.pop('subject', None)
        # logic to update subject
        clazz = validated_data.pop('clazz', None)
        # logic to update clazz
        return super().create(validated_data)

    def update(self, validated_data):
        subject = validated_data.pop('subject', None)
        # logic to update subject
        clazz = validated_data.pop('clazz', None)
        # logic to update clazz
        return super().update(validated_data)

api_views.py

from drf_rw_serializers import generics

from .models import Exam
from .serializers import WriteExamSerializer, ExamSerializer


class ExamListCreateView(generics.ListCreateAPIView):
    queryset = Exam.objects.all()
    write_serializer_class = WriteExamSerializer
    read_serializer_class = ReadExamSerializer
0
Greg Eremeev