Reputation: 15
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.
Upvotes: 1
Views: 1359
Reputation: 51978
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()
Upvotes: 2