From cf431ffb9d189002228dc626398f0ea63b4b89f9 Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Sat, 24 Oct 2026 13:32:47 +0330 Subject: [PATCH] perf(hadis): enable optional pagination on archive viewsets and eliminate N+1 queries in corrections and categories --- apps/hadis/serializers/serializers_admin.py | 23 ++++--- apps/hadis/views_admin.py | 76 ++++++++++++++++----- 2 files changed, 73 insertions(+), 26 deletions(-) diff --git a/apps/hadis/serializers/serializers_admin.py b/apps/hadis/serializers/serializers_admin.py index 3754e59..0396a73 100644 --- a/apps/hadis/serializers/serializers_admin.py +++ b/apps/hadis/serializers/serializers_admin.py @@ -186,6 +186,13 @@ class AdminHadisCategorySerializer(serializers.ModelSerializer): return [] + view = self.context.get("view") + request = self.context.get("request") + if getattr(view, "action", None) == "list": + include_ancestors = request.query_params.get("include_ancestors") if request else None + if str(include_ancestors).lower() not in ("true", "1"): + return [] + ancestors_qs = obj.get_ancestors(include_self=True).select_related('sect') return [ @@ -576,7 +583,7 @@ class AdminHadisReferenceSerializer(serializers.ModelSerializer): author = obj.book_reference.author - first_ed = obj.book_reference.editions.first() if hasattr(obj.book_reference, "editions") else None + first_ed = next(iter(obj.book_reference.editions.all()), None) if hasattr(obj.book_reference, "editions") else None pub = getattr(obj.book_reference, "publisher", None) @@ -700,7 +707,7 @@ class AdminHadisReferenceSerializer(serializers.ModelSerializer): } - for img in obj.images.all().order_by("priority", "id") + for img in sorted(obj.images.all(), key=lambda x: (x.priority or 0, x.id or 0)) ] @@ -1190,7 +1197,7 @@ class AdminHadisDetailSerializer(serializers.ModelSerializer): author = ref.book_reference.author - first_ed = ref.book_reference.editions.first() if hasattr(ref.book_reference, "editions") else None + first_ed = next(iter(ref.book_reference.editions.all()), None) if hasattr(ref.book_reference, "editions") else None pub = getattr(ref.book_reference, "publisher", None) @@ -1802,7 +1809,7 @@ class AdminCorrectionReferenceSerializer(serializers.ModelSerializer): author = obj.book_reference.author - first_ed = obj.book_reference.editions.first() if hasattr(obj.book_reference, "editions") else None + first_ed = next(iter(obj.book_reference.editions.all()), None) if hasattr(obj.book_reference, "editions") else None pub = getattr(obj.book_reference, "publisher", None) @@ -2232,8 +2239,6 @@ class AdminHadisCorrectionSerializer(serializers.ModelSerializer): ] - ret["references"] = AdminCorrectionReferenceSerializer(instance.references.all(), many=True, context=self.context).data - return ret @@ -2364,7 +2369,7 @@ class AdminInterpretationReferenceSerializer(serializers.ModelSerializer): author = obj.book_reference.author - first_ed = obj.book_reference.editions.first() if hasattr(obj.book_reference, "editions") else None + first_ed = next(iter(obj.book_reference.editions.all()), None) if hasattr(obj.book_reference, "editions") else None pub = getattr(obj.book_reference, "publisher", None) @@ -2670,7 +2675,7 @@ class AdminOriginalTextReferenceSerializer(serializers.ModelSerializer): author = obj.book_reference.author - first_ed = obj.book_reference.editions.first() if hasattr(obj.book_reference, "editions") else None + first_ed = next(iter(obj.book_reference.editions.all()), None) if hasattr(obj.book_reference, "editions") else None pub = getattr(obj.book_reference, "publisher", None) @@ -3094,8 +3099,6 @@ class AdminHadisInterpretationSerializer(serializers.ModelSerializer): ] - ret["references"] = AdminInterpretationReferenceSerializer(instance.references.all(), many=True, context=self.context).data - return ret diff --git a/apps/hadis/views_admin.py b/apps/hadis/views_admin.py index 66dc990..8f311b8 100644 --- a/apps/hadis/views_admin.py +++ b/apps/hadis/views_admin.py @@ -282,7 +282,7 @@ class DovodiHadisSectViewSet(ModelViewSet): authentication_classes = [TokenAuthentication] - pagination_class = None + pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) @@ -320,7 +320,7 @@ class DovodiHadisCategoryViewSet(ModelViewSet): authentication_classes = [TokenAuthentication] - pagination_class = None + pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) @@ -484,7 +484,7 @@ class AdminHadisTagViewSet(ModelViewSet): authentication_classes = [TokenAuthentication] - pagination_class = None + pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) @@ -514,7 +514,7 @@ class AdminHadisCollectionViewSet(ModelViewSet): authentication_classes = [TokenAuthentication] - pagination_class = None + pagination_class = OptionalPagination parser_classes = (MultiPartParser, FormParser, JSONParser) @@ -1366,7 +1366,7 @@ class AdminHadisCorrectionViewSet(ModelViewSet): def get_queryset(self): - from django.db.models import Count, OuterRef, Subquery, IntegerField + from django.db.models import Count, OuterRef, Subquery, IntegerField, Prefetch from django.db.models.functions import Coalesce @@ -1386,10 +1386,35 @@ class AdminHadisCorrectionViewSet(ModelViewSet): - queryset = HadisCorrection.objects.all().select_related("hadis").prefetch_related("references", "references__images").annotate( - - annotated_bookmark_count=Coalesce(Subquery(bookmarks_subquery, output_field=IntegerField()), 0) + references_prefetch = Prefetch( + "references", + queryset=CorrectionReference.objects.select_related( + "book_reference__author", "edition", "book_volume" + ).defer( + "book_reference__description", + "book_reference__normalized_text", + "book_reference__author__scholarity", + "book_reference__author__features", + ).prefetch_related("images", "book_reference__editions").order_by("id"), + ) + queryset = ( + HadisCorrection.objects.all() + .defer( + "normalized_text", + "embedded_in", + "hadis__text", + "hadis__translation", + "hadis__explanation", + "hadis__description", + "hadis__normalized_text", + "hadis__embedded_in", + ) + .select_related("hadis") + .prefetch_related(references_prefetch) + .annotate( + annotated_bookmark_count=Coalesce(Subquery(bookmarks_subquery, output_field=IntegerField()), 0) + ) ) hadis_id = self.request.query_params.get("hadis_id") or self.request.query_params.get("hadis") @@ -1482,7 +1507,7 @@ class AdminCorrectionReferenceViewSet(ModelViewSet): "book_reference__author", "edition", "book_volume" - ).prefetch_related("images") + ).prefetch_related("images", "book_reference__editions") correction_id = self.request.query_params.get("correction_id") @@ -1516,7 +1541,7 @@ class AdminHadisReferenceViewSet(ModelViewSet): "book_reference__author", "edition", "book_volume" - ).prefetch_related("images") + ).prefetch_related("images", "book_reference__editions") hadis_id = self.request.query_params.get("hadis_id") @@ -1563,7 +1588,7 @@ class AdminInterpretationReferenceViewSet(ModelViewSet): "book_reference__author", "edition", "book_volume" - ).prefetch_related("images") + ).prefetch_related("images", "book_reference__editions") interpretation_id = self.request.query_params.get("interpretation_id") or self.request.query_params.get("interpretation") @@ -1611,7 +1636,7 @@ class AdminOriginalTextReferenceViewSet(ModelViewSet): "book_reference__author", "edition", "book_volume" - ).prefetch_related("images") + ).prefetch_related("images", "book_reference__editions") original_text_id = self.request.query_params.get("original_text_id") or self.request.query_params.get("original_text") @@ -1641,7 +1666,26 @@ class AdminHadisInterpretationViewSet(ModelViewSet): def get_queryset(self): - queryset = HadisInterpretation.objects.all().select_related("category").prefetch_related("references", "references__images") + from django.db.models import Prefetch + + references_prefetch = Prefetch( + "references", + queryset=InterpretationReference.objects.select_related( + "book_reference__author", "edition", "book_volume" + ).defer( + "book_reference__description", + "book_reference__normalized_text", + "book_reference__author__scholarity", + "book_reference__author__features", + ).prefetch_related("images", "book_reference__editions").order_by("id"), + ) + + queryset = ( + HadisInterpretation.objects.all() + .defer("normalized_text", "category__description") + .select_related("category") + .prefetch_related(references_prefetch) + ) category_id = self.request.query_params.get("category_id") or self.request.query_params.get("category") @@ -1761,7 +1805,7 @@ class DovodiBookSubjectAreaViewSet(ModelViewSet): authentication_classes = [TokenAuthentication] - pagination_class = None + pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) @@ -1791,7 +1835,7 @@ class DovodiBookTypeViewSet(ModelViewSet): authentication_classes = [TokenAuthentication] - pagination_class = None + pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) @@ -2624,7 +2668,7 @@ class AdminBookTagViewSet(ModelViewSet): serializer_class = AdminBookTagSerializer - pagination_class = None + pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser)