Skip to content

Commit c96bc8d

Browse files
committed
Fix PerAggregatedViewSet N+1 query issue
1 parent 991f8c6 commit c96bc8d

2 files changed

Lines changed: 73 additions & 26 deletions

File tree

per/drf_views.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,8 +1075,27 @@ class PerAggregatedViewSet(viewsets.ReadOnlyModelViewSet):
10751075
get_filtered_queryset = RegionRestrictedAdmin.get_filtered_queryset
10761076

10771077
def get_queryset(self):
1078-
queryset = Overview.objects.filter(
1079-
id__in=Overview.objects.order_by("country_id", "-assessment_number").distinct("country_id").values("id")
1078+
latest_assessment = Prefetch(
1079+
"perassessment_set",
1080+
queryset=PerAssessment.objects.order_by("-id"),
1081+
to_attr="latest_perassessments",
1082+
)
1083+
latest_prioritization = Prefetch(
1084+
"formprioritization_set",
1085+
queryset=FormPrioritization.objects.order_by("-id"),
1086+
to_attr="latest_prioritizations",
1087+
)
1088+
latest_workplan = Prefetch(
1089+
"perworkplan_set",
1090+
queryset=PerWorkPlan.objects.order_by("-id"),
1091+
to_attr="latest_workplans",
1092+
)
1093+
queryset = (
1094+
Overview.objects.filter(
1095+
id__in=Overview.objects.order_by("country_id", "-assessment_number").distinct("country_id").values("id")
1096+
)
1097+
.select_related("country", "type_of_assessment")
1098+
.prefetch_related(latest_assessment, latest_prioritization, latest_workplan)
10801099
)
10811100
return self.get_filtered_queryset(self.request, queryset, dispatch=0)
10821101

per/serializers.py

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -628,23 +628,37 @@ class Meta:
628628
"translation_module_skip_auto_translation",
629629
)
630630

631+
@staticmethod
632+
def _get_latest_prefetched_id(obj, attr_name, model_cls, filter_kwargs) -> typing.Optional[int]:
633+
prefetched = getattr(obj, attr_name, None)
634+
if prefetched is not None:
635+
return prefetched[0].id if prefetched else None
636+
instance = model_cls.objects.filter(**filter_kwargs).order_by("-id").first()
637+
return instance.id if instance else None
638+
631639
def get_assessment(self, obj) -> typing.Optional[int]:
632-
assessment = PerAssessment.objects.filter(overview=obj).last()
633-
if assessment:
634-
return assessment.id
635-
return None
640+
return self._get_latest_prefetched_id(
641+
obj,
642+
"latest_perassessments",
643+
PerAssessment,
644+
{"overview": obj},
645+
)
636646

637647
def get_prioritization(self, obj) -> typing.Optional[int]:
638-
prioritization = FormPrioritization.objects.filter(overview=obj).last()
639-
if prioritization:
640-
return prioritization.id
641-
return None
648+
return self._get_latest_prefetched_id(
649+
obj,
650+
"latest_prioritizations",
651+
FormPrioritization,
652+
{"overview": obj},
653+
)
642654

643655
def get_workplan(self, obj) -> typing.Optional[int]:
644-
workplan = PerWorkPlan.objects.filter(overview=obj).last()
645-
if workplan:
646-
return workplan.id
647-
return None
656+
return self._get_latest_prefetched_id(
657+
obj,
658+
"latest_workplans",
659+
PerWorkPlan,
660+
{"overview": obj},
661+
)
648662

649663

650664
class PublicPerProcessSerializer(serializers.ModelSerializer):
@@ -677,23 +691,37 @@ class Meta:
677691
"ns_focal_point_email",
678692
)
679693

694+
@staticmethod
695+
def _get_latest_prefetched_id(obj, attr_name, model_cls, filter_kwargs) -> typing.Optional[int]:
696+
prefetched = getattr(obj, attr_name, None)
697+
if prefetched is not None:
698+
return prefetched[0].id if prefetched else None
699+
instance = model_cls.objects.filter(**filter_kwargs).order_by("-id").first()
700+
return instance.id if instance else None
701+
680702
def get_assessment(self, obj) -> typing.Optional[int]:
681-
assessment = PerAssessment.objects.filter(overview=obj).last()
682-
if assessment:
683-
return assessment.id
684-
return None
703+
return self._get_latest_prefetched_id(
704+
obj,
705+
"latest_perassessments",
706+
PerAssessment,
707+
{"overview": obj},
708+
)
685709

686710
def get_prioritization(self, obj) -> typing.Optional[int]:
687-
prioritization = FormPrioritization.objects.filter(overview=obj).last()
688-
if prioritization:
689-
return prioritization.id
690-
return None
711+
return self._get_latest_prefetched_id(
712+
obj,
713+
"latest_prioritizations",
714+
FormPrioritization,
715+
{"overview": obj},
716+
)
691717

692718
def get_workplan(self, obj) -> typing.Optional[int]:
693-
workplan = PerWorkPlan.objects.filter(overview=obj).last()
694-
if workplan:
695-
return workplan.id
696-
return None
719+
return self._get_latest_prefetched_id(
720+
obj,
721+
"latest_workplans",
722+
PerWorkPlan,
723+
{"overview": obj},
724+
)
697725

698726

699727
class QuestionResponsesSerializer(

0 commit comments

Comments
 (0)