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.
 
 

303 lines
11 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 ..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)
def list(self, request, *args, **kwargs):
return super().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)