Browse Source

transmitters relations added to sync api and creatied new apis for narrators relations. optimized query sending

master
Mohsen Taba 4 weeks ago
parent
commit
5832850a06
  1. 40
      apps/hadis/serializers/hadis.py
  2. 3
      apps/hadis/urls.py
  3. 185
      apps/hadis/views/transmitter.py

40
apps/hadis/serializers/hadis.py

@ -453,11 +453,12 @@ class TransmitterSyncSerializer(serializers.ModelSerializer):
original_texts = serializers.SerializerMethodField()
full_name = LocalizedField()
hadis = serializers.SerializerMethodField()
relations = serializers.SerializerMethodField()
class Meta:
model = Transmitters
fields = [
'id', 'full_name','slug' ,'biographical', 'scholars_opinions', 'original_texts','hadis'
'id', 'full_name','slug' ,'biographical', 'scholars_opinions', 'original_texts','hadis','relations'
]
def get_biographical(self, obj):
@ -540,6 +541,20 @@ class TransmitterSyncSerializer(serializers.ModelSerializer):
# لیست تمام رکوردهای واسط را می‌گیریم.
# استفاده از set باعث می‌شود اگر یک راوی در ۲ سند از یک حدیث بود، آیدی آن حدیث تکراری نشود.
return list(set(item.hadis_id for item in obj.hadises.all() if item.hadis_id))
def get_relations(self, obj):
"""
استخراج آنی آیدی اساتید و شاگردان از کانتکست بدون اجرای کوئری اضافه
"""
teachers_map = self.context.get('teachers_map', {})
students_map = self.context.get('students_map', {})
# حتماً آیدی را به عدد صحیح تبدیل می‌کنیم تا از مچ شدن با دیکشنری مطمئن شویم
obj_id = int(obj.id)
return {
"teachers": list(teachers_map.get(obj_id, set())),
"students": list(students_map.get(obj_id, set()))
}
@ -922,3 +937,26 @@ class NarratorLayerSerializer(serializers.Serializer):
"""Get localized description"""
request = self.context.get('request')
return get_localized_text(obj.description, request=request)
class NarratorRelationSerializer(serializers.ModelSerializer):
"""
سریالایزر سبک برای نمایش راویان به عنوان استاد یا شاگرد
"""
full_name = LocalizedField()
known_as = LocalizedField()
reliability = SerializerMethodField()
class Meta:
model = Transmitters
fields = ['id', 'slug', 'full_name', 'known_as', 'birth_year_hijri', 'death_year_hijri','reliability']
def get_reliability(self, obj):
"""Serialize the reliability foreign key"""
if obj.reliability:
return {
'id': obj.reliability.id,
'title': get_localized_text(obj.reliability.title, self.context.get('request')),
'color': obj.reliability.color,
'main_color_code': obj.reliability.main_color_code,
}

3
apps/hadis/urls.py

@ -4,7 +4,7 @@ from .views.category import HadisCategorySectListView, HadisCategoryTreeView, Ca
from .views.hadis import HadisCollectionListView, HadisListView, HadisBasicView, HadisDetailView, HadisSyncView, HadisTransmittersView, HadisCorrectionsView,HadisMainListView, HadisFiltersView, HadisLayersView,PinnedHadisCollectionListView, HadisFiltersSyncAPIView
from .views.transmitter import (
TransmitterView ,TransmitterDetailView, TransmitterSyncView,TransmitterOpinionView, TransmitterOriginalTextView, TransmitterFiltersView,
NarratorArgumentsListView
NarratorArgumentsListView , NarratorRelationsView
)
from .views.reference import BookDetailView, BookReferencesView, BookReferenceSyncView, BookAttributeView
from .views.reference_v2 import BookReferenceV2DetailView, BookReferenceV2SyncView, BookReferenceV2ListView
@ -92,6 +92,7 @@ urlpatterns = [
path('arguments/filters/', HadisFiltersView.as_view(), name='hadis-filters'),
# Narrator paths
path('narrators/<str:narrator_slug>/relations/', NarratorRelationsView.as_view(), name='narrator-relations'),
path('narrators/<str:narrator_slug>/arguments', NarratorArgumentsListView.as_view(), name='narrator-arguments'),
path('narrators/<str:narrator_slug>/opinions', TransmitterOpinionView.as_view(), name='narrator-opinions'),
path('narrators/<str:narrator_slug>/original_texts', TransmitterOriginalTextView.as_view(), name='narrator-original-texts'),

185
apps/hadis/views/transmitter.py

@ -2,7 +2,15 @@ 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
@ -114,7 +122,7 @@ class TransmitterOriginalTextView(ListAPIView):
class TransmitterSyncView(ListAPIView):
"""
API view to sync all transmitter data for offline mode
Returns all transmitters with biographical data and scholarly opinions
Returns all transmitters with biographical data, opinions, and exact flat relation IDs.
"""
serializer_class = TransmitterSyncSerializer
pagination_class = NoPagination
@ -124,9 +132,6 @@ class TransmitterSyncView(ListAPIView):
return self.list(request, *args, **kwargs)
def get_queryset(self):
"""
Prefetch ALL related data at once
"""
return (
Transmitters.objects
.prefetch_related(
@ -138,11 +143,57 @@ class TransmitterSyncView(ListAPIView):
)
def list(self, request, *args, **kwargs):
# 🧠 ۱. واکشی لینک‌ها با values_list برای جلوگیری از هرگونه تداخل دیکشنری
all_links = HadisTransmitter.objects.values_list(
'hadis_id', 'chain_index', 'order', 'transmitter_id'
)
# 🧠 ۲. بازسازی کل زنجیره‌های حدیثی به صورت کاملاً ایمن و عددی
chains = {}
for hadis_id, chain_index, order, transmitter_id in all_links:
if transmitter_id is None:
continue
key = (int(hadis_id), int(chain_index))
if key not in chains:
chains[key] = {}
chains[key][int(order)] = int(transmitter_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()
serializer = self.get_serializer(queryset, many=True, context={'request': request})
# 🧠 ۴. تزریق اجباری کانتکست به سریالایزر (جلوگیری از اوررایت شدن توسط DRF)
context = self.get_serializer_context()
context['teachers_map'] = teachers_map
context['students_map'] = students_map
serializer = self.get_serializer_class()(
queryset,
many=True,
context=context
)
response_data = {
'count': len(serializer.data), # ← No extra query!
'count': len(serializer.data),
'filters': get_transmitter_filters(request),
'results': serializer.data
}
@ -223,4 +274,124 @@ class NarratorArgumentsListView(ListAPIView):
).select_related(
'category__sect',
'hadis_status'
).distinct().order_by('id')
).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 NarratorRelationsView(APIView):
"""
API view to get the Teachers and Students of a specific narrator
based on their position in Hadith transmission chains.
Optimized to run fully inside the database.
"""
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. ساب‌کوئری برای بررسی اینکه آیا در این سند خاص، این شخص نقش «استاد» را برای راوی اصلی دارد؟
is_teacher_subquery = HadisTransmitter.objects.filter(
transmitter=narrator,
hadis_id=OuterRef('hadis_id'),
chain_index=OuterRef('chain_index'),
order=OuterRef('order') - 1 # استاد یعنی Order او یکی کمتر از راوی تارگت باشد (بالاتر در زنجیره)
)
# 3. ساب‌کوئری برای بررسی نقش «شاگرد»
is_student_subquery = HadisTransmitter.objects.filter(
transmitter=narrator,
hadis_id=OuterRef('hadis_id'),
chain_index=OuterRef('chain_index'),
order=OuterRef('order') + 1 # شاگرد یعنی Order او یکی بیشتر باشد (پایین‌تر در زنجیره)
)
# 4. واکشی سریع آیدی‌ها فقط با اجرای ۲ کوئری داخل خود SQL بدون حلقه پایتون
teacher_ids = HadisTransmitter.objects.annotate(
is_teacher=Exists(is_teacher_subquery)
).filter(is_teacher=True).values_list('transmitter_id', flat=True).distinct()
student_ids = HadisTransmitter.objects.annotate(
is_student=Exists(is_student_subquery)
).filter(is_student=True).values_list('transmitter_id', flat=True).distinct()
# 5. واکشی دیتای نهاییِ راویان با select_related برای جلوگیری از N+1 روی Reliability
teachers_qs = Transmitters.objects.filter(id__in=teacher_ids).select_related('reliability')
students_qs = Transmitters.objects.filter(id__in=student_ids).select_related('reliability')
# 6. ساخت خروجی
teachers_data = NarratorRelationSerializer(teachers_qs, many=True, context={'request': request}).data
students_data = NarratorRelationSerializer(students_qs, many=True, context={'request': request}).data
return Response({
"narrator_slug": narrator.slug,
"teachers_count": len(teachers_data),
"students_count": len(students_data),
"teachers": teachers_data,
"students": students_data
})
Loading…
Cancel
Save