From 1c1ca88422e19dd2e6fad1156ee4166f8a1e071e Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Thu, 22 Jan 2026 09:54:03 +0330 Subject: [PATCH] Add Hadis Layers API endpoint and update serializers - Introduced a new API endpoint to retrieve narrator layers associated with a specific hadis. - Enhanced the HadisLayersView to fetch and return unique narrator layers, including their names, slugs, and descriptions. - Updated serializers to include the slug in the TransmitterShortSerializer and added a new NarratorLayerSerializer for layer details. - Improved response structure and Swagger documentation for better clarity on usage and expected outputs. --- apps/hadis/docs.py | 76 +++++++++++++++++++++++++++++++++ apps/hadis/serializers/hadis.py | 74 +++++++++++++++++++++++++++----- apps/hadis/urls.py | 3 +- apps/hadis/views/hadis.py | 38 ++++++++++++++++- 4 files changed, 177 insertions(+), 14 deletions(-) diff --git a/apps/hadis/docs.py b/apps/hadis/docs.py index 533f833..9e4a87a 100644 --- a/apps/hadis/docs.py +++ b/apps/hadis/docs.py @@ -808,6 +808,82 @@ hadis_corrections_swagger = swagger_auto_schema( } ) +hadis_layers_swagger = swagger_auto_schema( + operation_description=""" + Get all narrator layers (Tabaqat) associated with a specific hadis. + + **Key Features:** + - Returns all unique narrator layers that appear in the transmission chain of a hadis + - Each layer includes name, slug, and description information + - Layers are ordered by their numerical order (generation/classification level) + - Useful for understanding the different generations of narrators in a hadis chain + + **Usage:** + - Use this endpoint to get the narrator layers for a specific hadis + - The layers represent different generations or classifications of narrators (e.g., Sahaba, Tabi'un, etc.) + - Each layer has a localized name and description + - The slug can be used for filtering transmitters by layer + + **Response Structure:** + - `count`: Total number of layers for this hadis + - `next/previous`: Pagination links (typically null for this endpoint) + - `results`: Array of layer objects with name, slug, and description + + **Note:** + - Only layers that have narrators in this specific hadis are returned + - Layers are ordered by their numerical layer number + - If no layers are found, the results array will be empty + """, + operation_summary="Get Hadis Narrator Layers", + operation_id="getHadisLayers", + tags=['Dobodbi - Hadis'], + manual_parameters=[ + openapi.Parameter( + 'hadis_slug', + openapi.IN_PATH, + description="Slug of the hadis. Must be a valid hadis slug that exists in the system. Only active hadis (status=True) are accessible.", + type=openapi.TYPE_STRING, + required=True, + example="hadis-ru-261-f7de1cbf" + ) + ], + responses={ + status.HTTP_200_OK: openapi.Response( + description="Successfully retrieved narrator layers for the specified hadis", + examples={ + "application/json": { + "count": 2, + "next": None, + "previous": None, + "results": [ + { + "name": "Students of the Imams", + "slug": "students-of-the-imams", + "description": "Direct students of the Imams of Ahl al-Bayt" + }, + { + "name": "Minor Narrators", + "slug": "minor-narrators", + "description": "Lesser known narrators" + } + ] + } + } + ), + status.HTTP_404_NOT_FOUND: openapi.Response( + description="The specified hadis slug does not exist or the hadis is not active", + examples={ + "application/json": { + "detail": "Not found." + } + } + ), + status.HTTP_500_INTERNAL_SERVER_ERROR: openapi.Response( + description="Internal server error occurred while processing the request" + ) + } +) + # Swagger documentation for HadisCollectionListView hadis_collections_swagger = swagger_auto_schema( diff --git a/apps/hadis/serializers/hadis.py b/apps/hadis/serializers/hadis.py index 312056f..33efcb5 100644 --- a/apps/hadis/serializers/hadis.py +++ b/apps/hadis/serializers/hadis.py @@ -293,7 +293,7 @@ class TransmitterShortSerializer(serializers.ModelSerializer): class Meta: model = Transmitters fields = [ - 'id', 'full_name', 'birth_year_hijri', 'death_year_hijri', + 'id', 'full_name', 'slug','birth_year_hijri', 'death_year_hijri', "known_as",'nickname','reliability' ] class TransmitterOpinionSerializer(serializers.ModelSerializer): @@ -453,20 +453,14 @@ class HadisTransmitterSerializer(serializers.ModelSerializer): """Serializer for HadisTransmitter with transmitter details""" transmitter = TransmitterShortSerializer(read_only=True) - narrator_layer_description = serializers.SerializerMethodField() layer = serializers.SerializerMethodField() status = serializers.SerializerMethodField() class Meta: model = HadisTransmitter fields = [ - 'id', 'order', 'is_gap','narrator_layer_description','layer', 'transmitter', 'status' + 'id', 'order', 'is_gap','layer', 'transmitter', 'status' ] - def get_narrator_layer_description(self, obj): - """Get narrator layer description""" - # ✅ Get language from request - request = self.context.get('request') - return get_localized_text(obj.narrator_layer.description,request) def get_layer(self, obj): @@ -526,10 +520,12 @@ class HadisTransmitterListSerializer(serializers.ModelSerializer): layer_names = [] for layer in layer_objects: name =get_localized_text(layer.name, request=request) + description = get_localized_text(layer.description, request=request) slug = layer.slug layer_names.append({ 'name': name, - 'slug': slug + 'slug': slug, + 'description': description }) return layer_names @@ -557,12 +553,42 @@ class HadisReferenceSerializer(serializers.ModelSerializer): book_title = serializers.SerializerMethodField() book_authors = serializers.SerializerMethodField() book_description = serializers.SerializerMethodField() + book_english_name = serializers.SerializerMethodField() + book_language = serializers.SerializerMethodField() + type_name = serializers.SerializerMethodField() + subject_area = serializers.SerializerMethodField() class Meta: model = HadisReference fields = [ - 'id', 'book_title','book_authors', 'book_description' + 'id', 'book_title', 'book_description', 'book_english_name', + 'book_language', 'type_name', 'subject_area','book_authors' ] + + def get_type_name(self,obj): + return "Comperhensive Shia's Hadith Encyclopedia" + def get_subject_area(self,obj): + return ["Hadith", + "Islamic History", + "Theology", + "Philosophy", + "Tafsir" + ] + + def get_book_english_name(self,obj): + english_name_data = obj.book_reference.title + for item in english_name_data: + if item.get('language_code') == 'en': + return item.get('text') + return None + + def get_book_language(self,obj): + language_data = obj.book_reference.language + field = LocalizedField() + field._context = self.context + + return field.to_representation(language_data) + def get_book_title(self, obj): title_data = obj.book_reference.title field = LocalizedField() @@ -585,7 +611,16 @@ class HadisReferenceSerializer(serializers.ModelSerializer): # 3. Loop through authors and convert each object to a string (name) # Result will be like: ["Al-Bukhari", "Muslim"] - return [field.to_representation(author.name) for author in authors] + out =[] + for author in authors: + name = field.to_representation(author.name) + out.append({ + 'name': name, + 'birth': '1037AH/1627CE', + 'death': '1105AH/1694CE', + }) + + return out except Exception: return [] @@ -743,3 +778,20 @@ class HadisDetailSerializer(serializers.ModelSerializer): request = self.context.get('request') language_code = getattr(request, 'LANGUAGE_CODE', 'en') return obj.translation.get(language_code) + + +class NarratorLayerSerializer(serializers.Serializer): + """Serializer for narrator layers information""" + name = serializers.SerializerMethodField() + slug = serializers.CharField(read_only=True) + description = serializers.SerializerMethodField() + + def get_name(self, obj): + """Get localized name""" + request = self.context.get('request') + return get_localized_text(obj.name, request=request) + + def get_description(self, obj): + """Get localized description""" + request = self.context.get('request') + return get_localized_text(obj.description, request=request) \ No newline at end of file diff --git a/apps/hadis/urls.py b/apps/hadis/urls.py index 5c1b740..980007e 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 ,test_deploy,debug_headers -from .views.hadis import HadisCollectionListView, HadisListView, HadisBasicView, HadisDetailView, HadisSyncView, HadisTransmittersView, HadisCorrectionsView,HadisMainListView, HadisFiltersView +from .views.hadis import HadisCollectionListView, HadisListView, HadisBasicView, HadisDetailView, HadisSyncView, HadisTransmittersView, HadisCorrectionsView,HadisMainListView, HadisFiltersView, HadisLayersView from .views.transmitter import TransmitterView ,TransmitterDetailView, TransmitterSyncView,TransmitterOpinionView, TransmitterOriginalTextView, TransmitterFiltersView from .views.reference import BookDetailView, BookReferencesView, BookReferenceSyncView, BookAttributeView from .views.version import ContentReleaseSyncView @@ -50,6 +50,7 @@ urlpatterns = [ # Hadis detail paths (with slug, more specific) path('/detail/', HadisDetailView.as_view(), name='hadis-detail'), path('/transmitters/', HadisTransmittersView.as_view(), name='hadis-transmitters'), + path('/transmitters/layers/', HadisLayersView.as_view(), name='hadis-layers'), path('/corrections/', HadisCorrectionsView.as_view(), name='hadis-corrections'), path('/', HadisBasicView.as_view(), name='hadis-basic'), # ← Least specific LAST diff --git a/apps/hadis/views/hadis.py b/apps/hadis/views/hadis.py index d9c1cee..525ea13 100644 --- a/apps/hadis/views/hadis.py +++ b/apps/hadis/views/hadis.py @@ -8,8 +8,8 @@ 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 ,HadisReference, HadisStatus ,ReferenceImage -from ..serializers import HadisListSerializer, HadisBasicSerializer, HadisDetailSerializer, HadisCollectionListSerializer, HadisSyncSerializer,HadisCorrectionSerializer,HadisTransmitterListSerializer , SimpleCategory -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 +from ..serializers import HadisListSerializer, HadisBasicSerializer, HadisDetailSerializer, HadisCollectionListSerializer, HadisSyncSerializer,HadisCorrectionSerializer,HadisTransmitterListSerializer , SimpleCategory, NarratorLayerSerializer +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 class HadisCollectionListView(ListAPIView): @@ -437,6 +437,40 @@ class HadisCorrectionsView(ListAPIView): return HadisCorrection.objects.none() + +class HadisLayersView(ListAPIView): + """ + API view to retrieve all narrator layers for a specific hadis + """ + serializer_class = NarratorLayerSerializer + pagination_class = NoPagination + lookup_field = 'slug' + lookup_url_kwarg = 'hadis_slug' + + @hadis_layers_swagger + def get(self, request, *args, **kwargs): + return self.list(request, *args, **kwargs) + + def get_queryset(self): + from ..models import NarratorLayer + + hadis_slug = self.kwargs.get('hadis_slug') + + # Get the hadis object to ensure it exists + hadis = get_object_or_404(Hadis, slug=hadis_slug, status=True) + + # Get all distinct narrator layer IDs for this hadis + layer_ids = HadisTransmitter.objects.filter( + hadis=hadis + ).values_list('narrator_layer', flat=True).distinct() + + # Filter out None values (transmitters without layers) + layer_ids = [lid for lid in layer_ids if lid is not None] + + # Return the layer objects ordered by number + return NarratorLayer.objects.filter(id__in=layer_ids).order_by('number') + + class HadisFiltersView(ListAPIView): """ API view to return filter data for hadis