From 021085b866079e1e530c0af90c9c2ea2bf1e94b8 Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Tue, 1 Sep 2026 10:53:40 +0330 Subject: [PATCH] feat(hadis): add bulk delete endpoint for admin hadis reference images --- apps/hadis/serializers/serializers_admin.py | 25 ++++++- apps/hadis/urls.py | 3 +- apps/hadis/views_admin.py | 83 ++++++++++++++++++++- 3 files changed, 108 insertions(+), 3 deletions(-) diff --git a/apps/hadis/serializers/serializers_admin.py b/apps/hadis/serializers/serializers_admin.py index 6e3e2c9..a1b6043 100644 --- a/apps/hadis/serializers/serializers_admin.py +++ b/apps/hadis/serializers/serializers_admin.py @@ -6,7 +6,7 @@ from django.db.models import Exists, OuterRef from utils.image_compression import maybe_compress_uploaded_file from utils import absolute_https_url from ..models import ( - Hadis, HadisCategory, HadisStatus, HadisTag, HadisCorrection, HadisInterpretation, HadisCollection, HadisSect, HadisInCollection, HadisTransmitter, HadisReference, + Hadis, HadisCategory, HadisStatus, HadisTag, HadisCorrection, HadisInterpretation, HadisCollection, HadisSect, HadisInCollection, HadisTransmitter, HadisReference, ReferenceImage, BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAttribute, BookSubjectArea, BookType, BookTag, BookEdition, BookVolume, BookResearcher, BookEditor, Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, TransmitterOpinion, TransmitterOriginalText, CorrectionReference, CorrectionReferenceImage, InterpretationReference, InterpretationReferenceImage, @@ -325,6 +325,20 @@ class AdminHadisDetailSerializer(serializers.ModelSerializer): } else: ret["book_reference_detail"] = None + + ref_images = [] + for r in refs: + for img in r.images.all().order_by("priority", "id"): + url = absolute_https_url(img.thumbnail.url, self.context.get("request")) if img.thumbnail else None + ref_images.append({ + "id": img.id, + "reference_id": r.id, + "hadis_id": instance.id, + "thumbnail": url, + "image": url, + "priority": img.priority, + }) + ret["reference_images"] = ref_images return ret def get_bookmark_count(self, obj): @@ -1214,6 +1228,15 @@ class AdminBookReferenceImageSerializer(serializers.ModelSerializer): return obj.book_volume.title if getattr(obj, 'book_volume', None) else "" +class AdminHadisReferenceImageSerializer(serializers.ModelSerializer): + thumbnail = AbsoluteImageField(required=True) + hadis_id = serializers.IntegerField(source="reference.hadis_id", read_only=True) + + class Meta: + model = ReferenceImage + fields = ["id", "reference", "hadis_id", "thumbnail", "priority"] + + class AdminBookReferenceDocumentSerializer(serializers.ModelSerializer): file = AbsoluteFileField(required=True) volume = serializers.SerializerMethodField() diff --git a/apps/hadis/urls.py b/apps/hadis/urls.py index 897abea..3ee645b 100644 --- a/apps/hadis/urls.py +++ b/apps/hadis/urls.py @@ -15,7 +15,7 @@ from .views.version import ContentReleaseSyncView from .views.info import HadisInfoView from .views_admin import ( AdminHadisCategoryViewSet, AdminHadisViewSet, AdminHadisCorrectionViewSet, AdminHadisInterpretationViewSet, AdminHadisStatusViewSet, AdminHadisTagViewSet, AdminHadisCollectionViewSet, - AdminBookSubjectAreaViewSet, AdminBookTypeViewSet, AdminBookTagViewSet, AdminBookAuthorViewSet, AdminBookAttributeViewSet, AdminBookReferenceImageViewSet, AdminBookReferenceDocumentViewSet, AdminBookReferenceViewSet, + AdminBookSubjectAreaViewSet, AdminBookTypeViewSet, AdminBookTagViewSet, AdminBookAuthorViewSet, AdminBookAttributeViewSet, AdminBookReferenceImageViewSet, AdminHadisReferenceImageViewSet, AdminBookReferenceDocumentViewSet, AdminBookReferenceViewSet, AdminTransmitterViewSet, AdminTransmitterReliabilityViewSet, DovodiBookSubjectAreaViewSet, DovodiBookTypeViewSet, DovodiNarratorLayerViewSet, DovodiTransmitterReliabilityViewSet, DovodiOpinionStatusViewSet, DovodiHadisSectViewSet, DovodiHadisCategoryViewSet, AdminCategoryQuranVerseViewSet, AdminHadisTransmitterViewSet, @@ -50,6 +50,7 @@ admin_router.register(r'dovodi-book-types', DovodiBookTypeViewSet, basename='dov admin_router.register(r'book-authors', AdminBookAuthorViewSet, basename='admin-book-authors') admin_router.register(r'book-attributes', AdminBookAttributeViewSet, basename='admin-book-attributes') admin_router.register(r'book-images', AdminBookReferenceImageViewSet, basename='admin-book-images') +admin_router.register(r'hadis-reference-images', AdminHadisReferenceImageViewSet, basename='admin-hadis-reference-images') admin_router.register(r'book-documents', AdminBookReferenceDocumentViewSet, basename='admin-book-documents') admin_router.register(r'transmitters', AdminTransmitterViewSet, basename='admin-transmitters') admin_router.register(r'transmitter-reliabilities', AdminTransmitterReliabilityViewSet, basename='admin-transmitter-reliabilities') diff --git a/apps/hadis/views_admin.py b/apps/hadis/views_admin.py index a9c6150..9fef2a7 100644 --- a/apps/hadis/views_admin.py +++ b/apps/hadis/views_admin.py @@ -10,7 +10,7 @@ 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, + 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, ContentRelease @@ -34,6 +34,7 @@ from .serializers.serializers_admin import ( AdminBookAuthorSerializer, AdminBookAttributeSerializer, AdminBookReferenceImageSerializer, + AdminHadisReferenceImageSerializer, AdminBookReferenceDocumentSerializer, AdminBookReferenceListSerializer, AdminBookReferenceDetailSerializer, @@ -587,6 +588,86 @@ class AdminBookReferenceImageViewSet(ModelViewSet): 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") + reference_id = request.data.get("reference_id") + 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]