【问题标题】:Creating multiple objects with one request in Django and Django Rest Framework在 Django 和 Django Rest Framework 中使用一个请求创建多个对象
【发布时间】:2017-09-12 02:42:48
【问题描述】:

我使用 Django 作为后端服务器,使用 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 对象,否则会显示错误。

现在,假设如果用户选择了多个座位,我可以遍历每个 selectedSeats 数组并在客户端获取座位 ID。并发布这样的内容:

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

但我感到困惑的是,如果 Django 休息框架每个请求只接受一个席位并相应地显示错误,我怎么能传递多个 seat.id?意思是如果票可用或不可用,则显示错误,如果可用,则为该放映席创建电影票。

【问题讨论】:

  • 我不是 django 专家,但你不能创建一种方法来处理 id 数组并循环遍历它们,而不是只处理一个 id - 抱歉,如果问题看起来很奇怪
  • 如果您可以更改 REST API,您可以添加一个新端点,例如“buy_tickets”。并将多个 seat.id 传递给它。
  • @AmrAly 是的,我也希望能找到答案。
  • 这里有一些很好的答案:stackoverflow.com/q/14666199/1224827

标签: django django-rest-framework


【解决方案1】:

使用 many=True 初始化序列化程序

在您的实现中,这很容易实现:

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

数据不是单个对象,而是对象数组。

您的信息表明您需要转换 request.data 以制作这些多个对象(所有相同的数据只是不同的座位号)。对吧?

无论如何:

见:How do I create multiple model instances with Django Rest Framework?

编辑:

这里是 drf 文档中的信息:http://www.django-rest-framework.org/api-guide/serializers/#dealing-with-multiple-objects

(强烈建议在编写您的第一个真正实现之前,从头到尾阅读 drf 文档并随意使用它。有很多方法可以使用 drf,并且了解所有这些方法会导致更好的决策)

编辑 2(问题更新后):

您可以从客户端发送此 JSON(见下文),或在调用 MovieTicketSerializer(...,many=True) 之前从客户端在您的 buy_ticket(request) 方法中发送的当前 JSON 创建此格式:

[
    {
        "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】:

This answer 是一个非常好的解决这个问题的方法:

您可以简单地覆盖 APIView 中的 get_serializer 方法,并将 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):
            if data is not None:
                data.is_valid(raise_exception=True)
                return super(SomeAPIView, self).get_serializer(instance=instance, data=data, many=True, partial=partial)
            else:
                return super(SomeAPIView, self).get_serializer(instance=instance, many=True, partial=partial)

如原帖 cmets 中所述,在将 data 关键字传递给序列化程序的情况下,您还必须调用 data.is_valid()。

【讨论】:

  • data.is_valid() 应该如何工作? data 字段包含 POST 传递的数据,所以它是一个 dict 列表,而不是序列化器。
  • @HannonCésar 我建议阅读序列化程序:django-rest-framework.org/api-guide/serializers is_valid() 验证传递的数据是有效的可序列化数据,而不是它是序列化程序。 dicts 列表确实是可序列化的。
  • 我认为我的问题表述不正确。我确实了解序列化程序是如何工作的。我的问题是因为在您的示例中,data 参数不是序列化程序实例,因此它没有 .is_valid() 方法。参数实际上就是请求中传递的数据。所以也许方法是分配像s = super(...).get_serializer(...)这样的结果并调用s.is_valid(data)然后返回s?
  • @HannonCésar 哦,是的,我现在明白你的意思了。我想你是正确的。事实上,我复制的答案在这里有更好的答案:stackoverflow.com/a/16697324/1224827
【解决方案3】:

您可以在查看功能中查看座位数并创建一张或多张票:

@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)
    serializer.save()
    return Response(serializer.data)

它会起作用,但老实说,这真是一团糟。您可以在不同的功能中移动用于创建座位的逻辑,使其看起来更好。

【讨论】:

  • 最好的情况,但你记得serializer.save()如果通常情况下
【解决方案4】:

如果您希望用户能够为一张票选择多个座位,最好删除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.

这将允许您在请求中传递一个“座位”数组。

【讨论】:

  • 其实我想为每个座位创建多张票。这意味着每个座位都有自己的票,但用户和节目将是相同的。
【解决方案5】:

如果您不介意在您的 django 项目中添加另一个应用程序,您可以尝试使用django-rest-framework-bulk,如果不介意您可以检查代码并查看它是如何实现的。

如果您使用此应用,您将能够通过在您的 POST 请求中发送元素列表来执行批量创建操作。

例如:

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

【讨论】:

    猜你喜欢
    • 2021-08-01
    • 1970-01-01
    • 2021-12-04
    • 2019-09-02
    • 2018-01-18
    • 2019-03-16
    • 2018-01-08
    • 2016-05-27
    • 2017-12-29
    相关资源
    最近更新 更多