Je rencontre actuellement un petit problème avec Django Rest Framework. J'essaie de poster un objet contenant des objets imbriqués.
Voici mon 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
Et create()
à partir de 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)
Et voici la réponse de Postman:
J'ai lu quelques articles ici sur ce problème, mais je suis toujours coincé avec. J'ai essayé de le réparer de plusieurs manières mais il renvoie toujours "This field is required."
.
Vous traitez le problème de la sérialisation imbriquée. Veuillez lire la documentation liée avant de continuer.
Votre question concerne un domaine complexe de problèmes liés à la DRF et nécessite par conséquent des explications et une discussion pour comprendre le fonctionnement des sérialiseurs et des ensembles de vues.
Je vais aborder le problème de la représentation de vos données Subject
et Class
via le même point de terminaison en utilisant une représentation différente des données pour différentes méthodes HTTP, car il s'agit généralement du problème lorsque les utilisateurs souhaitent représenter leurs données dans des formats imbriqués. ils souhaitent fournir à leurs interfaces utilisateur suffisamment d'informations pour une utilisation propre, par exemple. à travers les sélecteurs déroulants.
Par défaut, Django et Django REST Framework (DRF) font référence aux objets associés (vos Subject
et Class
) par leurs clés primaires. Ce sont, par défaut, des clés entières auto-incrémentées avec Django. Si vous voulez vous y référer par d'autres moyens, vous devez écrire des remplacements pour cela. Il y a quelques options différentes.
Class
avec une recherche composite composée d’un terme de recherche composite (chiffre, lettre). Vous pouvez remplacer les recherches d'objets associés dans votre méthode de vue create
(pour POST), par exemple, mais vous devrez ensuite gérer des recherches similaires dans votre méthode de vue update
(pour PUT et PATCH). Option 1: Regardez la classe et le sujet avec un attribut arbitraire dans create et update:
Définissez vos sérialiseurs de classe imbriqués en lecture seule:
class ExamSerializer(serializers.ModelSerializer):
subject = SubjectSerializer(read_only=True)
clazz = ClassSerializer(read_only=True)
Ignorez la création de votre vue pour rechercher les classes associées sur des attributs de forme libre. Consultez également comment DRF le met en œuvre avec mixins. Vous devrez également redéfinir votre méthode update
pour les gérer correctement et prendre en compte le support PATCH
(mise à jour partielle) en plus de PUT
(mise à jour) si vous prenez cette 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: Spécialisez vos sérialiseurs pour la lecture et l'écriture et utilisez des clés primaires; C'est l'approche idiomatique:
Commencez par définir un ModelSerializer par défaut que vous souhaitez utiliser pour des opérations normales (POST, PUT, PATCH):
class ExamSerializer(serializers.ModelSerializer)
class Meta:
model = Exam
fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')
Puis remplacez les champs nécessaires par le type de représentation que vous souhaitez leur donner pour la lecture des données (GET):
class ExamReadSerializer(ExamSerializer):
subject = SubjectSerializer(read_only=True)
clazz = ClassSerializer(read_only=True)
Puis spécifiez le sérialiseur que vous souhaitez utiliser pour différentes opérations pour votre ViewSet. Ici, nous renvoyons les données d'objet et de classe imbriquées pour les opérations de lecture, mais nous utilisons uniquement leurs clés primaires pour les opérations de mise à jour (beaucoup plus simple):
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
Comme vous pouvez le constater, l'option 2 semble relativement moins complexe et sujette aux erreurs, car elle ne contient que 3 lignes de code manuscrit au-dessus de DRF (l'implémentation get_serializer_class). Laissez simplement la logique du framework déterminer les représentations, la création et les mises à jour des objets pour vous.
J'ai vu beaucoup d'autres approches, mais jusqu'à présent, ce sont celles qui ont produit le moins de code à maintenir pour moi et qui exploitent la conception de DRF de manière propre.
Une approche plus facile sans faire de classes supplémentaires consiste à prendre la sérialisation sur vous-même
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
J'ai eu le même problème en essayant de poster un objet JSON imbriqué sur DRF (Django Rest Framework).
Une fois que vous avez correctement configuré l'écriture de sérialiseurs imbriqués (voir la documentation sur sérialiseurs imbriqués en écriture ), vous pouvez tester le fonctionnement de ce dernier en utilisant API parcourable et en y inscrivant/mettant les données. Si cela fonctionne et que vous obtenez toujours des erreurs "Ce champ est obligatoire" sur vos modèles imbriqués lors de la publication/insertion d'objets JSON, vous devrez peut-être définir le type de contenu de votre demande.
Cette réponse a fourni la solution dont j'avais besoin et qui est résumée ci-dessous.
$.ajax ({
// Other parameters e.g. url, type
data: JSON.stringify(data),
dataType: "json",
contentType: "application/json; charset=utf-8",
});
Je devais définir le "contentType" ainsi que "stringify" mon objet js.