diff --git a/apps/hadis/migrations/0014_hadiscorrection_priority_and_more.py b/apps/hadis/migrations/0014_hadiscorrection_priority_and_more.py new file mode 100644 index 0000000..2506239 --- /dev/null +++ b/apps/hadis/migrations/0014_hadiscorrection_priority_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.12 on 2026-06-13 08:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hadis', '0013_remove_correctionreferenceimage_pages_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='hadiscorrection', + name='priority', + field=models.IntegerField(default=0, verbose_name='priority'), + ), + migrations.AddField( + model_name='hadisinterpretation', + name='priority', + field=models.IntegerField(default=0, verbose_name='priority'), + ), + ] diff --git a/apps/hadis/models/hadis.py b/apps/hadis/models/hadis.py index 0d694d5..03fb933 100644 --- a/apps/hadis/models/hadis.py +++ b/apps/hadis/models/hadis.py @@ -496,6 +496,7 @@ class HadisCorrection(models.Model): legacy_id = models.CharField(max_length=255, unique=True, null=True, blank=True, db_index=True) text = models.TextField(verbose_name=_('Original Text'), default="", blank=True) translation = models.JSONField(verbose_name=_("translation"), default=list) + priority = models.IntegerField(default=0, verbose_name=_('priority')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("created at")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("updated at")) share_link = models.CharField(max_length=255, verbose_name=_('share link'), null=True, blank=True) @@ -572,6 +573,7 @@ class HadisInterpretation(models.Model): title = models.JSONField(default=list, verbose_name=_('Title')) text = models.TextField(verbose_name=_('Original Text')) translation = models.JSONField(verbose_name=_('Translation'), default=list) + priority = models.IntegerField(default=0, verbose_name=_('priority')) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/apps/hadis/serializers/serializers_admin.py b/apps/hadis/serializers/serializers_admin.py new file mode 100644 index 0000000..5be3802 --- /dev/null +++ b/apps/hadis/serializers/serializers_admin.py @@ -0,0 +1,165 @@ +from rest_framework import serializers +from ..models import Hadis, HadisCategory, HadisStatus, HadisTag, HadisCorrection, HadisInterpretation, HadisSect +from .category import LocalizedField + + +class AdminHadisCategorySerializer(serializers.ModelSerializer): + sect_type = serializers.CharField(source='sect.sect_type', read_only=True) + title = LocalizedField() + description = LocalizedField() + + class Meta: + model = HadisCategory + fields = [ + "id", + "title", + "slug", + "source_type", + "order", + "sect", + "sect_type", + "description", + ] + + +class AdminHadisStatusSerializer(serializers.ModelSerializer): + title = LocalizedField() + description = LocalizedField() + + class Meta: + model = HadisStatus + fields = ["id", "title", "slug", "color", "order", "description"] + + +class AdminHadisTagSerializer(serializers.ModelSerializer): + title = LocalizedField() + + class Meta: + model = HadisTag + fields = ["id", "title", "status"] + + +class AdminHadisListSerializer(serializers.ModelSerializer): + category_detail = AdminHadisCategorySerializer(source="category", read_only=True) + hadis_status_detail = AdminHadisStatusSerializer(source="hadis_status", read_only=True) + title = LocalizedField() + title_narrator = LocalizedField() + translation = LocalizedField() + + class Meta: + model = Hadis + fields = [ + "id", + "number", + "slug", + "title", + "title_narrator", + "text", + "translation", + "status", + "category", + "category_detail", + "hadis_status", + "hadis_status_detail", + "created_at", + "updated_at", + ] + read_only_fields = ["id", "slug", "created_at", "updated_at"] + + +class AdminHadisDetailSerializer(serializers.ModelSerializer): + category_detail = AdminHadisCategorySerializer(source="category", read_only=True) + hadis_status_detail = AdminHadisStatusSerializer(source="hadis_status", read_only=True) + tags_detail = AdminHadisTagSerializer(source="tags", many=True, read_only=True) + + category = serializers.PrimaryKeyRelatedField( + queryset=HadisCategory.objects.all(), + required=False, + allow_null=True, + ) + hadis_status = serializers.PrimaryKeyRelatedField( + queryset=HadisStatus.objects.all(), + required=False, + allow_null=True, + ) + tags = serializers.PrimaryKeyRelatedField( + queryset=HadisTag.objects.all(), + many=True, + required=False, + ) + + class Meta: + model = Hadis + fields = [ + "id", + "number", + "slug", + "category", + "category_detail", + "title_narrator", + "title", + "description", + "text", + "translation", + "status", + "hadis_status", + "hadis_status_detail", + "hadis_status_text", + "address", + "links", + "tags", + "tags_detail", + "share_link", + "explanation", + "created_at", + "updated_at", + "embedded_in", + ] + read_only_fields = ["id", "slug", "share_link", "created_at", "updated_at"] + + def create(self, validated_data): + tags = validated_data.pop("tags", []) + hadis = super().create(validated_data) + if tags: + hadis.tags.set(tags) + return hadis + + def update(self, instance, validated_data): + tags = validated_data.pop("tags", None) + hadis = super().update(instance, validated_data) + if tags is not None: + hadis.tags.set(tags) + return hadis + + +class AdminHadisCorrectionSerializer(serializers.ModelSerializer): + class Meta: + model = HadisCorrection + fields = [ + "id", + "hadis", + "title", + "slug", + "text", + "translation", + "priority", + "created_at", + "updated_at", + ] + read_only_fields = ["id", "slug", "created_at", "updated_at"] + + +class AdminHadisInterpretationSerializer(serializers.ModelSerializer): + class Meta: + model = HadisInterpretation + fields = [ + "id", + "category", + "title", + "text", + "translation", + "priority", + "created_at", + "updated_at", + ] + read_only_fields = ["id", "created_at", "updated_at"] diff --git a/apps/hadis/urls.py b/apps/hadis/urls.py index 9746cbe..c6364a1 100644 --- a/apps/hadis/urls.py +++ b/apps/hadis/urls.py @@ -1,10 +1,12 @@ -from django.urls import path +from django.urls import path, include +from rest_framework.routers import SimpleRouter from .views.category import HadisCategorySectListView, HadisCategoryTreeView, CategoriesView, CategoriesBySectView, HadisCategorySelectBySectView, HadisCategorySelectBySectSourceView , HadisCategoryTreeNormalView ,test_deploy,debug_headers,HadisCategoryXMindView from .views.hadis import HadisCollectionListView, HadisListView, HadisBasicView, HadisDetailView, HadisSyncView, HadisTransmittersView, HadisCorrectionsView,HadisMainListView, HadisFiltersView, HadisLayersView,PinnedHadisCollectionListView, HadisFiltersSyncAPIView from .views.transmitter import TransmitterView ,TransmitterDetailView, TransmitterSyncView,TransmitterOpinionView, TransmitterOriginalTextView, TransmitterFiltersView from .views.reference import BookDetailView, BookReferencesView, BookReferenceSyncView, BookAttributeView from .views.version import ContentReleaseSyncView from .views.info import HadisInfoView +from .views_admin import AdminHadisCategoryViewSet, AdminHadisViewSet, AdminHadisCorrectionViewSet, AdminHadisInterpretationViewSet, AdminHadisStatusViewSet, AdminHadisTagViewSet from django.views.decorators.cache import cache_page from django.views.decorators.vary import vary_on_headers @@ -12,8 +14,18 @@ from django.views.decorators.vary import vary_on_headers def cached_view(view_func): return cache_page(60*60*2,key_prefix='hadis_api')(vary_on_headers('Accept-Language')(view_func)) +admin_router = SimpleRouter() +admin_router.register(r'hadises', AdminHadisViewSet, basename='admin-hadises') +admin_router.register(r'categories', AdminHadisCategoryViewSet, basename='admin-hadis-categories') +admin_router.register(r'corrections', AdminHadisCorrectionViewSet, basename='admin-hadis-corrections') +admin_router.register(r'interpretations', AdminHadisInterpretationViewSet, basename='admin-hadis-interpretations') +admin_router.register(r'statuses', AdminHadisStatusViewSet, basename='admin-hadis-statuses') +admin_router.register(r'tags', AdminHadisTagViewSet, basename='admin-hadis-tags') urlpatterns = [ + # Admin endpoints + path('admin/', include(admin_router.urls)), + # Most specific first (with parameters) path('pinned-collections/', PinnedHadisCollectionListView.as_view(), name='pinned-hadis-collection-list'), path('collections/', HadisCollectionListView.as_view(), name='hadis-collection-list'), diff --git a/apps/hadis/views_admin.py b/apps/hadis/views_admin.py new file mode 100644 index 0000000..a1ae223 --- /dev/null +++ b/apps/hadis/views_admin.py @@ -0,0 +1,140 @@ +from django.db.models import Q +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 + +from .models import Hadis, HadisCategory, HadisCorrection, HadisInterpretation, HadisStatus, HadisTag +from .serializers.serializers_admin import ( + AdminHadisCategorySerializer, + AdminHadisCorrectionSerializer, + AdminHadisDetailSerializer, + AdminHadisInterpretationSerializer, + AdminHadisListSerializer, + AdminHadisStatusSerializer, + AdminHadisTagSerializer, +) + + +class AdminHadisCategoryViewSet(ReadOnlyModelViewSet): + serializer_class = AdminHadisCategorySerializer + permission_classes = [IsAuthenticated, IsSuperAdmin] + authentication_classes = [TokenAuthentication] + pagination_class = None + + def get_queryset(self): + return HadisCategory.objects.all().order_by("order", "id") + + +class AdminHadisStatusViewSet(ReadOnlyModelViewSet): + serializer_class = AdminHadisStatusSerializer + permission_classes = [IsAuthenticated, IsSuperAdmin] + authentication_classes = [TokenAuthentication] + pagination_class = None + + def get_queryset(self): + return HadisStatus.objects.all().order_by("order", "id") + + +class AdminHadisTagViewSet(ReadOnlyModelViewSet): + serializer_class = AdminHadisTagSerializer + permission_classes = [IsAuthenticated, IsSuperAdmin] + authentication_classes = [TokenAuthentication] + pagination_class = None + + def get_queryset(self): + return HadisTag.objects.all().order_by("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": + return AdminHadisListSerializer + return AdminHadisDetailSerializer + + def get_queryset(self): + queryset = Hadis.objects.all().select_related("category", "hadis_status") + + 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(category_id=category_id) + + status_filter = self.request.query_params.get("status") + if status_filter in {"true", "false"}: + queryset = queryset.filter(status=status_filter == "true") + + return queryset.distinct().order_by("-id") + + +class AdminHadisCorrectionViewSet(ModelViewSet): + serializer_class = AdminHadisCorrectionSerializer + permission_classes = [IsAuthenticated, IsSuperAdmin] + authentication_classes = [TokenAuthentication] + parser_classes = (JSONParser, MultiPartParser, FormParser) + + def get_queryset(self): + queryset = HadisCorrection.objects.all() + hadis_id = self.request.query_params.get("hadis_id") + if hadis_id: + queryset = queryset.filter(hadis_id=hadis_id) + return queryset.order_by("priority", "created_at") + + @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 AdminHadisInterpretationViewSet(ModelViewSet): + serializer_class = AdminHadisInterpretationSerializer + permission_classes = [IsAuthenticated, IsSuperAdmin] + authentication_classes = [TokenAuthentication] + parser_classes = (JSONParser, MultiPartParser, FormParser) + + def get_queryset(self): + queryset = HadisInterpretation.objects.all() + category_id = self.request.query_params.get("category_id") + if category_id: + queryset = queryset.filter(category_id=category_id) + return queryset.order_by("priority", "created_at") + + @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"})