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 ..serializers import ( HadisCategorySectListSerializer, HadisCategoryTreeSerializer, CategorySerializer , HadisCategorySelectSerializer , HadisCategorySelectSourceSerializer, get_localized_text ) 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 ) 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): """ Prefetch ALL data at once to avoid N+1 """ from django.db.models import Prefetch, Count, Q # Create a queryset for children that also has annotations children_queryset = ( HadisCategory.objects .select_related('sect') .prefetch_related('children') .annotate( hadis_count=Count('hadis', filter=Q(hadis__status=True)), children_count=Count('children', distinct=True) ) .order_by('order') ) return ( HadisCategory.objects .filter(parent__isnull=True, sect__is_active=True) .select_related('sect') .prefetch_related( Prefetch( 'children', queryset=children_queryset ), ) .annotate( hadis_count=Count('hadis', filter=Q(hadis__status=True)), children_count=Count('children', distinct=True) ) .order_by('sect__order', 'order') ) def list(self, request, *args, **kwargs): queryset = self.get_queryset() grouped_data = {} # Single pass through prefetched data (NO QUERIES) for category in queryset: sect_type = category.sect.sect_type if sect_type not in grouped_data: grouped_data[sect_type] = { 'sects': {}, 'categories': [] } # Add sect info sect_id = str(category.sect.id) if sect_id not in grouped_data[sect_type]['sects']: source_types = HadisCategory.objects.filter( sect=category.sect ).values_list('source_type', flat=True) grouped_data[sect_type]['sects'][sect_id] = { 'id': category.sect.id, 'sect_type': category.sect.sect_type, 'title': get_localized_text(category.sect.title, request), 'description': get_localized_text(category.sect.description,request), 'order': category.sect.order, 'source_types':list(set(source_types)) } # Build tree using prefetched data category_data = self._build_tree(category, request) grouped_data[sect_type]['categories'].append(category_data) # Count total categories (simple loop, no queries) total_count = self._count_tree_items(grouped_data) response_data = { 'count': total_count, 'results': grouped_data } return Response(response_data) def _build_tree(self, category, request): """ Build tree from ALREADY PREFETCHED data No database queries here! """ 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, 'hadis_count': category.hadis_count, # ← Use annotated value 'children_count': category.children_count, # ← Use annotated value 'has_hadis': category.hadis_count > 0, # ← Simple calculation '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(child, request) for child in category.children.all() # Already prefetched! ] } def _count_tree_items(self, grouped_data): """Count total items in tree""" total = 0 for sect_type_data in grouped_data.values(): for item in sect_type_data['categories']: total += 1 total += self._count_children(item.get('children', [])) return total def _count_children(self, children_list): """Recursively count children""" count = 0 for item in children_list: count += 1 if item.get('children'): count += self._count_children(item['children']) return count 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 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 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})