Создание нескольких объектов с одним запросом в Django и Django Rest Framework

Я использую Django в качестве backend-сервера и Vue.js для приложения для видеороликов переднего плана.

У меня есть модель Ticket

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')

И связанный с ним сериализатор

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

Чтобы купить новый билет, вид, который сопоставляется с этим URL 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)

Теперь из переднего конца (Vue.js) я могу создать новый билет в кино:

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;

Если форма была действительной, это создаст новый объект MovieTicket или покажет ошибку /s.

Теперь предположим, что если пользователь выбрал несколько мест, я могу пройти через каждый массив selectedSeats и получить идентификаторы места на стороне клиента. И опубликуйте что-то вроде этого:

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

Но то, что я запутался, - это как передать несколько seat.id, если среда Django rest принимает только одно место за запрос и соответственно отображает ошибки? Значение ошибки отображения, если билет доступен или нет, и если он доступен для создания билетов на кино для этого шоу-места.

Ответ 1

Инициализировать сериализатор со многими = True

В вашей реализации это действительно легко выполнить:

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

Данные - это не один объект, а массив объектов.

Ваша информация подсказывает, что вам нужно преобразовать request.data, чтобы сделать эти несколько объектов (все те же данные просто разные номера места). Правильно?

в любом случае:

см.: Как создать несколько экземпляров модели с помощью Django Rest Framework?

EDIT:

здесь информация в документе drf: http://www.django-rest-framework.org/api-guide/serializers/#dealing-with-multiple-objects

(настоятельно рекомендуем читать документы drf сверху вниз и просто играть с ним, прежде чем кодировать вашу первую реальную реализацию. Существует много способов использования drf, и знание всех из них приводит к лучшим решениям)

EDIT 2 (после обновления вопроса):

Вы можете отправить этот JSON с клиента (см. ниже) или создать этот формат из текущего JSON, который клиент отправляет в вашем методе buy_ticket(request), прежде чем вы вызовете 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
    }
]

Ответ 2

Вы можете проверить количество мест в функции просмотра и создать один или несколько билетов:

@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)

Это сработает, но, честно говоря, это такой беспорядок. Вы можете двигать логику для создания мест в разных функциях, она будет выглядеть лучше.

Ответ 3

Если вы хотите, чтобы пользователь мог выбрать несколько мест для одного билета, вероятно, неплохо было бы удалить однократное отображение Seat и MovieTicket и создать много-одно отношение. так:

сериализаторы:

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

И модель должна выглядеть так:

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.

Это позволит вам передать массив "мест" в запросе.

Ответ 4

Если вы не возражаете добавить другое приложение в свой проект django, вы можете попробовать с django-rest-framework-bulk, если не можете проверить код и посмотреть, как он был реализован.

Если вы используете это приложение, вы сможете выполнять операции массового создания, отправив список элементов в свой запрос POST.

например:

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

Ответ 5

Этот ответ был действительно хорошим решением этой проблемы:

Вы можете просто перезаписать метод get_serializer в вашем APIView и передать many=True в get_serializer базового вида следующим образом:

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)