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
21 KiB

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:
q_filter = (
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:
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)'],
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:
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]
@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'
)