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
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)
|