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 from ..serializers import TransmitterSerializer , TransmitterDetailSerializer, TransmitterSyncSerializer,TransmitterOpinionSerializer, TransmitterOriginalTextSerializer, 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) if search_query: queryset = queryset.filter( Q(full_name__icontains=search_query) | Q(kunya__icontains=search_query) | Q(known_as__icontains=search_query) | Q(nickname__icontains=search_query) ) # 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 = TransmitterOriginalTextSerializer 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' # Essential if serializer includes transmitter data ).prefetch_related( # Add any nested relations from TransmitterOriginalTextSerializer # 'translations', # 'manuscript_references', ) 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__authors', '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'): # Get the English title for the slug if reliability.title: reliabilities.append({ 'text': get_localized_text(reliability.title, request), 'slug': reliability.slug }) 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 # 👈 حتما این دو را ایمپورت کنید 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)'], ) 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: queryset = queryset.filter( 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)'], ) 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: queryset = queryset.filter( 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] def get_queryset(self): return TransmitterOriginalText.objects.all().select_related( 'transmitter' ).prefetch_related( 'references__book_reference__authors', 'references__edition', 'references__book_volume', 'references__images' )