web-dev-qa-db-fra.com

Création de plusieurs objets avec une seule demande dans Django et Django Rest Framework

J'utilise Django comme serveur principal et Vue.js pour l'application frontale Movie.

J'ai un modèle de billet

class MovieTicket(models.Model):
    show = models.ForeignKey(Show)
    seat = models.ForeignKey(Seat)
    user = models.ForeignKey(User)
    purchased_at = models.DateTimeField(default=timezone.now)
    qrcode = models.ImageField(upload_to='qrcode', blank=True, null=True)
    qrcode_data = models.CharField(max_length=255, unique=True, blank=True)

    class Meta:
        unique_together = ('show', 'seat')

Et son sérialiseur associé

class MovieTicketSerializer(serializers.ModelSerializer):
    class Meta:
        model = MovieTicket
        fields = '__all__'

Pour acheter un nouveau billet, une vue est mappée sur cette adresse http://dev.site.com/api/movies/buy-ticket/ :

@api_view(['POST'])
@permission_classes([IsAuthenticated])
def buy_ticket(request):
    serialized = MovieTicketSerializer(data=request.data)
    if serialized.is_valid():
        serialized.save()
        return Response(serialized.data, status=status.HTTP_201_CREATED)
    return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)

Maintenant, depuis le début (Vue.js), je peux créer un nouveau ticket de film:

const formBody = {
    show: this.$store.state.showSelected.showTime.id,
    user: this.$store.state.user.id,

    // selectedSeats is an array of seats that have been selected by the user. Here I am passing the first seat object.
    seat: this.$store.state.selectedSeats[0].seat.id
};

this.$http.post("http://dev.site.com/api/movies/buy-ticket/", formBody)
    .then(function (response) {
        console.log(response.data);
    })
    .catch(function (response) {
        console.log(response);
    });
return;

Si le formulaire était valide, cela créera un nouvel objet MovieTicket ou affichera la/les erreur (s).

Supposons maintenant que si l'utilisateur sélectionne plusieurs sièges, je peux parcourir chaque tableau selectedSeats et obtenir les identifiants de siège côté client. Et postez quelque chose comme ceci:

{
    "purchased_at": null,
    "qrcode": null,
    "qrcode_data": "",
    "show": 11,
    "seat": [
        106,
        219
    ],
    "user": 34
}

Mais ce que je ne comprends pas, c'est comment puis-je passer plusieurs seat.id si la structure de repos Django n'accepte qu'un siège par demande et affiche les erreurs en conséquence? Signification des erreurs d’affichage si un ticket est disponible ou non, et s’il est disponible, créer des tickets de film pour ce siège.

15
Aamu

Init le sérialiseur avec plusieurs = True

Dans votre implémentation, cela est vraiment facile à accomplir:

serialized = MovieTicketSerializer(data=request.data, many=True)

Les données ne sont pas un objet unique mais un tableau d'objets.

Vos informations suggèrent que vous devez transformer request.data pour que ces objets multiples (toutes les mêmes données soient simplement des numéros de siège différents). Droite?

de toute façon:

voir: Comment créer plusieurs instances de modèle avec Django Rest Framework?

MODIFIER:

voici les infos dans le docu drf: http://www.Django-rest-framework.org/api-guide/serializers/#dealing-with-multiple-objects

(Nous vous suggérons fortement de lire la documentation drf de haut en bas et de jouer avec elle avant de coder votre première implémentation réelle. Il existe de nombreuses façons d'utiliser drf, sachant que toutes permettent d'obtenir de meilleures décisions)

EDIT 2 (après la mise à jour de la question):

Vous pouvez envoyer ce JSON à partir du client (voir ci-dessous) ou créer ce format à partir du JSON actuel que le client envoie dans votre méthode buy_ticket(request) avant d'appeler MovieTicketSerializer(...,many=True):

[
    {
        "purchased_at": null,
        "qrcode": null,
        "qrcode_data": "",
        "show": 11,
        "seat": 106,
        "user": 34
    },
    {
        "purchased_at": null,
        "qrcode": null,
        "qrcode_data": "",
        "show": 11,
        "seat": 219,
        "user": 34
    }
]
11
klemens

Vous pouvez vérifier le nombre de sièges dans la fonction d'affichage et créer un ou plusieurs tickets: 

@api_view(['POST'])
@permission_classes([IsAuthenticated])
def buy_ticket(request):
    # Check if seats is a list
    if isinstance(request.data['seat'], list):
        seats = request.data.pop('seat')
        models = []
        for seat in seats:
            # validate each model with one seat at a time
            request.data['seat'] = seat
            serializer = MovieTicketSerializer(data=request.data)
            serializer.is_valid(raise_exception=True)
            models.append(serializer)
        # Save it only after all seats are valid. 
        # To avoid situations when one seat has wrong id 
        # And you already save previous
        saved_models = [model.save() for model in models]
        result_serializer = MovieTicketSerializer(saved_models, many=True)
        # Return list of tickets
        return Response(result_serializer.data)
    # Save ticket as usual
    serializer = MovieTicketSerializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    return Response(serializer.data)

Cela fonctionnera, mais honnêtement, c'est un tel désordre. Vous pouvez déplacer la logique pour la création de sièges dans différentes fonctions, cela aura l'air mieux.

2
Ivan Semochkin

Si vous souhaitez que l'utilisateur puisse sélectionner plusieurs sièges pour un ticket, il est probablement préférable de supprimer le mappage un-un de Seat et MovieTicket et de créer une relation plusieurs-un. ainsi:

Sérialiseurs:

class SeatSerializer(serializers.ModelSerializer):
    class Meta:
        model = Seat

class MovieTicketSerializer(serializers.ModelSerializer):
    seats = SeatSerializer(many=True)
    class Meta:
        model = MovieTicket
        fields = '__all__'

    def create(self, vlaidated_data):
        seats = validated_data.pop('seats')
        instance = MovieTicket.objects.create(
            **validated_data)

        for seat in seats:
            Seat.objects.create(
                movieticket=instance, **seats)

        return instance

Et le modèle devrait ressembler à:

class MovieTicket(models.Model):
    show = models.ForeignKey(Show)
    user = models.ForeignKey(User)
    purchased_at = models.DateTimeField(default=timezone.now)
    qrcode = models.ImageField(upload_to='qrcode', blank=True, null=True)
    qrcode_data = models.CharField(max_length=255, unique=True, blank=True)

class Seat(models.Model):
    movieticket = ForeignKey(
        MovieTicket, related_name="movieticket")

    # ... other fields.

Cela vous permettrait alors de transmettre un tableau de «sièges» dans la demande.

0
smurfMT

Si cela ne vous dérange pas d'ajouter une autre application à votre projet Django, vous pouvez essayer avec Django-rest-framework-bulk , sinon, vous pouvez vérifier le code et voir comment il a été mis en œuvre.

Si vous utilisez cette application, vous pourrez effectuer des opérations de création en bloc en envoyant une liste d'éléments sur votre demande POST.

par exemple:

[{'name': 'Jane'}, {'name': 'John'}, {'name': 'Johny'}]
0
menecio

Cette réponse était une très bonne solution à ce problème:

Vous pouvez simplement écraser la méthode get_serializer dans votre APIView et passer many=True dans get_serializer de la vue de base comme suit:

class SomeAPIView(CreateAPIView):
    queryset = SomeModel.objects.all()
    serializer_class = SomeSerializer

    def get_serializer(self, instance=None, data=None, many=False, partial=False):
        return super(SomeAPIView, self).get_serializer(instance=instance, data=data, many=True, partial=partial)
0
Blairg23