|
|
@ -2,6 +2,7 @@ from rest_framework.generics import ListAPIView |
|
|
from rest_framework.response import Response |
|
|
from rest_framework.response import Response |
|
|
from django.shortcuts import get_object_or_404 |
|
|
from django.shortcuts import get_object_or_404 |
|
|
from utils.pagination import NoPagination |
|
|
from utils.pagination import NoPagination |
|
|
|
|
|
from django.db.models import Q |
|
|
|
|
|
|
|
|
from ..models import HadisSect, HadisCategory |
|
|
from ..models import HadisSect, HadisCategory |
|
|
from ..serializers import ( |
|
|
from ..serializers import ( |
|
|
@ -68,7 +69,6 @@ class HadisCategorySectListView(ListAPIView): |
|
|
class HadisCategoryTreeView(ListAPIView): |
|
|
class HadisCategoryTreeView(ListAPIView): |
|
|
""" |
|
|
""" |
|
|
API view to get all HadisCategory tree structure grouped by sect |
|
|
API view to get all HadisCategory tree structure grouped by sect |
|
|
Returns all categories in a tree structure (source_type grouping removed for mobile filtering) |
|
|
|
|
|
""" |
|
|
""" |
|
|
serializer_class = HadisCategoryTreeSerializer |
|
|
serializer_class = HadisCategoryTreeSerializer |
|
|
pagination_class = NoPagination |
|
|
pagination_class = NoPagination |
|
|
@ -78,60 +78,72 @@ class HadisCategoryTreeView(ListAPIView): |
|
|
return self.list(request, *args, **kwargs) |
|
|
return self.list(request, *args, **kwargs) |
|
|
|
|
|
|
|
|
def get_queryset(self): |
|
|
def get_queryset(self): |
|
|
return HadisCategory.objects.filter( |
|
|
|
|
|
parent__isnull=True, |
|
|
|
|
|
sect__is_active=True |
|
|
|
|
|
).select_related('sect').prefetch_related( |
|
|
|
|
|
|
|
|
""" |
|
|
|
|
|
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', |
|
|
'children', |
|
|
'children__children' |
|
|
|
|
|
).order_by('sect__order', 'order') |
|
|
|
|
|
|
|
|
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): |
|
|
def list(self, request, *args, **kwargs): |
|
|
queryset = self.get_queryset() |
|
|
queryset = self.get_queryset() |
|
|
|
|
|
|
|
|
grouped_data = {} |
|
|
grouped_data = {} |
|
|
|
|
|
|
|
|
serializer_instance = HadisCategoryTreeSerializer(context={'request': request}) |
|
|
|
|
|
|
|
|
|
|
|
# گروهبندی بر اساس sect_type (shia/sunni) |
|
|
|
|
|
|
|
|
# Single pass through prefetched data (NO QUERIES) |
|
|
for category in queryset: |
|
|
for category in queryset: |
|
|
sect_type = category.sect.sect_type |
|
|
sect_type = category.sect.sect_type |
|
|
|
|
|
|
|
|
if sect_type not in grouped_data: |
|
|
if sect_type not in grouped_data: |
|
|
# ایجاد گروه برای هر sect_type |
|
|
|
|
|
grouped_data[sect_type] = { |
|
|
grouped_data[sect_type] = { |
|
|
'sects': {}, |
|
|
'sects': {}, |
|
|
'categories': [] |
|
|
'categories': [] |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
# اضافه کردن اطلاعات sect به گروه sect_type |
|
|
|
|
|
|
|
|
# Add sect info |
|
|
sect_id = str(category.sect.id) |
|
|
sect_id = str(category.sect.id) |
|
|
if sect_id not in grouped_data[sect_type]['sects']: |
|
|
if sect_id not in grouped_data[sect_type]['sects']: |
|
|
grouped_data[sect_type]['sects'][sect_id] = { |
|
|
grouped_data[sect_type]['sects'][sect_id] = { |
|
|
'id': category.sect.id, |
|
|
'id': category.sect.id, |
|
|
'sect_type': category.sect.sect_type, |
|
|
'sect_type': category.sect.sect_type, |
|
|
'title': get_localized_text(category.sect.title,self.request), |
|
|
|
|
|
|
|
|
'title': get_localized_text(category.sect.title, request), |
|
|
'description': category.sect.description, |
|
|
'description': category.sect.description, |
|
|
'order': category.sect.order |
|
|
'order': category.sect.order |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
category_data = self.build_enhanced_category_tree(category, serializer_instance) |
|
|
|
|
|
|
|
|
# Build tree using prefetched data |
|
|
|
|
|
category_data = self._build_tree(category, request) |
|
|
grouped_data[sect_type]['categories'].append(category_data) |
|
|
grouped_data[sect_type]['categories'].append(category_data) |
|
|
|
|
|
|
|
|
def count_children(children_list): |
|
|
|
|
|
count = 0 |
|
|
|
|
|
for item in children_list: |
|
|
|
|
|
count += 1 |
|
|
|
|
|
if 'children' in item and item['children']: |
|
|
|
|
|
count += count_children(item['children']) |
|
|
|
|
|
return count |
|
|
|
|
|
|
|
|
|
|
|
total_count = 0 |
|
|
|
|
|
for sect_type_data in grouped_data.values(): |
|
|
|
|
|
for item in sect_type_data['categories']: |
|
|
|
|
|
total_count += 1 |
|
|
|
|
|
if 'children' in item and item['children']: |
|
|
|
|
|
total_count += count_children(item['children']) |
|
|
|
|
|
|
|
|
# Count total categories (simple loop, no queries) |
|
|
|
|
|
total_count = self._count_tree_items(grouped_data) |
|
|
|
|
|
|
|
|
response_data = { |
|
|
response_data = { |
|
|
'count': total_count, |
|
|
'count': total_count, |
|
|
@ -140,51 +152,59 @@ class HadisCategoryTreeView(ListAPIView): |
|
|
|
|
|
|
|
|
return Response(response_data) |
|
|
return Response(response_data) |
|
|
|
|
|
|
|
|
def build_enhanced_category_tree(self, category, serializer_instance): |
|
|
|
|
|
"""Build enhanced category tree with father category info and hadis details""" |
|
|
|
|
|
# serializer_instance already has context={'request': request} |
|
|
|
|
|
base_data = serializer_instance.to_dict(category, request=self.request) |
|
|
|
|
|
|
|
|
|
|
|
# Rest of the code... |
|
|
|
|
|
enhanced_children = [] |
|
|
|
|
|
for child_data in base_data.get('children', []): |
|
|
|
|
|
enhanced_child = self.enhance_child_data(child_data, category, serializer_instance) |
|
|
|
|
|
enhanced_children.append(enhanced_child) |
|
|
|
|
|
|
|
|
|
|
|
base_data['children'] = enhanced_children |
|
|
|
|
|
return base_data |
|
|
|
|
|
|
|
|
|
|
|
def enhance_child_data(self, child_data, parent_category, serializer_instance): |
|
|
|
|
|
"""Enhance child data with father category info (no hadis payload for sync tree)""" |
|
|
|
|
|
|
|
|
|
|
|
# Add father category information |
|
|
|
|
|
child_data['father_category'] = { |
|
|
|
|
|
'id': parent_category.id, |
|
|
|
|
|
'title': get_localized_text(parent_category.title,self.request), |
|
|
|
|
|
'sect_id': parent_category.sect.id, |
|
|
|
|
|
'sect_type': parent_category.sect.sect_type, |
|
|
|
|
|
'source_type': parent_category.source_type |
|
|
|
|
|
|
|
|
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! |
|
|
|
|
|
] |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
# Note: we intentionally DO NOT load or attach hadis details here for performance. |
|
|
|
|
|
|
|
|
|
|
|
# Recursively enhance children's children |
|
|
|
|
|
if child_data.get('children', []): |
|
|
|
|
|
enhanced_grandchildren = [] |
|
|
|
|
|
try: |
|
|
|
|
|
from ..models import HadisCategory |
|
|
|
|
|
child_category = HadisCategory.objects.get(id=child_data['id']) |
|
|
|
|
|
|
|
|
|
|
|
for grandchild_data in child_data['children']: |
|
|
|
|
|
enhanced_grandchild = self.enhance_child_data(grandchild_data, child_category, serializer_instance) |
|
|
|
|
|
enhanced_grandchildren.append(enhanced_grandchild) |
|
|
|
|
|
except: |
|
|
|
|
|
# If there's an error, keep original children |
|
|
|
|
|
enhanced_grandchildren = child_data['children'] |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
child_data['children'] = enhanced_grandchildren |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
return child_data |
|
|
|
|
|
|
|
|
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): |
|
|
class HadisCategoryTreeNormalView(ListAPIView): |
|
|
|