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:
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.
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.
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 ).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.
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
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.
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):
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)
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