简体   繁体   中英

How to optimize django-rest-framework serializers with multiple queryset at SerializerMethodField()

I am new to django-rest-framework, and I have a project where I have to optimize the response time of an existing API endpoint. Using django-debug-toolbar, found some ways to use prefetch_related() to lessen the SQL queries. The problem is I found that the design of the serializer is to have multiple SerializerMethodField to get its statistics values and using prefetch_related only works on all() but on the second serializer method field with filter() is still being queried for every Class loop. You may see sample code below(this is not the specific code but something like this).

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)

In here, when I check the SQL queries on the django debug toolbar, get_total_sessions was being queried once on the SQL but get_activity_count was not since this is another query request on the SQL and being queried for every Class loop. For now, making a new model is out of the question so i am stuck on how to properly prefetch the second query request. Hope you can suggest possible way to resolve this. Thank you in advance guys.

You can annotate the count for the objects with queryset and then pass it through serializer class initialization. Like this:

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

Then you need to remove SerializerMethodField s from your serializer, a simple IntegerField should be suffice to get the data from queryset without addional db hits:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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