web-dev-qa-db-fra.com

Cadre de repos Django POST objets imbriqués

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:  Postman response

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.".

13
wencakisa

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.

  1. La première option consiste à spécialiser votre logique de création et de mise à jour: Référez-vous à votre classe via un autre attribut et écrivez manuellement les recherches pour la création, ou définissez la clé à laquelle vous vous référez comme primaire clé de votre classe. Vous pouvez définir le nom, l'UUID ou tout autre attribut de votre classe comme clé de base de données primaire, tant qu'il s'agit d'un unique, champ unique (la raison pour laquelle je le mentionne est parce que vous êtes à, Pour le moment, examinez vos modèles 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). 
  2. Deuxièmement, à mon avis, l'option préférable consiste à spécialiser vos représentations d'objet: Reportez-vous à vos classes normalement via la clé primaire et créez un sérialiseur pour lire l'objet et un pour créer et mettre à jour il. Cela peut être facilement réalisé en héritant une classe de sérialiseur et en remplaçant vos représentations. Utilisez la clé primaire dans vos demandes POST, PUT, PATCH, etc. pour mettre à jour vos références de classe et vos clés étrangères.

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.

33
Aleksi Häkli

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
2
validname

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.

0
Keoni Mahelona