From bcc2cf11046dcf524a489bd3bb3603e96f987c68 Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Sun, 25 Jan 2026 11:08:14 +0330 Subject: [PATCH] Add HadisCategoryXMindView API endpoint and Swagger documentation - Implemented HadisCategoryXMindView to retrieve a mind-map JSON structure for specific hadis categories. - Added Swagger documentation detailing the endpoint's features, usage, and response structure. - Updated URL routing to include the new endpoint for XMind format retrieval. - Enhanced the HadisCategoryXMindView to support multi-language content and bookmark status for authenticated users. --- apps/hadis/docs.py | 132 +++++++++++++++++++++++++++++++++++ apps/hadis/urls.py | 3 +- apps/hadis/views/category.py | 102 ++++++++++++++++++++++++++- 3 files changed, 234 insertions(+), 3 deletions(-) diff --git a/apps/hadis/docs.py b/apps/hadis/docs.py index 9e4a87a..5f881fd 100644 --- a/apps/hadis/docs.py +++ b/apps/hadis/docs.py @@ -2540,3 +2540,135 @@ content_release_sync_swagger = swagger_auto_schema( ) } ) + + +# Swagger documentation for HadisCategoryXMindView +hadis_category_xmind_swagger = swagger_auto_schema( + operation_description=""" + Retrieve a mind-map JSON structure (XMind format) for a specific category and its hadiths. + + **Key Features:** + - Returns XMind-compatible JSON structure for mind-map visualization + - Root node represents the category + - Child nodes represent all hadiths in that category + - Supports multi-language content based on query parameter or Accept-Language header + - Includes bookmark status for each hadith (if user is authenticated) + - Returns all hadiths without pagination (suitable for mind-map visualization) + + **Structure:** + - `rootTopic`: The main category node + - `id`: Category identifier (format: "cat-{category_id}") + - `title`: Localized category title + - `structureClass`: XMind structure type (org.xmind.ui.map.unbalanced for right-branching) + - `children.attached`: Array of hadith nodes + - Each hadith node contains: + - `id`: Hadith ID + - `slug`: URL-friendly identifier + - `title`: Localized hadith title + - `title_narrator`: Narrator information + - `text`: Original Arabic text + - `translation`: Translated text in requested language + - `share_link`: Direct link to the hadith + - `is_bookmarked`: Boolean indicating if user has bookmarked this hadith + + **Usage:** + - Use this endpoint to generate mind-map visualizations of hadith categories + - The response format is compatible with XMind and similar mind-mapping tools + - Language can be specified via `?lang=` query parameter or Accept-Language header + - Bookmark status is only available for authenticated users + + **Note:** + - This endpoint returns ALL hadiths in the category (no pagination) + - For categories with 1000+ hadiths, consider client-side limiting + - Only active (status=True) hadiths are included + """, + operation_summary="Get Category Mind-Map Structure (XMind Format)", + tags=['Dobodbi - Hadis'], + manual_parameters=[ + openapi.Parameter( + 'category_slug', + openapi.IN_PATH, + description="Unique slug identifier of the Hadis category. Must be a valid category slug that exists in the system.", + type=openapi.TYPE_STRING, + required=True, + example='cat-l3-25-7f5bcb' + ), + openapi.Parameter( + 'lang', + openapi.IN_QUERY, + description="Language code for content localization. Supported codes: 'en' (English), 'fa' (Persian), 'ar' (Arabic), 'ur' (Urdu), 'ru' (Russian). Defaults to 'en' if not specified. Can also be set via Accept-Language header.", + type=openapi.TYPE_STRING, + required=False, + default='en', + enum=['en', 'fa', 'ar', 'ur', 'ru'] + ), + openapi.Parameter( + 'Accept-Language', + openapi.IN_HEADER, + description="Alternative way to specify language code. If both query parameter and header are provided, query parameter takes precedence.", + type=openapi.TYPE_STRING, + required=False, + default='en', + enum=['en', 'fa', 'ar', 'ur', 'ru'] + ) + ], + responses={ + status.HTTP_200_OK: openapi.Response( + description="Successfully retrieved mind-map structure for the specified category", + examples={ + "application/json": { + "rootTopic": { + "id": "cat-25", + "title": "Book of Faith", + "structureClass": "org.xmind.ui.map.unbalanced", + "children": { + "attached": [ + { + "id": 1, + "slug": "hadis-intention-and-actions", + "title": "The Intention", + "title_narrator": "From Umar ibn al-Khattab", + "text": "إنما الأعمال بالنيات وإنما لكل امرئ ما نوى", + "translation": "Actions are but by intention, and every man shall have only what he intended", + "share_link": "http://example.com/hadis/hadis-intention-and-actions", + "is_bookmarked": False + }, + { + "id": 2, + "slug": "hadis-gabriel-hadith", + "title": "The Hadith of Gabriel", + "title_narrator": "From Abu Hurairah", + "text": "بينما نحن عند رسول الله صلى الله عليه وسلم ذات يوم", + "translation": "While we were sitting with the Messenger of Allah (peace be upon him) one day", + "share_link": "http://example.com/hadis/hadis-gabriel-hadith", + "is_bookmarked": True + }, + { + "id": 3, + "slug": "hadis-islam-faith-excellence", + "title": "Islam, Faith, and Excellence", + "title_narrator": "From Abu Hurairah", + "text": "عن أبي هريرة رضي الله عنه قال: قال رسول الله صلى الله عليه وسلم", + "translation": "Narrated Abu Hurairah: The Messenger of Allah (peace be upon him) said", + "share_link": "http://example.com/hadis/hadis-islam-faith-excellence", + "is_bookmarked": False + } + ] + } + } + } + } + ), + status.HTTP_404_NOT_FOUND: openapi.Response( + description="Category not found. The provided category_slug does not exist in the system.", + examples={ + "application/json": { + "detail": "Not found." + } + } + ), + status.HTTP_500_INTERNAL_SERVER_ERROR: openapi.Response( + description="Internal server error" + ) + } +) \ No newline at end of file diff --git a/apps/hadis/urls.py b/apps/hadis/urls.py index 980007e..ae7c6c4 100644 --- a/apps/hadis/urls.py +++ b/apps/hadis/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .views.category import HadisCategorySectListView, HadisCategoryTreeView, CategoriesView, CategoriesBySectView, HadisCategorySelectBySectView, HadisCategorySelectBySectSourceView , HadisCategoryTreeNormalView ,test_deploy,debug_headers +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 from .views.transmitter import TransmitterView ,TransmitterDetailView, TransmitterSyncView,TransmitterOpinionView, TransmitterOriginalTextView, TransmitterFiltersView from .views.reference import BookDetailView, BookReferencesView, BookReferenceSyncView, BookAttributeView @@ -32,6 +32,7 @@ urlpatterns = [ path('categories/', CategoriesView.as_view(), name='categories'), # ← Least specific LAST # Hadis paths + path('category//xmind/', HadisCategoryXMindView.as_view(), name='hadis-category-xmind'), # ← Must be before other category paths path('category//', HadisListView.as_view(), name='hadis-list'), path('arguments/', cached_view(HadisMainListView.as_view()), name='hadis-main-list'), path('arguments/filters/', cached_view(HadisFiltersView.as_view()), name='hadis-filters'), diff --git a/apps/hadis/views/category.py b/apps/hadis/views/category.py index 6ff4acc..0876ab8 100644 --- a/apps/hadis/views/category.py +++ b/apps/hadis/views/category.py @@ -5,6 +5,7 @@ from utils.pagination import NoPagination from django.db.models import Q from utils.pagination import StandardResultsSetPagination from ..models import HadisSect, HadisCategory,Hadis +from apps.bookmark.serializers.bookmark import BookmarkStatusSerializer from ..serializers import ( HadisCategorySectListSerializer, HadisCategoryTreeSerializer, @@ -20,7 +21,8 @@ from ..docs import ( categories_list_swagger, categories_by_sect_swagger, categories_tree_by_sect_swagger, - categories_tree_by_sect_source_swagger + categories_tree_by_sect_source_swagger, + hadis_category_xmind_swagger ) @@ -402,4 +404,100 @@ def debug_headers(request): 'SECURE_PROXY_SSL_HEADER_SETTING': getattr(settings, 'SECURE_PROXY_SSL_HEADER', None), } - return JsonResponse({'headers': headers, 'debug': scheme_debug}) \ No newline at end of file + return JsonResponse({'headers': headers, 'debug': scheme_debug}) + + + +from rest_framework.views import APIView +class HadisCategoryXMindView(APIView): + """ + Returns a mind-map JSON structure for a specific category and its hadiths. + Root -> Category + Children -> Hadiths + """ + + def get_localized_text(self, json_field, lang): + """Helper to extract text from your JSON structure""" + if not json_field or not isinstance(json_field, list): + return "Unknown" + + # 1. Try specific language + for item in json_field: + if item.get('language_code') == lang: + return item.get('text', '') + + # 2. Fallback to English + for item in json_field: + if item.get('language_code') == 'en': + return item.get('text', '') + + # 3. Fallback to first available + if len(json_field) > 0: + return json_field[0].get('text', '') + + return "Unknown" + + @hadis_category_xmind_swagger + def get(self, request, category_slug): + # 1. Determine Language (support ?lang=ru or Accept-Language header) + lang = request.query_params.get('lang','en') + + # 2. Get the Category (Root Node) + category = get_object_or_404(HadisCategory, slug=category_slug) + root_title = self.get_localized_text(category.title, lang) + + # 3. Get the Hadiths (Child Nodes) + # Note: Mind maps generally show ALL nodes, so we avoid pagination here. + # If you have 1000+ hadiths, consider limiting this query (e.g., [:50]). + hadiths = Hadis.objects.filter( + category=category, + status=True + ).order_by('number').only('id', 'number', 'title', 'title_narrator', 'translation', 'text', 'slug', 'share_link') + + # 4. Get user for bookmark check + user = request.user if request and hasattr(request, 'user') else None + if user and user.is_anonymous: + user = None + + # 5. Build Child Nodes List + children_nodes = [] + for hadis in hadiths: + # Get Title + hadis_title = self.get_localized_text(hadis.title, lang) + hadis_title_narrator = self.get_localized_text(hadis.title_narrator, lang) + hadis_translation = self.get_localized_text(hadis.translation, lang) + + # Get bookmark status + bookmark_info = BookmarkStatusSerializer.get_bookmark_info( + obj=hadis, + user=user, + service='hadith' + ) + is_bookmarked = bookmark_info.get('is_bookmarked', False) + + children_nodes.append({ + "id": hadis.id, + "slug": hadis.slug, + "title": hadis_title, + "title_narrator": hadis_title_narrator, + "text": hadis.text, + "translation": hadis_translation, + "share_link": hadis.share_link, + "is_bookmarked": is_bookmarked, + # Optional: Add 'href' if you want XMind to handle links, + # but usually the frontend handles the 'click' event based on ID. + }) + + # 6. Construct XMind JSON Structure + data = { + "rootTopic": { + "id": f"cat-{category.id}", + "title": root_title, + "structureClass": "org.xmind.ui.map.unbalanced", # Standard right-branching map + "children": { + "attached": children_nodes + } + } + } + + return Response(data) \ No newline at end of file