diff --git a/apps/hadis/docs.py b/apps/hadis/docs.py index 251508e..1f6d70a 100644 --- a/apps/hadis/docs.py +++ b/apps/hadis/docs.py @@ -2011,4 +2011,143 @@ categories_tree_by_sect_source_swagger = swagger_auto_schema( description="Internal server error occurred while processing the request" ) } +) + + +# Swagger documentation for HadisMainListView +hadis_main_list_swagger = swagger_auto_schema( + operation_description=""" + Retrieve a comprehensive list of all Hadis (traditions) with advanced search and filtering capabilities. + + **Key Features:** + - Returns all active hadis entries with their complete information + - Supports full-text search across titles, narrator titles, Arabic text, and translations + - Filter by hadis status and category titles + - Includes complete category and status metadata in the response + - Translations are automatically provided based on the Accept-Language header + + **Search Functionality:** + - Search parameter performs case-insensitive search across: + - Hadis titles (localized) + - Narrator titles (localized) + - Arabic text content + - Translation text (localized) + + **Filtering Options:** + - `status`: Filter by hadis status title (e.g., "authentic", "weak") + - `category`: Filter by category title (e.g., "prayer", "faith") + + **Response Structure:** + - `count`: Total number of hadis returned + - `categories`: List of all available categories with localized titles + - `statuses`: List of all available hadis statuses with localized titles + - `results`: Array of hadis objects with full details including individual status + """, + operation_summary="List All Hadis with Search & Filters", + tags=['Hadis'], + manual_parameters=[ + openapi.Parameter( + 'search', + openapi.IN_QUERY, + description="Search term to filter hadis. Searches across titles, narrator titles, Arabic text, and translations. Case-insensitive partial matching.", + type=openapi.TYPE_STRING, + required=False, + example="prayer" + ), + openapi.Parameter( + 'status', + openapi.IN_QUERY, + description="Filter hadis by status title. Case-insensitive partial matching.", + type=openapi.TYPE_STRING, + required=False, + example="authentic" + ), + openapi.Parameter( + 'category', + openapi.IN_QUERY, + description="Filter hadis by category title. Case-insensitive partial matching.", + type=openapi.TYPE_STRING, + required=False, + example="prayer" + ), + openapi.Parameter( + 'Accept-Language', + openapi.IN_HEADER, + description="Language code for localized content. Supported codes: 'en' (English), 'fa' (Persian), 'ar' (Arabic), 'ur' (Urdu), 'ru' (Russian). Defaults to 'en' if not specified.", + type=openapi.TYPE_STRING, + required=False, + default='en', + enum=['en', 'fa', 'ar', 'ur', 'ru'] + ) + ], + responses={ + status.HTTP_200_OK: openapi.Response( + description="Successfully retrieved list of hadis with search results, categories, and statuses", + examples={ + "application/json": { + "count": 150, + "categories": [ + "Faith Fundamentals", + "Prayer Rites", + "Fasting Practices", + "Pilgrimage Guide" + ], + "statuses": [ + "Authentic (Sahih)", + "Good (Hasan)", + "Weak (Da'if)" + ], + "results": [ + { + "id": 1, + "number": 1, + "slug": "intention-hadith", + "title": "The Opening Hadith on Intention", + "title_narrator": "From Umar ibn al-Khattab", + "text": "إنما الأعمال بالنيات وإنما لكل امرئ ما نوى", + "translation": "Actions are but by intention, and every man shall have only what he intended", + "category": { + "id": 1, + "title": "Faith Fundamentals", + "slug": "faith-fundamentals", + "source_type": "hadith", + "sect_type": "sunni" + }, + "status": { + "id": 1, + "title": "Authentic (Sahih)", + "color": "green" + }, + "share_link": "http://example.com/hadis/1" + }, + { + "id": 2, + "number": 2, + "slug": "prayer-timing", + "title": "Prayer Timing Importance", + "title_narrator": "From Abdullah ibn Mas'ud", + "text": "الصلاة على وقتها فريضة", + "translation": "Prayer at its proper time is obligatory", + "category": { + "id": 2, + "title": "Prayer Rites", + "slug": "prayer-rites", + "source_type": "hadith", + "sect_type": "sunni" + }, + "status": { + "id": 1, + "title": "Authentic (Sahih)", + "color": "green" + }, + "share_link": "http://example.com/hadis/2" + } + ] + } + } + ), + status.HTTP_500_INTERNAL_SERVER_ERROR: openapi.Response( + description="Internal server error occurred while processing the request" + ) + } ) \ No newline at end of file diff --git a/apps/hadis/serializers/hadis.py b/apps/hadis/serializers/hadis.py index 6320931..e3b8735 100644 --- a/apps/hadis/serializers/hadis.py +++ b/apps/hadis/serializers/hadis.py @@ -143,16 +143,17 @@ class HadisSyncSerializer(serializers.ModelSerializer): class HadisListSerializer(serializers.ModelSerializer): """Serializer for Hadis list""" category = serializers.SerializerMethodField() + status = serializers.SerializerMethodField() translation = LocalizedField() title = LocalizedField() title_narrator = LocalizedField() title_narrator = LocalizedField() - + class Meta: model = Hadis fields = ['id', 'number', 'slug', 'title','title_narrator', 'text' , - 'translation','category','share_link'] - + 'translation','category','status','share_link'] + def get_category(self, obj): """Get category id and title""" if obj.category: @@ -172,6 +173,41 @@ class HadisListSerializer(serializers.ModelSerializer): 'sect_type':obj.category.sect.sect_type } return None + + def get_status(self, obj): + """Get status id and title""" + if obj.hadis_status: + request = self.context.get('request') + language_code = getattr(request, "LANGUAGE_CODE", None) if request else None + if not language_code: + from django.utils.translation import get_language + language_code = get_language() or 'en' + + title = None + if obj.hadis_status.title and isinstance(obj.hadis_status.title, list): + # 1) Exact match + for item in obj.hadis_status.title: + if isinstance(item, dict) and item.get("language_code") == language_code: + title = item.get("text") + break + + # 2) Fallback to English + if not title: + for item in obj.hadis_status.title: + if isinstance(item, dict) and item.get("language_code") == "en": + title = item.get("text") + break + + # 3) First available + if not title and obj.hadis_status.title and isinstance(obj.hadis_status.title[0], dict): + title = obj.hadis_status.title[0].get("text") + + return { + 'id': obj.hadis_status.id, + 'title': title, + 'color': obj.hadis_status.color + } + return None # def get_translation(self, obj): # """Get translation based on request language""" diff --git a/apps/hadis/urls.py b/apps/hadis/urls.py index c984ad4..a426225 100644 --- a/apps/hadis/urls.py +++ b/apps/hadis/urls.py @@ -1,6 +1,6 @@ from django.urls import path from .views.category import HadisCategorySectListView, HadisCategoryTreeView, CategoriesView, CategoriesBySectView, HadisCategorySelectBySectView, HadisCategorySelectBySectSourceView , HadisCategoryTreeNormalView -from .views.hadis import HadisCollectionListView, HadisListView, HadisBasicView, HadisDetailView, HadisSyncView, HadisTransmittersView, HadisCorrectionsView +from .views.hadis import HadisCollectionListView, HadisListView, HadisBasicView, HadisDetailView, HadisSyncView, HadisTransmittersView, HadisCorrectionsView,HadisMainListView from .views.transmitter import TransmitterView ,TransmitterDetailView, TransmitterSyncView,TransmitterOpinionView, TransmitterOriginalTextView from .views.reference import BookDetailView, BookReferencesView, BookReferenceSyncView, BookAttributeView from .views.info import HadisInfoView @@ -16,6 +16,7 @@ urlpatterns = [ path('sync/references/', BookReferenceSyncView.as_view(), name='reference-sync'), path('info/', HadisInfoView.as_view(), name='hadis-info'), path('category//', HadisListView.as_view(), name='hadis-list'), + path('arguments/',HadisMainListView.as_view(), name='hadis-main-list'), path('/', HadisBasicView.as_view(), name='hadis-basic'), path('/detail/', HadisDetailView.as_view(), name='hadis-detail'), path('/transmitters/', HadisTransmittersView.as_view(), name='hadis-transmitters'), diff --git a/apps/hadis/views/hadis.py b/apps/hadis/views/hadis.py index 651aa20..e9b3216 100644 --- a/apps/hadis/views/hadis.py +++ b/apps/hadis/views/hadis.py @@ -4,10 +4,11 @@ from utils.pagination import NoPagination 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 HadisCategory, Hadis, HadisCollection,HadisTransmitter , HadisCorrection from ..serializers import HadisListSerializer, HadisBasicSerializer, HadisDetailSerializer, HadisCollectionListSerializer, HadisSyncSerializer,HadisCorrectionSerializer,HadisTransmitterListSerializer -from ..docs import hadis_list_swagger, hadis_detail_swagger, hadis_collections_swagger, hadis_sync_swagger, hadis_transmitters_swagger, hadis_corrections_swagger, hadis_basic_swagger +from ..docs import 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 class HadisCollectionListView(ListAPIView): @@ -83,6 +84,101 @@ class HadisListView(ListAPIView): ).select_related('category') +class HadisMainListView(ListAPIView): + """ + API view to list Hadis by category_id + """ + serializer_class = HadisListSerializer + + @hadis_main_list_swagger + def get(self, request, *args, **kwargs): + return self.list(request, *args, **kwargs) + + def get_queryset(self): + queryset = Hadis.objects.select_related('category', 'hadis_status') + + # Get search parameters + 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) + + # Apply search filter + if search_query: + queryset = self.apply_search_filter(queryset, search_query) + + # Apply status filter + if status_filter: + queryset = queryset.filter(hadis_status__title__icontains=status_filter) + + # Apply category filter + if category_filter: + queryset = queryset.filter(category__title__icontains=category_filter) + + return queryset + + def list(self, request, *args, **kwargs): + queryset = self.get_queryset() + serializer = self.get_serializer(queryset, many=True) + + # Get category titles + category_titles = self.get_category_titles(request) + + # Get status titles + status_titles = self.get_status_titles(request) + + response_data = { + 'count': queryset.count(), + 'categories': category_titles, + 'statuses': status_titles, + 'results': serializer.data + } + + return Response(response_data) + + def get_category_titles(self,request): + """Get list of category titles based on language""" + from ..models import HadisCategory + + categories = HadisCategory.objects.all() + category_titles = [] + + for category in categories: + title = get_localized_text(category.title,request) + category_titles.append(title) + + return category_titles + + def get_status_titles(self, request): + """Get list of status titles based on language""" + from ..models import HadisStatus + + statuses = HadisStatus.objects.all().order_by('order') + status_titles = [] + + for status in statuses: + title = get_localized_text(status.title,request) + status_titles.append(title) + return status_titles + + def apply_search_filter(self, queryset, search_query): + """ + Apply search filter across multiple fields including JSONFields. + Searches in: title, title_narrator, text, translation + """ + from django.db.models import Q + + # Basic search conditions + search_conditions = Q(text__icontains=search_query) + + # For JSONFields, search in the JSON string representation + # This will find matches in the "text" values within the JSON arrays + search_conditions |= Q(title__icontains=search_query) + search_conditions |= Q(title_narrator__icontains=search_query) + search_conditions |= Q(translation__icontains=search_query) + + return queryset.filter(search_conditions) + + class HadisBasicView(RetrieveAPIView): """ API view to retrieve basic Hadis information by hadis_slug