Создание нескольких объектов с одним запросом в 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)