You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

508 lines
18 KiB

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)