简体   繁体   English

Django Rest 框架 - 减少查询并提高 SerializerMethodField() 的性能

[英]Django Rest Framework - reduce queries and increase performance for SerializerMethodField()

I have a model like this with relationship Booking -- Payment (one to may)我有一个像这样的 model 与关系预订 - 付款(一个到可能)

(one Booking can have many Payments) (一个预订可以有多个付款)

My problem is that I calling too many expensive queries because of SerializerFieldModel().我的问题是因为 SerializerFieldModel() 调用了太多昂贵的查询。

class Booking(AbstractItem):
    accommodation = models.CharField(max_length=100)
    room = models.CharField(max_length=100)
    booking_method = models.CharField(max_length=100)

class Payment(AbstractItem):
    booking = models.ForeignKey(Booking, on_delete=models.CASCADE)
    # paid, refund
    status = models.PositiveIntegerField(choices=core_choices.PAYMENT_STATUS, default=core_choices.PAID, null=True)
    # cash, credit_card, transfer
    payment_method = models.PositiveIntegerField(choices=core_choices.PAYMENT_METHOD, default=core_choices.CASH)
    price = models.DecimalField(max_digits=10, decimal_places=0, null=True)

This is my serializer这是我的序列化器

class BookingXLSXSerializer(ModelSerializer):
    paid_cash = SerializerMethodField()
    paid_card = SerializerMethodField()
    refund_cash = SerializerMethodField()
    refund_card = SerializerMethodField()

    class Meta:
        model = Booking
        fields = ('id', 'accommodation ', 'room', 'booking_method', 'paid_cash', 'paid_card', 'refund_cash', 'refund_card')


    def get_paid_cash(self, obj):
        payments = Payment.objects.filter(booking=obj.id, status=core_choices.CASH)
        cash = 0
        for payment in payments:
           cash += payment.price
        return cash

    #I noticed that many of the def could use the .all() query then just filter it out
...

this is my view:这是我的观点:

class MyExampleViewSet(ListAPIView):
    queryset = Booking.objects.all()
    serializer_class = BookingXLSXSerializer
    pagination_class = SuperLargePagination

I noticed that many SerializerMethodField() could share query and use different filter.我注意到许多 SerializerMethodField() 可以共享查询并使用不同的过滤器。 Is there a smarter way to reduce calling queries for SerializerMethodField().有没有更聪明的方法来减少对 SerializerMethodField() 的调用查询。 Or maybe a way to share the query?或者也许是一种共享查询的方法?

prefetch_related() prefetch_related()

You can try adding a prefetch_related clause:您可以尝试添加prefetch_related子句:

queryset = Booking.objects.prefetch_related('payment_set').all()

To benefit from this, you need to change the code in your serializer to actually use the related field, for example:要从中受益,您需要更改序列化程序中的代码以实际使用相关字段,例如:

cash = obj.payment_set.filter(status=core_choices.CASH).aggregate(Sum('price'))['price_sum']

This will still result in two queries, though.但是,这仍然会导致两个查询。 annotate is more precise. annotate更精确。

See also Django: Does prefetch_related() follow reverse relationship lookup?另请参阅Django:prefetch_related() 是否遵循反向关系查找?

annotate()注释()

Another, more complex possibility which also gives you more influence what the DB is actually returning to you, are annotations :另一个更复杂的可能性也给你更多的影响数据库实际上返回给你的是注释

This will reduce it to one query letting the DB do all of the work.这会将其减少为一个查询,让数据库完成所有工作。 I'm writing this down without testing, you will have to check the details out for yourself.我在没有测试的情况下写下来,你必须自己检查细节。

It is definitly not easy to create complex annotations but it is a great tool for improving performance, and you can find a lot of good code for it already on this site.创建复杂的注释肯定不容易,但它是提高性能的好工具,你可以在这个网站上找到很多好的代码。 (And it can actually be fun...) (它实际上可能很有趣......)

from django.db.models import OuterRef, Subquery, IntegerField

class MyExampleViewSet(ListAPIView):
    cash_sub_qs = Payment.objects.filter(
        booking=OuterRef('pk'),
        status=core_choices.CASH
    ).annotate(paid_cash=Sum('price')).values('paid_cash')[:1]
    queryset = Booking.objects.annotate(
        paid_cash=Subquery(cash_sub_qs, output_field=DecimalField()),
        refund_cash=..., # missing from your question, prolly also a sum?
    ).all()
    serializer_class = BookingXLSXSerializer
    pagination_class = SuperLargePagination

Your serializer should only use the queryset call as a fallback:您的序列化程序应该只使用 queryset 调用作为后备:

class BookingXLSXSerializer(ModelSerializer):
    paid_cash = SerializerMethodField()
    paid_card = SerializerMethodField()
    refund_cash = SerializerMethodField()
    refund_card = SerializerMethodField()

    class Meta:
        model = Booking
        fields = ('id', 'accommodation ', 'room', 'booking_method', 'paid_cash', 'paid_card', 'refund_cash', 'refund_card')


    def get_paid_cash(self, obj):
        cash = getattr(obj, 'paid_cash', None) # this is the annotated field
        if cash is None:
            cash = obj.payment_set.filter(status=core_choices.CASH).aggregate(Sum('price'))['price_sum']
        return cash

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM