diff --git a/apps/hadis/serializers/category.py b/apps/hadis/serializers/category.py index 73bfd39..25171d5 100644 --- a/apps/hadis/serializers/category.py +++ b/apps/hadis/serializers/category.py @@ -36,7 +36,18 @@ def get_localized_text(json_list, request=None, fallback_lang="en", language_cod # Get target language if not provided if not language_code: - language_code = getattr(request, "LANGUAGE_CODE", None) if request else None + if request: + query_params = getattr(request, "query_params", None) or getattr(request, "GET", None) + if query_params: + language_code = query_params.get("language_code") or query_params.get("lang") + if not language_code: + language_code = getattr(request, "LANGUAGE_CODE", None) + if not language_code and hasattr(request, "headers"): + language_code = request.headers.get("Accept-Language") + elif not language_code and hasattr(request, "META"): + language_code = request.META.get("HTTP_ACCEPT_LANGUAGE") + if language_code: + language_code = language_code.split(",")[0].split(";")[0].split("-")[0].strip().lower() if not language_code: language_code = get_language() or fallback_lang diff --git a/apps/hadis/serializers/hadis.py b/apps/hadis/serializers/hadis.py index 1082497..0a56614 100644 --- a/apps/hadis/serializers/hadis.py +++ b/apps/hadis/serializers/hadis.py @@ -304,11 +304,21 @@ class HadisSyncSerializer(serializers.ModelSerializer): ).data return [] +class HadisTagSerializer(serializers.ModelSerializer): + """Serializer for HadisTag""" + title = LocalizedField() + + class Meta: + model = HadisTag + fields = ['id', 'title'] + + class HadisListSerializer(serializers.ModelSerializer): """Serializer for Hadis list""" category = serializers.SerializerMethodField() status = serializers.SerializerMethodField() bookmark = serializers.SerializerMethodField() + tags = HadisTagSerializer(many=True, read_only=True) translation = LocalizedField() title = LocalizedField() title_narrator = LocalizedField() @@ -321,7 +331,7 @@ class HadisListSerializer(serializers.ModelSerializer): model = Hadis fields = ['id', 'number', 'slug', 'title', 'title_narrator', 'text', 'translation', 'category', 'status', 'bookmark', 'share_link', - 'address', 'links', 'reference_images', 'images'] + 'address', 'links', 'reference_images', 'images', 'tags'] def get_address(self, obj): formatted = format_address_output(obj.address) @@ -420,15 +430,6 @@ class HadisStatusSerializer(serializers.ModelSerializer): fields = ['id', 'title', 'color', 'main_color_code', 'description'] -class HadisTagSerializer(serializers.ModelSerializer): - """Serializer for HadisTag""" - title = LocalizedField() - - class Meta: - model = HadisTag - fields = ['id', 'title'] - - class TransmitterSerializer(serializers.ModelSerializer): """Serializer for Transmitters""" full_name = LocalizedField() diff --git a/apps/hadis/urls.py b/apps/hadis/urls.py index 8a2017b..a32def2 100644 --- a/apps/hadis/urls.py +++ b/apps/hadis/urls.py @@ -3,7 +3,7 @@ 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, CategoryHadisCorrectionsView, HadisMainListView, HadisFiltersView, HadisLayersView,PinnedHadisCollectionListView, HadisFiltersSyncAPIView, - HadisCorrectionDetailView, HadisInterpretationDetailView, HadisInterpretsListView, HadisSourceDetailsView + HadisCorrectionDetailView, HadisInterpretationDetailView, HadisInterpretsListView, HadisSourceDetailsView, HadisTagListView ) from .views.transmitter import ( TransmitterView ,TransmitterDetailView, TransmitterSyncView,TransmitterOpinionView, TransmitterOriginalTextView, TransmitterFiltersView, @@ -91,6 +91,7 @@ urlpatterns = [ path('sync/database/', HadisDatabaseVersionView.as_view(), name='hadis-database-sync'), path('sync/filters/', cached_view(HadisFiltersSyncAPIView.as_view()), name='hadis-filters-sync'), path('info/', cached_view(HadisInfoView.as_view()), name='hadis-info'), + path('tags/', cached_view(HadisTagListView.as_view()), name='hadis-tag-list'), # Category paths (more specific first) path('categories/tree/', cached_view(HadisCategoryTreeNormalView.as_view()), name='hadis-category-tree-normal'), diff --git a/apps/hadis/views/hadis.py b/apps/hadis/views/hadis.py index 61f70bc..a350362 100644 --- a/apps/hadis/views/hadis.py +++ b/apps/hadis/views/hadis.py @@ -10,15 +10,25 @@ from rest_framework.response import Response from django.db.models import Count from django.db.models import Prefetch from ..serializers.category import get_localized_text -from ..models import Transmitters, HadisCategory, Hadis, HadisCollection,HadisTransmitter , HadisCorrection ,HadisReference, HadisStatus ,ReferenceImage, CorrectionReference, HadisInterpretation, InterpretationReference -from ..serializers import HadisListSerializer, HadisBasicSerializer, HadisDetailSerializer, HadisCollectionListSerializer, HadisSyncSerializer,HadisCorrectionSerializer,HadisTransmitterListSerializer , SimpleCategory, NarratorLayerSerializer , PinnedHadisCollectionSerializer, HadisSourceDetailsSerializer +from ..models import Transmitters, HadisCategory, Hadis, HadisCollection,HadisTransmitter , HadisCorrection ,HadisReference, HadisStatus ,ReferenceImage, CorrectionReference, HadisInterpretation, InterpretationReference, HadisTag +from ..serializers import HadisListSerializer, HadisBasicSerializer, HadisDetailSerializer, HadisCollectionListSerializer, HadisSyncSerializer,HadisCorrectionSerializer,HadisTransmitterListSerializer , SimpleCategory, NarratorLayerSerializer , PinnedHadisCollectionSerializer, HadisSourceDetailsSerializer, HadisTagSerializer from ..docs import arguments_filters_swagger ,hadis_list_swagger, hadis_detail_swagger, hadis_collections_swagger, hadis_sync_swagger, hadis_transmitters_swagger, hadis_corrections_swagger, hadis_basic_swagger, hadis_main_list_swagger, hadis_layers_swagger from django.db.models import Q -from ..serializers.category import get_localized_text from rest_framework import status from utils.mixins import CanonicalSlugViewSetMixin, Gone410ViewMixin +class HadisTagListView(ListAPIView): + """ + API view to list active Hadis Tags + """ + serializer_class = HadisTagSerializer + pagination_class = NoPagination + permission_classes = [AllowAny] + + def get_queryset(self): + return HadisTag.objects.filter(status=True).order_by('id') + class PinnedHadisCollectionListView(ListAPIView): """ API view to list pinned hadis collections with metadata info @@ -293,6 +303,14 @@ class HadisListView(ListAPIView): for status in HadisStatus.objects.all().order_by('order') ] + tags_list = [ + { + "id": tag.id, + "title": get_localized_text(tag.title, request=request, language_code=lang) or "", + } + for tag in HadisTag.objects.filter(status=True).order_by('id') + ] + # 6. Inject it into the response data if isinstance(response.data, dict): ordered_data = {} @@ -303,8 +321,9 @@ class HadisListView(ListAPIView): ordered_data['current_category'] = category_data ordered_data['quran_data'] = quran_data - # 👇 تزریق لیست استاتوس‌ها قبل از نتایج + # 👇 تزریق لیست استاتوس‌ها و تگ‌ها قبل از نتایج ordered_data['statuses'] = statuses_list + ordered_data['tags'] = tags_list if 'results' in response.data: ordered_data['results'] = response.data['results'] @@ -323,6 +342,7 @@ class HadisListView(ListAPIView): ).annotate( layer_count=Count('transmitters__narrator_layer', distinct=True) ).select_related('category').prefetch_related( + 'tags', Prefetch( 'references', queryset=HadisReference.objects.prefetch_related( @@ -338,6 +358,7 @@ class HadisListView(ListAPIView): search_conditions |= Q(title__icontains=search_query) search_conditions |= Q(title_narrator__icontains=search_query) search_conditions |= Q(translation__icontains=search_query) + search_conditions |= Q(tags__title__icontains=search_query) queryset = queryset.filter(search_conditions) # 👇 2. Apply Status Filter (Supports multiple comma-separated slugs) @@ -368,6 +389,20 @@ class HadisListView(ListAPIView): author_q |= Q(references__book_reference__author_id__in=numeric_ids) queryset = queryset.filter(author_q) + # 👇 6. Apply Tag Filter (Supports comma-separated IDs or titles) + tag_filter = self.request.query_params.get('tag', None) or self.request.query_params.get('tags', None) + if tag_filter: + tag_items = [t.strip() for t in tag_filter.split(',') if t.strip()] + numeric_tag_ids = [int(t) for t in tag_items if t.isdigit()] + non_numeric_tags = [t for t in tag_items if not t.isdigit()] + tag_q = Q() + if numeric_tag_ids: + tag_q |= Q(tags__id__in=numeric_tag_ids) + for t_val in non_numeric_tags: + tag_q |= Q(tags__title__icontains=t_val) + if tag_q: + queryset = queryset.filter(tag_q) + # Filter by bookmarks if provided is_bookmark = self.request.query_params.get('is_bookmark', '').lower() if is_bookmark == 'true' and self.request.user.is_authenticated: @@ -431,6 +466,7 @@ class HadisMainListView(ListAPIView): search_query = self.request.query_params.get('search', None) status_filter = self.request.query_params.get('status', None) category_filter = self.request.query_params.get('category', None) + tag_filter = self.request.query_params.get('tag', None) or self.request.query_params.get('tags', None) # Apply search filter if search_query: @@ -444,6 +480,19 @@ class HadisMainListView(ListAPIView): if category_filter: queryset = queryset.filter(category__title__icontains=category_filter) + # Apply tag filter + if tag_filter: + tag_items = [t.strip() for t in tag_filter.split(',') if t.strip()] + numeric_tag_ids = [int(t) for t in tag_items if t.isdigit()] + non_numeric_tags = [t for t in tag_items if not t.isdigit()] + tag_q = Q() + if numeric_tag_ids: + tag_q |= Q(tags__id__in=numeric_tag_ids) + for t_val in non_numeric_tags: + tag_q |= Q(tags__title__icontains=t_val) + if tag_q: + queryset = queryset.filter(tag_q) + # Filter by bookmarks if provided is_bookmark = self.request.query_params.get('is_bookmark', '').lower() if is_bookmark == 'true' and self.request.user.is_authenticated: @@ -528,6 +577,7 @@ class HadisMainListView(ListAPIView): search_conditions |= Q(title__icontains=search_query) search_conditions |= Q(title_narrator__icontains=search_query) search_conditions |= Q(translation__icontains=search_query) + search_conditions |= Q(tags__title__icontains=search_query) return queryset.filter(search_conditions)