from rest_framework.generics import ListAPIView from rest_framework.response import Response from django.shortcuts import get_object_or_404 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, CategorySerializer , HadisCategorySelectSerializer , HadisCategorySelectSourceSerializer, get_localized_text, SimpleCategory ) from ..docs import ( hadis_sect_list_swagger, hadis_category_tree_swagger, categories_list_swagger, categories_by_sect_swagger, categories_tree_by_sect_swagger, categories_tree_by_sect_source_swagger, hadis_category_xmind_swagger ) class HadisCategorySectListView(ListAPIView): """ API view to list all HadisSects grouped by sect_type (shia/sunni) """ queryset = HadisSect.objects.filter(is_active=True).order_by('order') serializer_class = HadisCategorySectListSerializer pagination_class = NoPagination @hadis_sect_list_swagger def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) class HadisCategoryTreeView(ListAPIView): """ API view to get all HadisCategory tree structure grouped by sect """ serializer_class = HadisCategoryTreeSerializer pagination_class = NoPagination @hadis_category_tree_swagger def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def get_queryset(self): """ Fetch ALL active categories in a single query with annotations. """ from django.db.models import Count, Q return ( HadisCategory.objects .filter(sect__is_active=True) .select_related('sect') .annotate( # Count of hadiths directly in this category direct_hadis_count=Count('hadis', filter=Q(hadis__status=True)) ) .order_by('sect__order', 'tree_id', 'lft', 'order') ) def list(self, request, *args, **kwargs): queryset = self.get_queryset() # 1. Build a mapping of all categories and their children category_map = {cat.id: cat for cat in queryset} children_map = {} roots = [] for cat in queryset: if cat.parent_id is None: roots.append(cat) else: if cat.parent_id not in children_map: children_map[cat.parent_id] = [] children_map[cat.parent_id].append(cat) # 2. Pre-calculate recursive hadis counts (total hadiths in sub-tree) # This matches CategorySerializer.children_count logic recursive_counts = {} def get_recursive_count(cat_id): if cat_id in recursive_counts: return recursive_counts[cat_id] cat = category_map[cat_id] count = cat.direct_hadis_count for child in children_map.get(cat_id, []): count += get_recursive_count(child.id) recursive_counts[cat_id] = count return count for cat in queryset: get_recursive_count(cat.id) # 3. Build grouped structure grouped_data = {} for root in roots: sect_type = root.sect.sect_type if sect_type not in grouped_data: grouped_data[sect_type] = { 'sects': {}, 'categories': [] } # Add sect info sect_id = str(root.sect.id) if sect_id not in grouped_data[sect_type]['sects']: # Optimistic source_types fetch - though still a query per sect # but better than before source_types = HadisCategory.objects.filter( sect=root.sect ).values_list('source_type', flat=True).order_by().distinct() grouped_data[sect_type]['sects'][sect_id] = { 'id': root.sect.id, 'sect_type': root.sect.sect_type, 'title': get_localized_text(root.sect.title, request), 'description': get_localized_text(root.sect.description, request), 'order': root.sect.order, 'source_types': list(source_types) } # Build tree using mapping category_data = self._build_tree_recursive(root, request, children_map, recursive_counts) grouped_data[sect_type]['categories'].append(category_data) # Count total categories total_count = len(queryset) response_data = { 'count': total_count, 'results': grouped_data } return Response(response_data) def _build_tree_recursive(self, category, request, children_map, recursive_counts): """ Build tree from flat mapping """ children = children_map.get(category.id, []) # Align fields with CategorySerializer return { 'id': category.id, 'title': get_localized_text(category.title, request), 'description': get_localized_text(category.description, request), 'slug': category.slug, 'source_type': category.source_type, 'sect_id': category.sect_id, 'sect_type': category.sect.sect_type, 'hadis_count': category.direct_hadis_count, # Direct count 'children_count': recursive_counts.get(category.id, 0), # Total in sub-tree (matches normal endpoint) 'has_hadis': category.direct_hadis_count > 0, 'order': category.order, 'thumbnail': self._get_thumbnail_url(category, request), 'xmind_file': self._get_xmind_url(category, request), 'has_xmind_file': bool(getattr(category, 'xmind_file', None)), 'children': [ self._build_tree_recursive(child, request, children_map, recursive_counts) for child in children ] } def _get_thumbnail_url(self, category, request): """Get absolute thumbnail URL""" if hasattr(category, 'thumbnail') and category.thumbnail: return request.build_absolute_uri(category.thumbnail.url) if request else category.thumbnail.url return None def _get_xmind_url(self, category, request): """Get absolute xmind URL""" if getattr(category, 'xmind_file', None): return request.build_absolute_uri(category.xmind_file.url) if request else category.xmind_file.url return None class HadisCategoryTreeNormalView(ListAPIView): """ Normal (paginated) tree view for HadisCategory. Unlike the sync view, this simply returns the root categories (filtered to active sects) with their nested children, and uses the project's default pagination. """ serializer_class = HadisCategoryTreeSerializer pagination_class = StandardResultsSetPagination @hadis_category_tree_swagger def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def get_queryset(self): return HadisCategory.objects.filter( parent__isnull=True, sect__is_active=True ).order_by('sect__order', 'order') class HadisCategorySelectBySectView(ListAPIView): """ Tree view for HadisCategory filtered by sect_type and category slug. Returns the children categories of the specified category (by slug) within the sect_type. """ serializer_class = HadisCategorySelectSerializer pagination_class = StandardResultsSetPagination @categories_tree_by_sect_swagger def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def list(self, request, *args, **kwargs): # 1. Run the standard list logic (get pagination, filter, results) response = super().list(request, *args, **kwargs) # 2. Find the "Parent" Category based on the URL slug category_slug = self.kwargs.get('slug') category_obj = get_object_or_404(HadisCategory, slug=category_slug) # 3. Serialize this single category for the Hero section # You might need a simple serializer just for titles/descriptions category_data = SimpleCategory(category_obj).data # 4. Inject it into the response data # Note: We access response.data because we are using DRF's Response object if isinstance(response.data, dict): # Reorder the response to place current_category before results ordered_data = {} for key in ['count', 'next', 'previous']: if key in response.data: ordered_data[key] = response.data[key] ordered_data['current_category'] = category_data if 'results' in response.data: ordered_data['results'] = response.data['results'] response.data = ordered_data return response def get_queryset(self): sect_type = self.kwargs.get('sect_type') slug = self.kwargs.get('slug') print(slug) print(sect_type) # Find the parent category by slug and sect_type try: parent_category = HadisCategory.objects.get( slug=slug, sect__sect_type=sect_type, sect__is_active=True ) except HadisCategory.DoesNotExist: print('not ok') return HadisCategory.objects.none() # Return children of this category, filtered as before return HadisCategory.objects.filter( parent=parent_category, sect__sect_type=sect_type, sect__is_active=True ).order_by('order') class HadisCategorySelectBySectSourceView(ListAPIView): """ Tree view for HadisCategory filtered by sect_type, category slug and source_type. Returns the children categories of the specified category (by slug) within the sect_type, filtered by source_type. """ serializer_class = HadisCategorySelectSourceSerializer pagination_class = StandardResultsSetPagination @categories_tree_by_sect_source_swagger def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def list(self, request, *args, **kwargs): # 1. Run the standard list logic (get pagination, filter, results) response = super().list(request, *args, **kwargs) # 2. Find the "Parent" Category based on the URL slug category_slug = self.kwargs.get('slug') category_obj = get_object_or_404(HadisCategory, slug=category_slug) # 3. Serialize this single category for the Hero section # You might need a simple serializer just for titles/descriptions category_data = SimpleCategory(category_obj).data # 4. Inject it into the response data # Note: We access response.data because we are using DRF's Response object if isinstance(response.data, dict): # Reorder the response to place current_category before results ordered_data = {} for key in ['count', 'next', 'previous']: if key in response.data: ordered_data[key] = response.data[key] ordered_data['current_category'] = category_data if 'results' in response.data: ordered_data['results'] = response.data['results'] response.data = ordered_data return response def get_queryset(self): sect_type = self.kwargs.get('sect_type') slug = self.kwargs.get('slug') source_type = self.kwargs.get('source_type') # Find the parent category by slug and sect_type try: parent_category = HadisCategory.objects.get( slug=slug, sect__sect_type=sect_type, sect__is_active=True ) except HadisCategory.DoesNotExist: return HadisCategory.objects.none() # Return children of this category, filtered by source_type return HadisCategory.objects.filter( parent=parent_category, sect__sect_type=sect_type, sect__is_active=True, source_type=source_type ).order_by('order') class CategoriesView(ListAPIView): """ API view to list all HadisCategories """ queryset = HadisCategory.objects.all() serializer_class = CategorySerializer pagination_class = StandardResultsSetPagination @categories_list_swagger def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) class CategoriesBySectView(ListAPIView): """ API view to list HadisCategories filtered by sect_type """ serializer_class = CategorySerializer pagination_class = StandardResultsSetPagination def get_queryset(self): sect_type = self.kwargs.get('sect_type') return HadisCategory.objects.filter(sect__sect_type=sect_type) @categories_by_sect_swagger def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) from rest_framework.decorators import api_view, permission_classes, authentication_classes from rest_framework.permissions import AllowAny from rest_framework.response import Response @api_view(['GET', 'POST']) @permission_classes([AllowAny]) # Let anyone access this @authentication_classes([]) # Disable auth so we don't get 403 def test_deploy(request): # This filters all headers Django receives and returns them as JSON headers = { k: v for k, v in request.META.items() if k.startswith('HTTP_') or k == 'CONTENT_TYPE' } # Also check if Authentication settings are actually active from django.conf import settings auth_settings = settings.REST_FRAMEWORK.get('DEFAULT_AUTHENTICATION_CLASSES', 'NOT SET') return Response({ "received_headers": headers, "active_auth_settings": auth_settings }) from django.http import JsonResponse from django.conf import settings def debug_headers(request): # # Security: strictly limitation to prevent leaking sensitive info to public # # Only allow if a specific secret key is passed in the URL # if request.GET.get('secret_debug_key') != 'super_secret_123': # return JsonResponse({'error': 'Unauthorized'}, status=403) # Return all HTTP headers Django received from Nginx headers = { k: v for k, v in request.META.items() if k.startswith('HTTP_') or k in ['CONTENT_TYPE', 'CONTENT_LENGTH'] } # Also return the scheme Django thinks it is using scheme_debug = { 'scheme': request.scheme, 'is_secure': request.is_secure(), 'SECURE_PROXY_SSL_HEADER_SETTING': getattr(settings, 'SECURE_PROXY_SSL_HEADER', None), } 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)