from django.contrib.admin.utils import lookup_field from django.db.models import Prefetch, Q from rest_framework.generics import ListAPIView , RetrieveAPIView from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import AllowAny from django.shortcuts import get_object_or_404 from django.db.models import F from drf_yasg.utils import swagger_auto_schema from ..models import Transmitters, HadisTransmitter from ..serializers import NarratorRelationSerializer from utils.pagination import StandardResultsSetPagination from ..models import Transmitters, TransmitterOpinion, TransmitterOriginalText, Hadis, OriginalTextReference, OriginalTextReferenceImage from ..serializers import TransmitterSerializer, TransmitterDetailSerializer, TransmitterSyncSerializer, TransmitterOpinionSerializer, TransmitterOriginalTextSerializer, TransmitterOriginalTextDetailSerializer, HadisListSerializer from ..docs import transmitter_filters_swagger,transmitter_list_swagger, transmitter_detail_swagger, transmitter_sync_swagger, transmitter_opinion_swagger, transmitter_original_text_swagger from utils.pagination import NoPagination from .category import get_localized_text from ..serializers import HadisListSerializer from drf_yasg.utils import swagger_auto_schema class TransmitterFliterView(ListAPIView): pass class TransmitterView(ListAPIView): queryset = Transmitters.objects.all() serializer_class = TransmitterSerializer pagination_class = StandardResultsSetPagination @transmitter_list_swagger def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): queryset = Transmitters.objects.all() # Get query parameters search_query = self.request.query_params.get('search', None) status_filter = self.request.query_params.get('status', None) madhhab_filter = self.request.query_params.get('madhhab', None) generation_filter = self.request.query_params.get('generation', None) # 1. Apply search filter (Searching across name-related JSONFields, slug, legacy_id) if search_query: from utils.text_normalizer import normalize_for_search norm_q = normalize_for_search(search_query) q_filter = ( Q(normalized_text__icontains=norm_q) | Q(full_name__icontains=search_query) | Q(kunya__icontains=search_query) | Q(known_as__icontains=search_query) | Q(nickname__icontains=search_query) | Q(slug__icontains=search_query) | Q(legacy_id__icontains=search_query) ) if search_query.isdigit(): q_filter |= Q(legacy_number=int(search_query)) | Q(generation=int(search_query)) queryset = queryset.filter(q_filter) # 2. Apply categorical filters if status_filter: queryset = queryset.filter(reliability__slug=status_filter) if madhhab_filter: queryset = queryset.filter(madhhab=madhhab_filter) if generation_filter: queryset = queryset.filter(generation=generation_filter) return queryset class TransmitterDetailView(RetrieveAPIView): serializer_class = TransmitterDetailSerializer lookup_field = 'slug' lookup_url_kwarg = 'narrator_slug' @transmitter_detail_swagger def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): input = self.kwargs.get('narrator_slug') return Transmitters.objects.filter(slug=input) class TransmitterOpinionView(ListAPIView): serializer_class = TransmitterOpinionSerializer pagination_class = StandardResultsSetPagination @transmitter_opinion_swagger def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def get_queryset(self): narrator_slug = self.kwargs.get('narrator_slug') return TransmitterOpinion.objects.filter( transmitter__slug=narrator_slug ).select_related( 'transmitter', # Essential if serializer includes transmitter data 'status' # Added for the new OpinionStatus ForeignKey ).prefetch_related( # Add any nested relations from TransmitterOriginalTextSerializer # 'translations', # 'manuscript_references', ) class TransmitterOriginalTextView(ListAPIView): serializer_class = TransmitterOriginalTextDetailSerializer pagination_class = StandardResultsSetPagination @transmitter_original_text_swagger def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def get_queryset(self): narrator_slug = self.kwargs.get('narrator_slug') return TransmitterOriginalText.objects.filter( transmitter__slug=narrator_slug ).select_related( 'transmitter' ).prefetch_related( Prefetch( 'references', queryset=OriginalTextReference.objects.select_related( 'book_reference', 'book_reference__author', 'edition', 'book_volume', 'book_volume__edition' ).prefetch_related( Prefetch('images', queryset=OriginalTextReferenceImage.objects.order_by('priority')), 'book_reference__editions' ) ) ) class TransmitterSyncView(ListAPIView): """ API view to sync all transmitter data for offline mode Returns all transmitters with biographical data, opinions, and exact flat relation IDs. """ serializer_class = TransmitterSyncSerializer pagination_class = NoPagination @transmitter_sync_swagger def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def get_queryset(self): from ..models import TransmitterOriginalText, TransmitterRelative return ( Transmitters.objects # 👇 ۱. واکشی اطلاعات reliability راوی در همان کوئری اصلی .select_related('reliability') .prefetch_related( # 👇 ۲. اضافه کردن 'scholar' برای جلوگیری از کوئری اضافه در نظرات دانشمندان Prefetch('opinions', queryset=TransmitterOpinion.objects.select_related('status', 'scholar')), Prefetch( 'originaltexts', # 👇 ۳. اضافه کردن 'transmitter' برای جلوگیری از کوئری چرخشی در DetailSerializer متون اصلی queryset=TransmitterOriginalText.objects.select_related('transmitter').prefetch_related( 'references__book_reference__author', 'references__edition', 'references__book_volume', 'references__images' ) ), Prefetch('relatives', queryset=TransmitterRelative.objects.select_related('narrator')) ) .order_by('id') ) def list(self, request, *args, **kwargs): # 🧠 ۱. واکشی لینک‌ها با values_list برای جلوگیری از هرگونه تداخل دیکشنری و افزایش سرعت all_links = HadisTransmitter.objects.values_list( 'hadis_id', 'chain_index', 'order', 'transmitter_id', 'hadis__category_id' ) # 🧠 ۲. بازسازی کل زنجیره‌های حدیثی به صورت کاملاً ایمن و عددی chains = {} categories_map = {} for hadis_id, chain_index, order, transmitter_id, category_id in all_links: if transmitter_id is None: continue tx_id = int(transmitter_id) if tx_id not in categories_map: categories_map[tx_id] = set() if category_id is not None: categories_map[tx_id].add(int(category_id)) key = (int(hadis_id), int(chain_index)) if key not in chains: chains[key] = {} chains[key][int(order)] = tx_id # دایرکتوری‌های نگاشت روابط teachers_map = {} students_map = {} # 🧠 ۳. پردازش خطی ساختار درختواره for key, orders in chains.items(): for order, tx_id in orders.items(): if tx_id not in teachers_map: teachers_map[tx_id] = set() if tx_id not in students_map: students_map[tx_id] = set() # استاد (پوزیشن قبلی در ترتیب روایی / order + 1) if (order + 1) in orders: teachers_map[tx_id].add(orders[order + 1]) # شاگرد (پوزیشن بعدی در ترتیب روایی / order - 1) if (order - 1) in orders: students_map[tx_id].add(orders[order - 1]) queryset = self.get_queryset() # 🧠 ۴. تزریق اجباری کانتکست به سریالایزر (جلوگیری از اوررایت شدن توسط DRF) context = self.get_serializer_context() context['teachers_map'] = teachers_map context['students_map'] = students_map context['categories_map'] = categories_map serializer = self.get_serializer_class()( queryset, many=True, context=context ) response_data = { 'count': len(serializer.data), 'filters': get_transmitter_filters(request), 'results': serializer.data } return Response(response_data) def get_transmitter_filters(request): # Get all unique generations generations = Transmitters.objects.exclude(generation__isnull=True).values_list('generation', flat=True).distinct().order_by('generation') # Get madhhab choices madhabs = [] for choice in Transmitters.MadhhabChoices: madhabs.append(choice.value) # Get reliabilities from TransmitterReliability model from ..models.transmitter import TransmitterReliability reliabilities = [] for reliability in TransmitterReliability.objects.all().order_by('id'): if reliability.title: reliabilities.append({ 'id': reliability.id, 'text': get_localized_text(reliability.title, request), 'title': get_localized_text(reliability.title, request), 'slug': reliability.slug, 'color': reliability.color, 'main_color_code': reliability.main_color_code, }) return { 'generations': list(generations), 'madhabs': madhabs, 'reliabilities': reliabilities } class TransmitterFiltersView(ListAPIView): """ API view to return filter data for transmitters Returns generations, madhabs, and reliabilities for filtering """ pagination_class = NoPagination @transmitter_filters_swagger def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def get_queryset(self): # This view doesn't need a queryset, it returns computed data return Transmitters.objects.none() def list(self, request, *args, **kwargs): return Response(get_transmitter_filters(request)) from ..serializers.category import CategorySerializer from ..models.category import HadisCategory class NarratorArgumentsListView(ListAPIView): """ API view to list all Hadis Categories where a specific narrator exists in the transmission chain of hadiths within that category. """ serializer_class = CategorySerializer pagination_class = StandardResultsSetPagination # در صورت نیاز به احراز هویت می‌توانید کامنت زیر را باز کنید: # authentication_classes = [TokenAuthentication] @swagger_auto_schema( operation_summary="Narrator Arguments List (Categories)", operation_description="Returns a paginated list of Hadis Categories for a specific narrator based on the hadiths they appear in.", tags=['Dobodbi - Hadis (V2)'], ) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): narrator_slug = self.kwargs.get('narrator_slug') # فیلتر کردن دسته‌بندی‌هایی که در آن‌ها حدیث فعالی وجود دارد که این راوی در زنجیره آن حضور دارد return HadisCategory.objects.filter( hadis__transmitters__transmitter__slug=narrator_slug, hadis__status=True ).distinct().order_by('id') # class NarratorRelationsView(APIView): # """ # API view to get the Teachers and Students of a specific narrator # based on their position in Hadith transmission chains. # """ # permission_classes = [AllowAny] # @swagger_auto_schema( # operation_summary="Get Narrator Teachers and Students", # operation_description="Analyzes transmission chains to find the teachers (order + 1) and students (order - 1) of a narrator.", # tags=['Dobodbi - Hadis (V2)'], # ) # def get(self, request, narrator_slug, *args, **kwargs): # # 1. پیدا کردن راوی اصلی # narrator = get_object_or_404(Transmitters, slug=narrator_slug) # # 2. پیدا کردن تمام اتصالاتی که این راوی در آن‌ها حضور دارد # narrator_links = HadisTransmitter.objects.filter(transmitter=narrator) # teacher_ids = set() # student_ids = set() # # 3. تحلیل زنجیره‌ها برای پیدا کردن اساتید و شاگردان # for link in narrator_links: # # استاد = کسی که در همان حدیث و همان زنجیره، Order او یکی بیشتر از راوی ماست # teacher_link = HadisTransmitter.objects.filter( # hadis_id=link.hadis_id, # chain_index=link.chain_index, # order=link.order + 1 # ).first() # if teacher_link: # teacher_ids.add(teacher_link.transmitter_id) # # شاگرد = کسی که در همان حدیث و همان زنجیره، Order او یکی کمتر از راوی ماست # student_link = HadisTransmitter.objects.filter( # hadis_id=link.hadis_id, # chain_index=link.chain_index, # order=link.order - 1 # ).first() # if student_link: # student_ids.add(student_link.transmitter_id) # # 4. واکشی اطلاعات اساتید و شاگردان از دیتابیس به صورت یکجا # teachers_qs = Transmitters.objects.filter(id__in=teacher_ids) # students_qs = Transmitters.objects.filter(id__in=student_ids) # # 5. سریالایز کردن داده‌ها با در نظر گرفتن زبان ریکوئست # teachers_data = NarratorRelationSerializer(teachers_qs, many=True, context={'request': request}).data # students_data = NarratorRelationSerializer(students_qs, many=True, context={'request': request}).data # # 6. بازگرداندن پاسخ # return Response({ # "narrator_slug": narrator.slug, # "teachers_count": len(teachers_data), # "students_count": len(students_data), # "teachers": teachers_data, # "students": students_data # }) from django.db.models import Exists, OuterRef # 👈 حتما این دو را ایمپورت کنید from drf_yasg import openapi class NarratorTeachersView(ListAPIView): """ API view to get the Teachers of a specific narrator based on their position in Hadith transmission chains. Optimized to run fully inside the database. """ serializer_class = NarratorRelationSerializer pagination_class = StandardResultsSetPagination permission_classes = [AllowAny] @swagger_auto_schema( operation_summary="Get Narrator Teachers", operation_description="Analyzes transmission chains to find the teachers (order + 1) of a narrator, with optional search.", tags=['Dobodbi - Hadis (V2)'], manual_parameters=[ openapi.Parameter('search', openapi.IN_QUERY, description="Search term for narrator's name", type=openapi.TYPE_STRING), ] ) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): narrator_slug = self.kwargs.get('narrator_slug') narrator = get_object_or_404(Transmitters, slug=narrator_slug) is_teacher_subquery = HadisTransmitter.objects.filter( transmitter=narrator, hadis_id=OuterRef('hadis_id'), chain_index=OuterRef('chain_index'), order=OuterRef('order') - 1 # استاد یعنی Order او یکی کمتر از راوی تارگت باشد ) teacher_ids = HadisTransmitter.objects.annotate( is_teacher=Exists(is_teacher_subquery) ).filter(is_teacher=True).values_list('transmitter_id', flat=True).distinct() queryset = Transmitters.objects.filter(id__in=teacher_ids).select_related('reliability') search_query = self.request.query_params.get('search', None) if search_query: from utils.text_normalizer import normalize_for_search norm_q = normalize_for_search(search_query) queryset = queryset.filter( Q(normalized_text__icontains=norm_q) | Q(full_name__icontains=search_query) | Q(kunya__icontains=search_query) | Q(known_as__icontains=search_query) | Q(nickname__icontains=search_query) ) return queryset class NarratorStudentsView(ListAPIView): """ API view to get the Students of a specific narrator based on their position in Hadith transmission chains. Optimized to run fully inside the database. """ serializer_class = NarratorRelationSerializer pagination_class = StandardResultsSetPagination permission_classes = [AllowAny] @swagger_auto_schema( operation_summary="Get Narrator Students", operation_description="Analyzes transmission chains to find the students (order - 1) of a narrator, with optional search.", tags=['Dobodbi - Hadis (V2)'], manual_parameters=[ openapi.Parameter('search', openapi.IN_QUERY, description="Search term for narrator's name", type=openapi.TYPE_STRING), ] ) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): narrator_slug = self.kwargs.get('narrator_slug') narrator = get_object_or_404(Transmitters, slug=narrator_slug) is_student_subquery = HadisTransmitter.objects.filter( transmitter=narrator, hadis_id=OuterRef('hadis_id'), chain_index=OuterRef('chain_index'), order=OuterRef('order') + 1 # شاگرد یعنی Order او یکی بیشتر باشد ) student_ids = HadisTransmitter.objects.annotate( is_student=Exists(is_student_subquery) ).filter(is_student=True).values_list('transmitter_id', flat=True).distinct() queryset = Transmitters.objects.filter(id__in=student_ids).select_related('reliability') search_query = self.request.query_params.get('search', None) if search_query: from utils.text_normalizer import normalize_for_search norm_q = normalize_for_search(search_query) queryset = queryset.filter( Q(normalized_text__icontains=norm_q) | Q(full_name__icontains=search_query) | Q(kunya__icontains=search_query) | Q(known_as__icontains=search_query) | Q(nickname__icontains=search_query) ) return queryset from ..serializers.hadis import TransmitterOriginalTextDetailSerializer class TransmitterOriginalTextDetailView(RetrieveAPIView): """API View to get full details of a specific TransmitterOriginalText by slug""" serializer_class = TransmitterOriginalTextDetailSerializer lookup_field = 'slug' lookup_url_kwarg = 'original_text_slug' permission_classes = [AllowAny] @swagger_auto_schema( operation_summary="Get Original Text Detail", operation_description="Returns the full details of a specific original text belonging to a narrator, including its content, translations, and book references.", tags=['Dobodbi - Hadis (V2)'], ) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): return TransmitterOriginalText.objects.all().select_related( 'transmitter' ).prefetch_related( 'references__book_reference__author', 'references__edition', 'references__book_volume', 'references__images' )