from django.db.models import Q, Exists, OuterRef, Count from rest_framework.authentication import TokenAuthentication from rest_framework.decorators import action from rest_framework.parsers import FormParser, JSONParser, MultiPartParser from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet from apps.account.permissions import IsSuperAdmin from utils.pagination import StandardResultsSetPagination, OptionalPagination from .models import ( Hadis, HadisCategory, HadisCollection, HadisInCollection, HadisCorrection, HadisInterpretation, HadisStatus, HadisTag, HadisSect, HadisTransmitter, HadisReference, ReferenceImage, BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAuthorRutba, BookAttribute, BookSubjectArea, BookType, BookTag, BookEdition, BookVolume, Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, ScholarlyOpinion, TransmitterOpinion, TransmitterOriginalText, YearValue, NarratorDate, CorrectionReference, InterpretationReference, OriginalTextReference, ContentRelease, CategoryQuranVerse, City, Country, BookPublisher, Kunya, TransmitterFeature, TransmitterProfession, SahabiStatus, Madhhab, RelationType, ) from .serializers.serializers_admin import ( AdminHadisCategorySerializer, AdminHadisSectArchiveSerializer, AdminHadisCategoryArchiveSerializer, AdminHadisCategoryArchiveDetailSerializer, AdminHadisCorrectionSerializer, AdminCorrectionReferenceSerializer, AdminInterpretationReferenceSerializer, AdminOriginalTextReferenceSerializer, AdminHadisReferenceSerializer, AdminHadisDetailSerializer, AdminHadisInterpretationSerializer, AdminHadisCollectionSerializer, AdminHadisListSerializer, AdminHadisBriefSerializer, AdminHadisStatusSerializer, AdminHadisTagSerializer, AdminBookSubjectAreaSerializer, AdminBookTypeSerializer, AdminBookTagSerializer, AdminBookAuthorSerializer, AdminBookAuthorRutbaSerializer, AdminBookAttributeSerializer, AdminBookReferenceImageSerializer, AdminHadisReferenceImageSerializer, AdminBookReferenceDocumentSerializer, AdminBookReferenceListSerializer, AdminBookReferenceDetailSerializer, AdminTransmitterListSerializer, AdminTransmitterDetailSerializer, AdminTransmitterReliabilitySerializer, AdminYearValueSerializer, AdminNarratorLayerSerializer, AdminOpinionStatusSerializer, AdminScholarlyOpinionSerializer, AdminHadisTransmitterSerializer, AdminTransmitterOpinionSerializer, AdminTransmitterOriginalTextSerializer, AdminBookEditionSerializer, AdminBookVolumeSerializer, AdminContentReleaseSerializer, AdminCitySerializer, AdminCountrySerializer, AdminBookPublisherSerializer, AdminKunyaSerializer, AdminTransmitterFeatureSerializer, AdminTransmitterProfessionSerializer, AdminSahabiStatusSerializer, AdminMadhhabSerializer, AdminRelationTypeSerializer, ) class AdminHadisCategoryViewSet(ReadOnlyModelViewSet): serializer_class = AdminHadisCategorySerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination def get_queryset(self): hadis_subquery = Hadis.objects.filter(Q(category=OuterRef("pk")) | Q(categories=OuterRef("pk"))) children_subquery = HadisCategory.objects.filter(parent=OuterRef("pk")) quran_verse_subquery = CategoryQuranVerse.objects.filter(category=OuterRef("pk")) queryset = HadisCategory.objects.select_related("parent", "sect").annotate( annotated_has_hadith=Exists(hadis_subquery), annotated_has_children=Exists(children_subquery), annotated_has_quran_verse=Exists(quran_verse_subquery), ) has_quran_verse = self.request.query_params.get("has_quran_verse") exclude_verse_id = self.request.query_params.get("exclude_verse_id") if has_quran_verse in ["false", "False", "0"]: if exclude_verse_id: queryset = queryset.filter( Q(source_type="quran", quran_verse__isnull=True) | Q(quran_verse__id=exclude_verse_id) ) else: queryset = queryset.filter(source_type="quran", quran_verse__isnull=True) elif has_quran_verse in ["true", "True", "1"]: queryset = queryset.filter(quran_verse__isnull=False) source_type = self.request.query_params.get("source_type") if source_type: queryset = queryset.filter(source_type=source_type) else: source_type = self.request.query_params.get("source_type") if source_type: queryset = queryset.filter(source_type=source_type) search = self.request.query_params.get("search") if search: queryset = queryset.filter( Q(title__icontains=search) | Q(slug__icontains=search) ) has_children = self.request.query_params.get("has_children") if has_children is not None and has_children != "all": if str(has_children).lower() in ["false", "0"]: queryset = queryset.filter(annotated_has_children=False) elif str(has_children).lower() in ["true", "1"]: queryset = queryset.filter(annotated_has_children=True) is_leaf = self.request.query_params.get("is_leaf") if is_leaf is not None and is_leaf != "all": if str(is_leaf).lower() in ["true", "1"]: queryset = queryset.filter(annotated_has_children=False) elif str(is_leaf).lower() in ["false", "0"]: queryset = queryset.filter(annotated_has_children=True) is_topic = self.request.query_params.get("is_topic") if is_topic is not None and is_topic != "all": if str(is_topic).lower() in ["true", "1"]: queryset = queryset.filter(is_topic=True) elif str(is_topic).lower() in ["false", "0"]: queryset = queryset.filter(is_topic=False) return queryset.order_by("order", "id") class DovodiHadisSectViewSet(ModelViewSet): serializer_class = AdminHadisSectArchiveSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = HadisSect.objects.all() search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(description__icontains=search_query) | Q(sect_type__icontains=search_query) ) return queryset.order_by("order", "id") class DovodiHadisCategoryViewSet(ModelViewSet): serializer_class = AdminHadisCategoryArchiveSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_serializer_class(self): if self.action in ["retrieve"]: return AdminHadisCategoryArchiveDetailSerializer return AdminHadisCategoryArchiveSerializer def get_queryset(self): hadis_subquery = Hadis.objects.filter(Q(category=OuterRef("pk")) | Q(categories=OuterRef("pk"))) children_subquery = HadisCategory.objects.filter(parent=OuterRef("pk")) queryset = HadisCategory.objects.select_related("parent", "sect").annotate( annotated_has_hadith=Exists(hadis_subquery), annotated_has_children=Exists(children_subquery), ) search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(description__icontains=search_query) | Q(slug__icontains=search_query) | Q(source_type__icontains=search_query) ) source_type = self.request.query_params.get("source_type") if source_type and source_type != "all": queryset = queryset.filter(source_type=source_type) sect_type = self.request.query_params.get("sect_type") if sect_type and sect_type != "all": queryset = queryset.filter(sect__sect_type=sect_type) sect_id = self.request.query_params.get("sect") if sect_id and sect_id != "all": queryset = queryset.filter(sect_id=sect_id) has_hadith = self.request.query_params.get("has_hadith") if has_hadith is not None and has_hadith != "all": if str(has_hadith).lower() in ["false", "0"]: queryset = queryset.filter(annotated_has_hadith=False) elif str(has_hadith).lower() in ["true", "1"]: queryset = queryset.filter(annotated_has_hadith=True) has_children = self.request.query_params.get("has_children") if has_children is not None and has_children != "all": if str(has_children).lower() in ["false", "0"]: queryset = queryset.filter(annotated_has_children=False) elif str(has_children).lower() in ["true", "1"]: queryset = queryset.filter(annotated_has_children=True) is_leaf = self.request.query_params.get("is_leaf") if is_leaf is not None and is_leaf != "all": if str(is_leaf).lower() in ["true", "1"]: queryset = queryset.filter(annotated_has_children=False) elif str(is_leaf).lower() in ["false", "0"]: queryset = queryset.filter(annotated_has_children=True) is_topic = self.request.query_params.get("is_topic") if is_topic is not None and is_topic != "all": if str(is_topic).lower() in ["true", "1"]: queryset = queryset.filter(is_topic=True) elif str(is_topic).lower() in ["false", "0"]: queryset = queryset.filter(is_topic=False) return queryset.order_by("-id") class AdminHadisStatusViewSet(ModelViewSet): serializer_class = AdminHadisStatusSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = HadisStatus.objects.all() search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) | Q(description__icontains=search_query) ) return queryset.order_by("order", "id") class AdminHadisTagViewSet(ModelViewSet): serializer_class = AdminHadisTagSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = HadisTag.objects.all() search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter(title__icontains=search_query) return queryset.order_by("id") class AdminHadisCollectionViewSet(ModelViewSet): serializer_class = AdminHadisCollectionSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (MultiPartParser, FormParser, JSONParser) def get_queryset(self): queryset = HadisCollection.objects.all() search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) | Q(summary__icontains=search_query) ) status_filter = self.request.query_params.get("status") if status_filter in {"true", "false"}: queryset = queryset.filter(status=status_filter == "true") return queryset.order_by("-created_at", "-id") @action(detail=True, methods=["post"], url_path="assign-items") def assign_items(self, request, pk=None): collection = self.get_object() add_ids = request.data.get("add_ids", []) or request.data.get("item_ids", []) remove_ids = request.data.get("remove_ids", []) if add_ids: new_items = [ HadisInCollection(hadis_id=hid, collection_id=collection.id) for hid in add_ids ] HadisInCollection.objects.bulk_create(new_items, ignore_conflicts=True) if remove_ids: HadisInCollection.objects.filter(collection_id=collection.id, hadis_id__in=remove_ids).delete() return Response({"status": "success", "attached_count": collection.hadis_items.count()}) class AdminTransmitterReliabilityViewSet(ReadOnlyModelViewSet): serializer_class = AdminTransmitterReliabilitySerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination def get_queryset(self): queryset = TransmitterReliability.objects.all() search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) | Q(description__icontains=search_query) ) return queryset.order_by("-id") class DovodiNarratorLayerViewSet(ModelViewSet): serializer_class = AdminNarratorLayerSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = NarratorLayer.objects.select_related("hadis").all() hadis_id = self.request.query_params.get("hadis") or self.request.query_params.get("hadis_id") if hadis_id: queryset = queryset.filter(hadis_id=hadis_id) search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(name__icontains=search_query) | Q(description__icontains=search_query) | Q(slug__icontains=search_query) ) return queryset.order_by("number", "id") class DovodiTransmitterReliabilityViewSet(ModelViewSet): serializer_class = AdminTransmitterReliabilitySerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = TransmitterReliability.objects.all() search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) | Q(description__icontains=search_query) ) return queryset.order_by("-id") class DovodiOpinionStatusViewSet(ModelViewSet): serializer_class = AdminOpinionStatusSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = OpinionStatus.objects.all() search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) | Q(description__icontains=search_query) ) return queryset.order_by("-id") class DovodiScholarlyOpinionViewSet(ModelViewSet): serializer_class = AdminScholarlyOpinionSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = ScholarlyOpinion.objects.select_related("status").annotate( transmitters_count_val=Count("transmitter_opinions__transmitter", distinct=True) ) search_query = self.request.query_params.get("search") if search_query: q_filter = ( Q(opinion_text__icontains=search_query) | Q(status__title__icontains=search_query) ) if search_query.isdigit(): q_filter |= Q(id=int(search_query)) queryset = queryset.filter(q_filter) status_id = self.request.query_params.get("status") if status_id and status_id != "all": queryset = queryset.filter(status_id=status_id) return queryset.order_by("-id") class DovodiYearValueViewSet(ModelViewSet): serializer_class = AdminYearValueSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): try: YearValue.ensure_default_context_tags() except Exception: pass queryset = YearValue.objects.all() category = self.request.query_params.get("category") or self.request.query_params.get("date_type") if category and category != "all": queryset = queryset.filter(category=category) parse_type = self.request.query_params.get("parse_type") or self.request.query_params.get("date_mode") if parse_type and parse_type != "all": queryset = queryset.filter(parse_type=parse_type) # If context_only=true or parse_type/date_mode=context, deduplicate #tags by returning one representative record per raw_value context_only = self.request.query_params.get("context_only") if context_only in ("true", "1", "True") or parse_type == "context": queryset = queryset.filter(parse_type="context") if not category: # Prefer 'death' record so each #tag appears once in the context manager list death_tags = set( YearValue.objects.filter(parse_type="context", category="death").values_list("raw_value", flat=True) ) queryset = queryset.filter( Q(category="death") | (Q(category="birth") & ~Q(raw_value__in=death_tags)) ) search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(raw_value__icontains=search_query) | Q(translations__icontains=search_query) ) return queryset.order_by("parse_type", "raw_value", "id") @action(detail=False, methods=["post"], url_path="parse-preview") def parse_preview(self, request): from .utils.date_engine import parse_canonical_date, build_translations_for_parsed raw_value = str(request.data.get("raw_value") or "").strip() category = str(request.data.get("category") or "death").strip() parsed = parse_canonical_date(raw_value, category=category) translations = [] if parsed.is_valid: if parsed.parse_type == "context": yv = YearValue.objects.filter(raw_value=parsed.raw_value).first() if yv and yv.translations: translations = yv.translations if not translations: translations = build_translations_for_parsed(parsed, category=category) return Response({ "raw_value": parsed.raw_value, "is_valid": parsed.is_valid, "parse_type": parsed.parse_type, "value": parsed.value, "value_min": parsed.value_min, "value_max": parsed.value_max, "values_list": parsed.values_list, "error": parsed.error, "suggestion": parsed.suggestion, "translations": translations, }) class AdminTransmitterViewSet(ModelViewSet): permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = StandardResultsSetPagination parser_classes = (MultiPartParser, FormParser, JSONParser) def get_serializer_class(self): if self.action == "list": return AdminTransmitterListSerializer return AdminTransmitterDetailSerializer def get_queryset(self): queryset = Transmitters.objects.select_related("reliability").prefetch_related("narrator_dates__year_value") if getattr(self, "action", None) != "list": queryset = queryset.prefetch_related("relatives__relation_type_ref", "relatives__narrator") search_query = self.request.query_params.get("search") if search_query: q_filter = ( Q(full_name__icontains=search_query) | Q(kunya__icontains=search_query) | Q(known_as__icontains=search_query) | Q(nickname__icontains=search_query) | Q(origin__icontains=search_query) | Q(lived_in__icontains=search_query) | Q(died_in__icontains=search_query) | Q(slug__icontains=search_query) | Q(legacy_id__icontains=search_query) | Q(birth_year_hijri__icontains=search_query) | Q(death_year_hijri__icontains=search_query) | Q(age_at_death__icontains=search_query) ) if search_query.isdigit(): q_filter |= Q(legacy_number=int(search_query)) | Q(generation=int(search_query)) queryset = queryset.filter(q_filter) status_filter = self.request.query_params.get("status") if status_filter and status_filter != "all": queryset = queryset.filter(reliability__slug=status_filter) madhhab_filter = self.request.query_params.get("madhhab") if madhhab_filter and madhhab_filter != "all": queryset = queryset.filter(madhhab=madhhab_filter) generation_filter = self.request.query_params.get("generation") if generation_filter and generation_filter != "all": queryset = queryset.filter(generation=generation_filter) return queryset.order_by("-created_at", "-id") class AdminHadisViewSet(ModelViewSet): permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = StandardResultsSetPagination parser_classes = (MultiPartParser, FormParser, JSONParser) def get_serializer_class(self): if self.action == "list": if self.request.query_params.get("brief") == "true": return AdminHadisBriefSerializer return AdminHadisListSerializer return AdminHadisDetailSerializer def get_queryset(self): queryset = ( Hadis.objects.all() .select_related("category", "hadis_status") .prefetch_related("categories", "collection_items__collection") ) search_query = self.request.query_params.get("search") if search_query: q_filter = ( Q(title__icontains=search_query) | Q(title_narrator__icontains=search_query) | Q(text__icontains=search_query) | Q(translation__icontains=search_query) | Q(slug__icontains=search_query) ) if search_query.isdigit(): q_filter |= Q(number=int(search_query)) queryset = queryset.filter(q_filter) category_id = self.request.query_params.get("category") if category_id and category_id != "all": queryset = queryset.filter(Q(category_id=category_id) | Q(categories__id=category_id)).distinct() collection_id = self.request.query_params.get("collection") or self.request.query_params.get("collection_id") if collection_id and collection_id != "all": queryset = queryset.filter(collection_items__collection_id=collection_id).distinct() source_type = self.request.query_params.get("source_type") if source_type: queryset = queryset.filter(Q(category__source_type=source_type) | Q(categories__source_type=source_type)).distinct() status_filter = self.request.query_params.get("status") if status_filter in {"true", "false"}: queryset = queryset.filter(status=status_filter == "true") return queryset.order_by("-id") @action(detail=False, methods=["post"], url_path="bulk-assign-topics") def bulk_assign_topics(self, request): from django.db import transaction from rest_framework import status as drf_status from .signals import invalidate_hadis_cache hadis_ids = request.data.get("hadis_ids") or [] category_ids = request.data.get("category_ids") or [] mode = request.data.get("mode", "add") if not isinstance(hadis_ids, list) or not hadis_ids: return Response( {"detail": "hadis_ids must be a non-empty list."}, status=drf_status.HTTP_400_BAD_REQUEST, ) if not isinstance(category_ids, list) or not category_ids: return Response( {"detail": "category_ids must be a non-empty list."}, status=drf_status.HTTP_400_BAD_REQUEST, ) try: hadis_ids = [int(hid) for hid in hadis_ids] category_ids = list(dict.fromkeys(int(cid) for cid in category_ids)) except (ValueError, TypeError): return Response( {"detail": "Invalid ID format."}, status=drf_status.HTTP_400_BAD_REQUEST, ) categories = list(HadisCategory.objects.filter(id__in=category_ids)) if len(categories) != len(category_ids): return Response( {"detail": "One or more selected topics do not exist."}, status=drf_status.HTTP_400_BAD_REQUEST, ) for cat in categories: if cat.children.exists(): return Response( {"detail": "Cannot assign items to a parent category that has subcategories. Please select leaf topics only."}, status=drf_status.HTTP_400_BAD_REQUEST, ) hadises_qs = Hadis.objects.filter(id__in=hadis_ids) existing_hadis_ids = list(hadises_qs.values_list("id", flat=True)) if not existing_hadis_ids: return Response( {"detail": "No matching items found."}, status=drf_status.HTTP_404_NOT_FOUND, ) through_model = Hadis.categories.through first_category_id = category_ids[0] with transaction.atomic(): if mode == "replace": through_model.objects.filter(hadis_id__in=existing_hadis_ids).delete() relations = [ through_model(hadis_id=hid, hadiscategory_id=cid) for hid in existing_hadis_ids for cid in category_ids ] through_model.objects.bulk_create(relations, ignore_conflicts=True) Hadis.objects.filter(id__in=existing_hadis_ids).update(category_id=first_category_id) else: relations = [ through_model(hadis_id=hid, hadiscategory_id=cid) for hid in existing_hadis_ids for cid in category_ids ] through_model.objects.bulk_create(relations, ignore_conflicts=True) Hadis.objects.filter(id__in=existing_hadis_ids, category__isnull=True).update( category_id=first_category_id ) invalidate_hadis_cache() cat_map = {c.id: c for c in categories} ordered_cats = [cat_map[cid] for cid in category_ids if cid in cat_map] categories_detail = [ { "id": c.id, "title": c.title, "slug": c.slug, "source_type": c.source_type, "is_topic": True, } for c in ordered_cats ] return Response( { "updated_count": len(existing_hadis_ids), "mode": mode, "categories_detail": categories_detail, }, status=drf_status.HTTP_200_OK, ) class AdminHadisCorrectionViewSet(ModelViewSet): serializer_class = AdminHadisCorrectionSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] parser_classes = (MultiPartParser, FormParser, JSONParser) pagination_class = OptionalPagination def get_queryset(self): from django.db.models import Count, OuterRef, Subquery, IntegerField, Prefetch from django.db.models.functions import Coalesce from apps.bookmark.models.bookmark import Bookmark bookmarks_subquery = Bookmark.objects.filter( service=Bookmark.ServiceChoices.HADITH_CORRECTION, content_id=OuterRef("id"), status=True ).values("content_id").annotate(count=Count("id")).values("count") 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") if hadis_id: queryset = queryset.filter(hadis_id=hadis_id) search = self.request.query_params.get("search") if search: from django.db.models import Q queryset = queryset.filter( Q(title__icontains=search) | Q(text__icontains=search) ) return queryset.order_by("-created_at", "-id") def perform_create(self, serializer): priority = self.request.data.get("priority", 1) try: priority = int(priority) except (ValueError, TypeError): priority = 1 hadis_id = self.request.data.get("hadis") if hadis_id: from django.db.models import F HadisCorrection.objects.filter(hadis_id=hadis_id, priority__gte=priority).update(priority=F("priority") + 1) serializer.save() @action(detail=False, methods=["post"]) def reorder(self, request): correction_ids = request.data.get("correction_ids", []) if not correction_ids: return Response({"error": "correction_ids list is required"}, status=400) for index, correction_id in enumerate(correction_ids, start=1): HadisCorrection.objects.filter(id=correction_id).update(priority=index) return Response({"status": "success"}) class AdminCorrectionReferenceViewSet(ModelViewSet): serializer_class = AdminCorrectionReferenceSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] parser_classes = (MultiPartParser, FormParser, JSONParser) pagination_class = OptionalPagination def get_queryset(self): qs = CorrectionReference.objects.all().select_related( "book_reference__author", "edition", "book_volume" ).prefetch_related("images", "book_reference__editions") correction_id = self.request.query_params.get("correction_id") if correction_id: qs = qs.filter(correction_id=correction_id) return qs.order_by("id") class AdminHadisReferenceViewSet(ModelViewSet): serializer_class = AdminHadisReferenceSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] parser_classes = (MultiPartParser, FormParser, JSONParser) pagination_class = OptionalPagination def get_queryset(self): qs = HadisReference.objects.all().select_related( "book_reference__author", "edition", "book_volume" ).prefetch_related("images", "book_reference__editions") hadis_id = self.request.query_params.get("hadis_id") if hadis_id: qs = qs.filter(hadis_id=hadis_id) return qs.order_by("id") def _sync_hadis_title(self, instance): # If the argument has no title, fill it from the first source's book title hadis = getattr(instance, "hadis", None) if hadis and not Hadis._has_localized_value(hadis.title): if hadis.auto_populate_title(): hadis.save() def perform_create(self, serializer): self._sync_hadis_title(serializer.save()) def perform_update(self, serializer): self._sync_hadis_title(serializer.save()) class AdminInterpretationReferenceViewSet(ModelViewSet): serializer_class = AdminInterpretationReferenceSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] parser_classes = (MultiPartParser, FormParser, JSONParser) pagination_class = OptionalPagination def get_queryset(self): qs = InterpretationReference.objects.all().select_related( "book_reference__author", "edition", "book_volume" ).prefetch_related("images", "book_reference__editions") interpretation_id = self.request.query_params.get("interpretation_id") or self.request.query_params.get("interpretation") if interpretation_id: qs = qs.filter(interpretation_id=interpretation_id) return qs.order_by("id") class AdminOriginalTextReferenceViewSet(ModelViewSet): def perform_create(self, serializer): instance = serializer.save() ot = instance.original_text if ot and (not ot.title or (isinstance(ot.title, list) and not any(isinstance(x, dict) and (x.get('text', '') or x.get('title', '')).strip() for x in ot.title))): if ot.auto_populate_title(): ot.save(update_fields=['title', 'slug', 'share_link']) def perform_update(self, serializer): instance = serializer.save() ot = instance.original_text if ot and (not ot.title or (isinstance(ot.title, list) and not any(isinstance(x, dict) and (x.get('text', '') or x.get('title', '')).strip() for x in ot.title))): if ot.auto_populate_title(): ot.save(update_fields=['title', 'slug', 'share_link']) serializer_class = AdminOriginalTextReferenceSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] parser_classes = (MultiPartParser, FormParser, JSONParser) pagination_class = OptionalPagination def get_queryset(self): qs = OriginalTextReference.objects.all().select_related( "book_reference__author", "edition", "book_volume" ).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") if original_text_id: qs = qs.filter(original_text_id=original_text_id) return qs.order_by("id") class AdminHadisInterpretationViewSet(ModelViewSet): serializer_class = AdminHadisInterpretationSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] parser_classes = (MultiPartParser, FormParser, JSONParser) pagination_class = OptionalPagination def get_queryset(self): 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") if category_id: queryset = queryset.filter(category_id=category_id) search = self.request.query_params.get("search") if search: from django.db.models import Q queryset = queryset.filter( Q(title__icontains=search) | Q(text__icontains=search) ) return queryset.order_by("-created_at", "-id") def perform_create(self, serializer): priority = self.request.data.get("priority", 1) try: priority = int(priority) except (ValueError, TypeError): priority = 1 category_id = self.request.data.get("category") if category_id: from django.db.models import F HadisInterpretation.objects.filter(category_id=category_id, priority__gte=priority).update(priority=F("priority") + 1) serializer.save() @action(detail=False, methods=["post"]) def reorder(self, request): interpretation_ids = request.data.get("interpretation_ids", []) if not interpretation_ids: return Response({"error": "interpretation_ids list is required"}, status=400) for index, interpretation_id in enumerate(interpretation_ids, start=1): HadisInterpretation.objects.filter(id=interpretation_id).update(priority=index) return Response({"status": "success"}) class AdminBookSubjectAreaViewSet(ReadOnlyModelViewSet): serializer_class = AdminBookSubjectAreaSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = None def get_queryset(self): return BookSubjectArea.objects.all().order_by("-created_at", "-id") class AdminBookTypeViewSet(ReadOnlyModelViewSet): serializer_class = AdminBookTypeSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = None def get_queryset(self): return BookType.objects.all().order_by("-created_at", "-id") class DovodiBookSubjectAreaViewSet(ModelViewSet): serializer_class = AdminBookSubjectAreaSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = BookSubjectArea.objects.all() search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter(title__icontains=search_query) return queryset.order_by("-created_at", "-id") class DovodiBookTypeViewSet(ModelViewSet): serializer_class = AdminBookTypeSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = BookType.objects.all() search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter(title__icontains=search_query) return queryset.order_by("-created_at", "-id") class DovodiBookAuthorRutbaViewSet(ModelViewSet): serializer_class = AdminBookAuthorRutbaSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = BookAuthorRutba.objects.all() search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) | Q(description__icontains=search_query) ) return queryset.order_by("-id") class AdminBookAuthorViewSet(ModelViewSet): serializer_class = AdminBookAuthorSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (MultiPartParser, FormParser, JSONParser) def get_queryset(self): queryset = ( BookAuthor.objects .select_related("rutba_status") .prefetch_related("author_dates__year_value") .all() ) search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter(name__icontains=search_query) return queryset.order_by("-created_at", "-id") @action(detail=True, methods=["post"], url_path="assign-references") def assign_references(self, request, pk=None): author = self.get_object() add_ids = request.data.get("add_ids", []) or request.data.get("book_ids", []) or request.data.get("item_ids", []) remove_ids = request.data.get("remove_ids", []) if add_ids: BookReference.objects.filter(id__in=add_ids).update(author=author) if remove_ids: BookReference.objects.filter(id__in=remove_ids, author=author).update(author=None) return Response({"status": "success", "references_count": author.book_references.count()}) class AdminBookAttributeViewSet(ModelViewSet): serializer_class = AdminBookAttributeSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = None def get_queryset(self): queryset = BookAttribute.objects.all() book_reference_id = self.request.query_params.get("book_reference_id") if book_reference_id: queryset = queryset.filter(book_reference_id=book_reference_id) return queryset.order_by("id") class AdminBookReferenceImageViewSet(ModelViewSet): serializer_class = AdminBookReferenceImageSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] parser_classes = (MultiPartParser, FormParser, JSONParser) pagination_class = None def get_queryset(self): queryset = BookReferenceImage.objects.all() book_reference_id = self.request.query_params.get("book_reference_id") if book_reference_id: queryset = queryset.filter(book_reference_id=book_reference_id) return queryset.order_by("order", "id") @action(detail=False, methods=["post"]) def reorder(self, request): image_ids = request.data.get("image_ids", []) or request.data.get("ids", []) if not image_ids: return Response({"error": "image_ids list is required"}, status=400) for index, img_id in enumerate(image_ids, start=1): BookReferenceImage.objects.filter(id=img_id).update(order=index) return Response({"status": "success"}) class AdminHadisReferenceImageViewSet(ModelViewSet): serializer_class = AdminHadisReferenceImageSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] parser_classes = (MultiPartParser, FormParser, JSONParser) pagination_class = None def get_queryset(self): queryset = ReferenceImage.objects.all().select_related("reference") hadis_id = self.request.query_params.get("hadis_id") if hadis_id: queryset = queryset.filter(reference__hadis_id=hadis_id) reference_id = self.request.query_params.get("reference_id") if reference_id: queryset = queryset.filter(reference_id=reference_id) return queryset.order_by("priority", "id") def create(self, request, *args, **kwargs): hadis_id = request.data.get("hadis_id") or request.data.get("hadis") reference_id = request.data.get("reference_id") or request.data.get("reference") thumbnail = request.FILES.get("thumbnail") or request.FILES.get("image") priority = request.data.get("priority", 0) if not thumbnail: return Response({"error": "thumbnail image is required"}, status=400) if hadis_id: try: hadis = Hadis.objects.get(id=hadis_id) except Hadis.DoesNotExist: return Response({"error": "Hadis not found"}, status=404) reference = hadis.references.first() if not reference: reference = HadisReference.objects.create(hadis=hadis) elif reference_id: try: reference = HadisReference.objects.get(id=reference_id) except HadisReference.DoesNotExist: return Response({"error": "HadisReference not found"}, status=404) else: return Response({"error": "hadis_id or reference_id is required"}, status=400) if not priority: last_img = reference.images.order_by("-priority").first() priority = (last_img.priority + 1) if last_img else 1 else: try: priority = int(priority) except (ValueError, TypeError): priority = 1 ref_image = ReferenceImage.objects.create( reference=reference, thumbnail=thumbnail, priority=priority ) serializer = self.get_serializer(ref_image) return Response(serializer.data, status=201) @action(detail=False, methods=["post"]) def reorder(self, request): image_ids = request.data.get("image_ids", []) or request.data.get("ids", []) if not image_ids: return Response({"error": "image_ids list is required"}, status=400) for index, img_id in enumerate(image_ids, start=1): ReferenceImage.objects.filter(id=img_id).update(priority=index) return Response({"status": "success"}) @action(detail=False, methods=["post"]) def bulk_delete(self, request): image_ids = request.data.get("ids", []) or request.data.get("image_ids", []) if not image_ids: return Response({"error": "ids list is required"}, status=400) deleted_count, _ = ReferenceImage.objects.filter(id__in=image_ids).delete() return Response({"status": "success", "deleted_count": deleted_count}) class AdminBookReferenceDocumentViewSet(ModelViewSet): serializer_class = AdminBookReferenceDocumentSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] parser_classes = (MultiPartParser, FormParser, JSONParser) pagination_class = None def get_queryset(self): queryset = BookReferenceDocument.objects.all() book_reference_id = self.request.query_params.get("book_reference_id") if book_reference_id: queryset = queryset.filter(book_reference_id=book_reference_id) return queryset.order_by("order", "id") class AdminBookReferenceViewSet(ModelViewSet): permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = StandardResultsSetPagination parser_classes = (MultiPartParser, FormParser, JSONParser) def get_serializer_class(self): if self.action == "list": return AdminBookReferenceListSerializer return AdminBookReferenceDetailSerializer def get_queryset(self): queryset = BookReference.objects.all().annotate(annotated_hadis_count=Count("hadis_references__hadis", distinct=True)).prefetch_related("subject_area", "volumes", "editions").select_related("type", "author") search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) | Q(editions__isbn__icontains=search_query) | Q(editions__publisher__icontains=search_query) | Q(description__icontains=search_query) | Q(author__name__icontains=search_query) | Q(tag__title__icontains=search_query) | Q(editions__tag__title__icontains=search_query) ) type_id = self.request.query_params.get("type") if type_id and type_id != "all": queryset = queryset.filter(type_id=type_id) madhab = self.request.query_params.get("madhab") if madhab and madhab != "all": queryset = queryset.filter(type__sect=madhab) subject_area_id = self.request.query_params.get("subject_area") if subject_area_id and subject_area_id != "all": queryset = queryset.filter(subject_area__id=subject_area_id) author_id = self.request.query_params.get("author") if author_id and author_id != "all": queryset = queryset.filter(author_id=author_id) ordering = self.request.query_params.get("ordering") if ordering: return queryset.distinct().order_by(ordering) return queryset.distinct().order_by("priority", "-created_at") class AdminCategoryQuranVerseViewSet(ModelViewSet): permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] parser_classes = (JSONParser, MultiPartParser, FormParser) pagination_class = OptionalPagination def get_serializer_class(self): from .serializers.serializers_admin import AdminCategoryQuranVerseSerializer return AdminCategoryQuranVerseSerializer def get_queryset(self): from .models.category import CategoryQuranVerse queryset = ( CategoryQuranVerse.objects.all() .select_related("category", "category__sect") .prefetch_related("secondary_translations") ) search_query = self.request.query_params.get("search") if search_query: q_filter = ( Q(surah_name__icontains=search_query) | Q(ayah_number__icontains=search_query) | Q(arabic_text__icontains=search_query) | Q(translation__icontains=search_query) | Q(translator__icontains=search_query) | Q(category__title__icontains=search_query) | Q(category__slug__icontains=search_query) ) queryset = queryset.filter(q_filter) category_id = self.request.query_params.get("category") if category_id and category_id != "all": queryset = queryset.filter(category_id=category_id) source_type = self.request.query_params.get("source_type") if source_type: queryset = queryset.filter(category__source_type=source_type) return queryset.order_by("-id") class AdminHadisTransmitterViewSet(ModelViewSet): def perform_create(self, serializer): instance = serializer.save() hadis = instance.hadis if hadis and (not hadis.title_narrator or (isinstance(hadis.title_narrator, list) and not any(isinstance(item, dict) and item.get('text', '').strip() for item in hadis.title_narrator))): if hadis.auto_populate_title_narrator(): hadis.save(update_fields=['title_narrator', 'normalized_text']) def perform_update(self, serializer): instance = serializer.save() hadis = instance.hadis if hadis and (not hadis.title_narrator or (isinstance(hadis.title_narrator, list) and not any(isinstance(item, dict) and item.get('text', '').strip() for item in hadis.title_narrator))): if hadis.auto_populate_title_narrator(): hadis.save(update_fields=['title_narrator', 'normalized_text']) serializer_class = AdminHadisTransmitterSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = HadisTransmitter.objects.all().select_related("transmitter", "narrator_layer", "status").prefetch_related("uncertain_transmitters") hadis_id = self.request.query_params.get("hadis_id") if hadis_id: queryset = queryset.filter(hadis_id=hadis_id) return queryset.order_by("order") @action(detail=False, methods=["post"]) def reorder(self, request): from rest_framework.decorators import action from rest_framework.response import Response from django.db import transaction hadis_transmitter_ids = request.data.get("hadis_transmitter_ids", []) if not hadis_transmitter_ids: return Response({"error": "hadis_transmitter_ids list is required"}, status=400) with transaction.atomic(): # Pass 1: Set temporary positive offset order indices to avoid duplicate key violations (>= 0) for index, ht_id in enumerate(hadis_transmitter_ids): HadisTransmitter.objects.filter(id=ht_id).update(order=10000 + index) # Pass 2: Set final sequential order indices starting from 0 for index, ht_id in enumerate(hadis_transmitter_ids): HadisTransmitter.objects.filter(id=ht_id).update(order=index) return Response({"status": "success"}) class AdminTransmitterOpinionViewSet(ModelViewSet): permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] serializer_class = AdminTransmitterOpinionSerializer pagination_class = None def get_queryset(self): queryset = TransmitterOpinion.objects.all().select_related("status", "scholar", "opinion", "opinion__status") transmitter_id = self.request.query_params.get("transmitter_id") if transmitter_id: queryset = queryset.filter(transmitter_id=transmitter_id) return queryset.order_by("order", "id") @action(detail=False, methods=["post"]) def reorder(self, request): opinion_ids = request.data.get("opinion_ids", []) if not opinion_ids: return Response({"error": "opinion_ids list is required"}, status=400) from django.db import transaction with transaction.atomic(): for index, opinion_id in enumerate(opinion_ids): TransmitterOpinion.objects.filter(id=opinion_id).update(order=10000 + index) for index, opinion_id in enumerate(opinion_ids): TransmitterOpinion.objects.filter(id=opinion_id).update(order=index) return Response({"status": "success"}) class AdminTransmitterOriginalTextViewSet(ModelViewSet): permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] serializer_class = AdminTransmitterOriginalTextSerializer parser_classes = (JSONParser, MultiPartParser, FormParser) pagination_class = None def get_queryset(self): queryset = TransmitterOriginalText.objects.all().prefetch_related("references", "references__images") transmitter_id = self.request.query_params.get("transmitter_id") if transmitter_id: queryset = queryset.filter(transmitter_id=transmitter_id) return queryset.order_by("order", "id") @action(detail=False, methods=["post"]) def reorder(self, request): original_text_ids = request.data.get("original_text_ids", []) if not original_text_ids: return Response({"error": "original_text_ids list is required"}, status=400) from django.db import transaction with transaction.atomic(): for index, ot_id in enumerate(original_text_ids): TransmitterOriginalText.objects.filter(id=ot_id).update(order=10000 + index) for index, ot_id in enumerate(original_text_ids): TransmitterOriginalText.objects.filter(id=ot_id).update(order=index) return Response({"status": "success"}) class AdminBookEditionViewSet(ModelViewSet): permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] serializer_class = AdminBookEditionSerializer pagination_class = OptionalPagination def get_queryset(self): queryset = BookEdition.objects.all().select_related( "tag", "publisher_item__city__country", "publisher_item__country", "city_of_publication_city__country", "country_of_publication_country", ).prefetch_related("books", "volumes", "researchers", "editors") book_reference_id = self.request.query_params.get("book_reference_id") if book_reference_id: from django.db.models import Q queryset = queryset.filter(Q(book_reference_id=book_reference_id) | Q(books__id=book_reference_id)).distinct() search_query = self.request.query_params.get("search") if search_query: from django.db.models import Q queryset = queryset.filter( Q(title__icontains=search_query) | Q(edition_number__icontains=search_query) | Q(publisher__icontains=search_query) | Q(year_of_publication__icontains=search_query) | Q(isbn__icontains=search_query) | Q(books__title__icontains=search_query) ).distinct() return queryset.order_by("-created_at") class AdminBookTagViewSet(ModelViewSet): permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] serializer_class = AdminBookTagSerializer pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = BookTag.objects.all() search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter(title__icontains=search_query) return queryset.order_by("-id") class AdminBookVolumeViewSet(ModelViewSet): permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] serializer_class = AdminBookVolumeSerializer pagination_class = None parser_classes = (MultiPartParser, FormParser, JSONParser) def get_queryset(self): queryset = BookVolume.objects.all() book_reference_id = self.request.query_params.get("book_reference_id") edition_id = self.request.query_params.get("edition_id") if book_reference_id: queryset = queryset.filter(book_reference_id=book_reference_id) if edition_id: if edition_id in ["none", "null"]: queryset = queryset.filter(edition__isnull=True) else: queryset = queryset.filter(edition_id=edition_id) return queryset.order_by("title", "id") import re def compute_next_version(current_version_str: str) -> str: """ Increments the last integer part of a version string. Examples: 'V 0.51' -> 'V 0.52' 'V0.5' -> 'V0.6' '1.0.0' -> '1.0.1' '1' -> '2' 'v1.9' -> 'v1.10' """ if not current_version_str or not str(current_version_str).strip(): return "V 1.0" matches = list(re.finditer(r'\d+', str(current_version_str))) if not matches: return f"{str(current_version_str).strip()} 2" last_match = matches[-1] start, end = last_match.span() num_str = last_match.group() next_num = int(num_str) + 1 if len(num_str) > 1 and num_str.startswith('0'): formatted_num = str(next_num).zfill(len(num_str)) else: formatted_num = str(next_num) return str(current_version_str)[:start] + formatted_num + str(current_version_str)[end:] class AdminContentReleaseViewSet(ModelViewSet): serializer_class = AdminContentReleaseSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = None def get_queryset(self): return ContentRelease.objects.all().order_by("-published_at", "-id") def list(self, request, *args, **kwargs): queryset = self.get_queryset() current_release = queryset.filter(is_active=True).first() current_data = self.get_serializer(current_release).data if current_release else None current_ver_name = current_release.version_name if current_release else "V 0.5" next_suggested = compute_next_version(current_ver_name) history_data = self.get_serializer(queryset[:50], many=True).data return Response({ "current_version": current_data, "next_suggested_version": next_suggested, "history": history_data }) @action(detail=False, methods=["post"], url_path="increment") def increment(self, request): current_release = self.get_queryset().filter(is_active=True).first() current_ver_name = current_release.version_name if current_release else "V 0.5" version_name = request.data.get("version_name") if not version_name or not str(version_name).strip(): version_name = compute_next_version(current_ver_name) else: version_name = str(version_name).strip() description = request.data.get("description", "") or "" # Create new content release new_release = ContentRelease.objects.create( version_name=version_name, description=description, is_active=True ) from .signals import invalidate_hadis_cache invalidate_hadis_cache() # تریگر خودکار ساخت و فشرده‌سازی دیتابیس SQLite در پس‌زمینه self._trigger_sqlite_export(notes=f"Auto-generated for version {version_name}") return Response({ "status": "success", "message": f"Version incremented to {version_name}", "release": self.get_serializer(new_release).data, "next_suggested_version": compute_next_version(version_name) }) def _trigger_sqlite_export(self, notes=""): """اجرای پس‌زمینه دستور تولید دیتابیس SQLite بدون متوقف کردن پاسخ HTTP""" import threading import logging from django.core.management import call_command logger = logging.getLogger(__name__) def _task(): try: logger.info("Starting background SQLite export...") call_command('export_hadis_sqlite', notes=notes) logger.info("Background SQLite export completed successfully.") except Exception as e: logger.error(f"Failed to export SQLite in background: {e}", exc_info=True) thread = threading.Thread(target=_task, daemon=True) thread.start() def perform_create(self, serializer): instance = serializer.save() from .signals import invalidate_hadis_cache invalidate_hadis_cache() self._trigger_sqlite_export(notes=f"Auto-generated for created version {instance.version_name}") def perform_update(self, serializer): super().perform_update(serializer) from .signals import invalidate_hadis_cache invalidate_hadis_cache() def perform_destroy(self, instance): super().perform_destroy(instance) from .signals import invalidate_hadis_cache invalidate_hadis_cache() class DovodiCountryViewSet(ModelViewSet): serializer_class = AdminCountrySerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = Country.objects.all().order_by("slug") search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) ) return queryset class DovodiBookPublisherViewSet(ModelViewSet): serializer_class = AdminBookPublisherSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = BookPublisher.objects.select_related("city", "country").all().order_by("slug") search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) | Q(city__title__icontains=search_query) | Q(country__title__icontains=search_query) ) city_id = self.request.query_params.get("city") if city_id and city_id != "all": queryset = queryset.filter(city_id=city_id) country_id = self.request.query_params.get("country") if country_id and country_id != "all": queryset = queryset.filter(country_id=country_id) return queryset class DovodiCityViewSet(ModelViewSet): serializer_class = AdminCitySerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = City.objects.all().select_related("country").order_by("slug") search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) ) country_id = self.request.query_params.get("country") or self.request.query_params.get("country_id") if country_id: queryset = queryset.filter(country_id=country_id) return queryset class DovodiKunyaViewSet(ModelViewSet): serializer_class = AdminKunyaSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = Kunya.objects.all().order_by("slug") search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) ) return queryset class DovodiTransmitterFeatureViewSet(ModelViewSet): serializer_class = AdminTransmitterFeatureSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = TransmitterFeature.objects.all().order_by("slug") search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) ) return queryset class DovodiTransmitterProfessionViewSet(ModelViewSet): serializer_class = AdminTransmitterProfessionSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = TransmitterProfession.objects.all().order_by("slug") search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) ) return queryset class DovodiSahabiStatusViewSet(ModelViewSet): serializer_class = AdminSahabiStatusSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = SahabiStatus.objects.all().order_by("id") search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) ) return queryset class DovodiMadhhabViewSet(ModelViewSet): serializer_class = AdminMadhhabSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = Madhhab.objects.all().order_by("slug", "id") search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) ) return queryset class DovodiRelationTypeViewSet(ModelViewSet): serializer_class = AdminRelationTypeSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): queryset = RelationType.objects.all().order_by("slug", "id") search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) | Q(code__icontains=search_query) ) return queryset