From d3c075c7fb834373cea18fb659a407b83cb943bb Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Tue, 6 Oct 2026 12:20:06 +0330 Subject: [PATCH] feat(hadis): add multi-reference endpoints and serializers for interpretations with scan images --- apps/hadis/serializers/serializers_admin.py | 158 +++++++++++++++++++- apps/hadis/urls.py | 3 +- apps/hadis/views_admin.py | 20 ++- 3 files changed, 178 insertions(+), 3 deletions(-) diff --git a/apps/hadis/serializers/serializers_admin.py b/apps/hadis/serializers/serializers_admin.py index fdfea3c..64d15a8 100644 --- a/apps/hadis/serializers/serializers_admin.py +++ b/apps/hadis/serializers/serializers_admin.py @@ -1076,8 +1076,162 @@ class AdminHadisCorrectionSerializer(serializers.ModelSerializer): return instance +class AdminInterpretationReferenceSerializer(serializers.ModelSerializer): + book_reference_detail = serializers.SerializerMethodField(read_only=True) + edition_detail = serializers.SerializerMethodField(read_only=True) + book_volume_detail = serializers.SerializerMethodField(read_only=True) + images = serializers.SerializerMethodField(read_only=True) + delete_image_ids = serializers.ListField( + child=serializers.IntegerField(), + required=False, + write_only=True + ) + + class Meta: + model = InterpretationReference + fields = [ + "id", + "interpretation", + "book_reference", + "book_reference_detail", + "edition", + "edition_detail", + "book_volume", + "book_volume_detail", + "hadith_number", + "volume", + "pages", + "address", + "url", + "images", + "delete_image_ids", + ] + + def get_book_reference_detail(self, obj): + if obj.book_reference: + request = self.context.get("request") + author = obj.book_reference.author + first_ed = obj.book_reference.editions.first() if hasattr(obj.book_reference, "editions") else None + pub = getattr(obj.book_reference, "publisher", None) + if pub is None and first_ed: + pub = first_ed.publisher + if isinstance(pub, list): + pub = get_localized_text(pub, request) + year = getattr(obj.book_reference, "year_of_publication", None) + if not year and first_ed: + year = first_ed.year_of_publication + num_vols = getattr(obj.book_reference, "number_of_volumes", None) + if num_vols is None and first_ed: + num_vols = first_ed.number_of_volumes + return { + "id": obj.book_reference.id, + "title": get_localized_text(obj.book_reference.title, request), + "slug": obj.book_reference.slug, + "publisher": pub or "", + "year_of_publication": year or "", + "number_of_volumes": num_vols, + "has_editions": getattr(obj.book_reference, "has_editions", False), + "author": { + "id": author.id, + "name": get_localized_text(author.name, request), + "slug": author.slug + } if author else None + } + return None + + def get_edition_detail(self, obj): + if obj.edition: + request = self.context.get("request") + pub = obj.edition.publisher + if isinstance(pub, list): + pub = get_localized_text(pub, request) + return { + "id": obj.edition.id, + "edition_number": obj.edition.edition_number, + "publisher": pub, + "year_of_publication": obj.edition.year_of_publication, + "number_of_volumes": obj.edition.number_of_volumes, + } + return None + + def get_book_volume_detail(self, obj): + if obj.book_volume: + return { + "id": obj.book_volume.id, + "title": obj.book_volume.title, + } + return None + + def get_images(self, obj): + request = self.context.get("request") + return [ + { + "id": img.id, + "image": absolute_https_url(img.image.url, request) if request and img.image else (img.image.url if img.image else None), + "thumbnail": absolute_https_url(img.image.url, request) if request and img.image else (img.image.url if img.image else None), + "priority": img.priority, + } + for img in obj.images.all() + ] + + def to_internal_value(self, data): + import json + data = safe_copy_data(data) + book_ref = data.get("book_reference") + if book_ref in ("", "none", "null"): + data["book_reference"] = None + + edition = data.get("edition") + if edition in ("", "none", "null"): + data["edition"] = None + + book_vol = data.get("book_volume") + if book_vol in ("", "none", "null"): + data["book_volume"] = None + + delete_ids = data.get("delete_image_ids") + if delete_ids: + if isinstance(delete_ids, str): + try: + data["delete_image_ids"] = json.loads(delete_ids) + except ValueError: + data["delete_image_ids"] = [int(x) for x in delete_ids.split(",") if x.strip().isdigit()] + elif isinstance(delete_ids, list): + parsed = [] + for x in delete_ids: + if isinstance(x, str) and x.strip().isdigit(): + parsed.append(int(x)) + elif isinstance(x, int): + parsed.append(x) + data["delete_image_ids"] = parsed + return super().to_internal_value(data) + + def create(self, validated_data): + delete_image_ids = validated_data.pop("delete_image_ids", []) + instance = super().create(validated_data) + self._handle_images(instance, delete_image_ids) + return instance + + def update(self, instance, validated_data): + delete_image_ids = validated_data.pop("delete_image_ids", []) + instance = super().update(instance, validated_data) + self._handle_images(instance, delete_image_ids) + return instance + + def _handle_images(self, instance, delete_image_ids): + request = self.context.get("request") + if delete_image_ids: + instance.images.filter(id__in=delete_image_ids).delete() + + if request: + new_images = request.FILES.getlist("uploaded_images") or request.FILES.getlist("uploaded_images[]") or request.FILES.getlist("images") + for img in new_images: + InterpretationReferenceImage.objects.create(reference=instance, image=img) + + class AdminHadisInterpretationSerializer(serializers.ModelSerializer): category_detail = AdminHadisCategoryBriefSerializer(source="category", read_only=True) + references = AdminInterpretationReferenceSerializer(many=True, read_only=True) # Writable relation fields book_reference_id = serializers.PrimaryKeyRelatedField( @@ -1111,10 +1265,11 @@ class AdminHadisInterpretationSerializer(serializers.ModelSerializer): "book_reference_id", "address", "delete_image_ids", + "references", "created_at", "updated_at", ] - read_only_fields = ["id", "created_at", "updated_at"] + read_only_fields = ["id", "references", "created_at", "updated_at"] def to_internal_value(self, data): import json @@ -1168,6 +1323,7 @@ class AdminHadisInterpretationSerializer(serializers.ModelSerializer): } for img in images ] + ret["references"] = AdminInterpretationReferenceSerializer(instance.references.all(), many=True, context=self.context).data return ret def create(self, validated_data): diff --git a/apps/hadis/urls.py b/apps/hadis/urls.py index f26e2db..ca2c283 100644 --- a/apps/hadis/urls.py +++ b/apps/hadis/urls.py @@ -14,7 +14,7 @@ from .views.reference_v2 import BookReferenceV2DetailView, BookReferenceV2SyncVi from .views.version import ContentReleaseSyncView, HadisDatabaseVersionView from .views.info import HadisInfoView from .views_admin import ( - AdminHadisCategoryViewSet, AdminHadisViewSet, AdminHadisCorrectionViewSet, AdminCorrectionReferenceViewSet, AdminHadisReferenceViewSet, AdminHadisInterpretationViewSet, AdminHadisStatusViewSet, AdminHadisTagViewSet, AdminHadisCollectionViewSet, + AdminHadisCategoryViewSet, AdminHadisViewSet, AdminHadisCorrectionViewSet, AdminCorrectionReferenceViewSet, AdminHadisReferenceViewSet, AdminInterpretationReferenceViewSet, AdminHadisInterpretationViewSet, AdminHadisStatusViewSet, AdminHadisTagViewSet, AdminHadisCollectionViewSet, AdminBookSubjectAreaViewSet, AdminBookTypeViewSet, AdminBookTagViewSet, AdminBookAuthorViewSet, AdminBookAttributeViewSet, AdminBookReferenceImageViewSet, AdminHadisReferenceImageViewSet, AdminBookReferenceDocumentViewSet, AdminBookReferenceViewSet, AdminTransmitterViewSet, AdminTransmitterReliabilityViewSet, DovodiBookSubjectAreaViewSet, DovodiBookTypeViewSet, DovodiNarratorLayerViewSet, DovodiTransmitterReliabilityViewSet, DovodiOpinionStatusViewSet, @@ -38,6 +38,7 @@ admin_router.register(r'corrections', AdminHadisCorrectionViewSet, basename='adm admin_router.register(r'correction-references', AdminCorrectionReferenceViewSet, basename='admin-correction-references') admin_router.register(r'hadis-references', AdminHadisReferenceViewSet, basename='admin-hadis-references') admin_router.register(r'interpretations', AdminHadisInterpretationViewSet, basename='admin-hadis-interpretations') +admin_router.register(r'interpretation-references', AdminInterpretationReferenceViewSet, basename='admin-interpretation-references') admin_router.register(r'statuses', AdminHadisStatusViewSet, basename='admin-hadis-statuses') admin_router.register(r'tags', AdminHadisTagViewSet, basename='admin-hadis-tags') admin_router.register(r'collections', AdminHadisCollectionViewSet, basename='admin-hadis-collections') diff --git a/apps/hadis/views_admin.py b/apps/hadis/views_admin.py index 79edc18..3fe9cc0 100644 --- a/apps/hadis/views_admin.py +++ b/apps/hadis/views_admin.py @@ -13,7 +13,7 @@ from .models import ( Hadis, HadisCategory, HadisCollection, HadisInCollection, HadisCorrection, HadisInterpretation, HadisStatus, HadisTag, HadisSect, HadisTransmitter, HadisReference, ReferenceImage, BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAttribute, BookSubjectArea, BookType, BookTag, BookEdition, BookVolume, Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, TransmitterOpinion, TransmitterOriginalText, - CorrectionReference, + CorrectionReference, InterpretationReference, ContentRelease, CategoryQuranVerse, ) @@ -24,6 +24,7 @@ from .serializers.serializers_admin import ( AdminHadisCategoryArchiveDetailSerializer, AdminHadisCorrectionSerializer, AdminCorrectionReferenceSerializer, + AdminInterpretationReferenceSerializer, AdminHadisReferenceSerializer, AdminHadisDetailSerializer, AdminHadisInterpretationSerializer, @@ -484,6 +485,23 @@ class AdminHadisReferenceViewSet(ModelViewSet): return qs.order_by("id") +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") + 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 AdminHadisInterpretationViewSet(ModelViewSet): serializer_class = AdminHadisInterpretationSerializer permission_classes = [IsAuthenticated, IsSuperAdmin]