如何在SerializerMethodField()处使用多个queryset优化Djangorestframework序列化程序

2024-05-23 17:26:41 发布

您现在位置:Python中文网/ 问答频道 /正文

我是django rest框架的新手,我有一个项目,需要优化现有API端点的响应时间。使用django调试工具栏,找到了一些使用prefetch_related()减少SQL查询的方法。问题是,我发现序列化程序的设计是使用多个SerializerMethodField来获取其统计值,并且使用prefetch_related仅在all()上有效,但在第二个序列化程序方法字段上,仍然会为每个类循环查询filter()。您可能会看到下面的示例代码(这不是特定的代码,而是类似这样的代码)

serializer.py:

class ClassStatisticsSerializer(ModelBaseSerializer):
    total_sessions = serializers.SerializerMethodField()
    activity_count = serializers.SerializerMethodField()

    def get_total_sessions(self, instance):
        sessions = instance.classactivity_set.all()
        date = self.context.get('date')
        if date:
            sessions = sessions.filter(date=date)

        if len(sessions) == 0:
            return None

        return sessions.count()

    def get_activity_count(self, instance):
        activities = instance.classactivity_set.filter(is_present=False)
        date = self.context.get('date')
        if date:
            activities = activities.filter(date=date)

        if len(activities) == 0:
            return None

        return activities.count()

    class Meta:
        model = Class
        fields = (
           'id',
           'batch',
           'type,
           'total_sessions'
           'activity_count'
        )

views.py:

class ClassStatisticsList(APIView):
     def get(self, request, *args, **kwargs):
          queryset = Class.objects.all().prefetch_related('classactivity_set')
          serializer = ClassStatisticsSerializer()
          return Response(serializer, status=status.HTTP_200_OK)

在这里,当我检查django debug工具栏上的SQL查询时,get_total_会话在SQL上被查询了一次,但get_activity_计数没有,因为这是SQL上的另一个查询请求,并且每个类循环都被查询。现在,制作一个新模型是不可能的,所以我一直在研究如何正确地预取第二个查询请求。希望您能提出可能的解决方法。提前谢谢各位


Tags: djangoinstanceselfsqlgetdatereturnif
1条回答
网友
1楼 · 发布于 2024-05-23 17:26:41

您可以annotate使用queryset计算对象的计数,然后通过序列化程序类初始化传递它。像这样:

from django.db.models import Count, Q

condition = {}
if date:
    condition = {'date':date}

queryset = Class.objects.all().annotate(
    total_sessions=Count(
        'classactivity',
        filter=Q(**condition),
        distinct=True
    ),
    activity_count=Count(
        'classactivity',
        filter=Q(**condition)&Q(classactivity__is_present=False),
        distinct=True
    )
)

然后您需要从序列化程序中删除SerializerMethodField,一个简单的整数字段应该足以从queryset中获取数据,而无需额外的db命中:

class ClassStatisticsSerializer(ModelBaseSerializer):
    total_sessions = serializers.IntegerField()
    activity_count = serializers.IntegerField()

相关问题 更多 >