Browse Source

narrator api enhanced. relations added and populated

master
Mohsen Taba 3 weeks ago
parent
commit
1fa8bc04c2
  1. 105
      apps/hadis/management/commands/pop_data.py
  2. 23
      apps/hadis/migrations/0027_transmitterrelative_relation_value_and_more.py
  3. 69
      apps/hadis/models/transmitter.py
  4. 27
      apps/hadis/serializers/hadis.py
  5. 5
      apps/hadis/urls.py
  6. 91
      apps/hadis/views/transmitter.py

105
apps/hadis/management/commands/pop_data.py

@ -0,0 +1,105 @@
# backend/apps/hadis/management/commands/populate_remaining_data.py
import random
from django.core.management.base import BaseCommand
from django.db import transaction
from django.db.models import Q
from apps.hadis.models import Transmitters, TransmitterRelative, TransmitterOpinion
from apps.hadis.models.transmitter import RELATION_TYPE_MAP
class Command(BaseCommand):
help = 'Populates missing companion_type, default Arabic opinions, and generates 10 random relatives.'
def handle(self, *args, **options):
self.stdout.write(self.style.WARNING("Starting population script..."))
# ==========================================
# 1. Populate Companion Type
# ==========================================
comp_updated = Transmitters.objects.filter(
Q(companion_type__isnull=True) | Q(companion_type="")
).update(companion_type="al-A'la ibn Wasil")
self.stdout.write(self.style.SUCCESS(f"✅ Updated {comp_updated} narrators with companion_type."))
# ==========================================
# 2. Populate Transmitter Opinions (Arabic Text)
# ==========================================
opinions = TransmitterOpinion.objects.all()
opinions_to_update = []
default_ar_text = {"language_code": "ar", "text": "ثقة، وهو أثبت من عبد الرحمن بن مهدي."}
for op in opinions:
if not op.opinion_text:
op.opinion_text = [default_ar_text]
opinions_to_update.append(op)
elif isinstance(op.opinion_text, list):
# بررسی اینکه آیا رکورد عربی وجود دارد یا خیر
has_ar = any(isinstance(item, dict) and item.get('language_code') == 'ar' for item in op.opinion_text)
if not has_ar:
op.opinion_text.append(default_ar_text)
opinions_to_update.append(op)
if opinions_to_update:
TransmitterOpinion.objects.bulk_update(opinions_to_update, ['opinion_text'], batch_size=500)
self.stdout.write(self.style.SUCCESS(f"✅ Updated {len(opinions_to_update)} opinions with default Arabic text."))
else:
self.stdout.write(self.style.SUCCESS("✅ All opinions already have Arabic text."))
# ==========================================
# 3. Populate Relatives (10 per narrator: 5, 4, 1)
# ==========================================
# پیدا کردن راویانی که هیچ نسبتی برایشان ثبت نشده است
narrators_without_relatives = Transmitters.objects.filter(relatives__isnull=True).distinct()
count = narrators_without_relatives.count()
if count == 0:
self.stdout.write(self.style.SUCCESS("✅ All narrators already have relatives."))
return
all_transmitter_ids = list(Transmitters.objects.values_list('id', flat=True))
if not all_transmitter_ids:
self.stdout.write(self.style.ERROR("❌ No transmitters found in DB to link."))
return
# 10 اسم رندوم پیش‌فرض
NAMES = ["عمر", "علي", "فاطمة", "خديجة", "زينب", "حسن", "حسين", "محمد", "أحمد", "عائشة"]
relation_choices = [choice[0] for choice in TransmitterRelative.RelationTypeChoices.choices]
new_relatives = []
for narrator in narrators_without_relatives:
group_sizes = [5, 4, 1]
# انتخاب ۳ نوع رابطه رندوم و متفاوت برای این راوی
selected_types = random.sample(relation_choices, 3)
# بر هم زدن لیست اسم‌ها
shuffled_names = random.sample(NAMES, 10)
# انتخاب ۳ ایندکس رندوم (از بین 0 تا 9) که قرار است به راویان دیگر متصل شوند
linked_indices = set(random.sample(range(10), 3))
current_idx = 0
for i, size in enumerate(group_sizes):
rel_type = selected_types[i]
rel_val = RELATION_TYPE_MAP.get(rel_type, [])
for _ in range(size):
linked_narrator_id = None
# اگر ایندکس فعلی جزو ۳ ایندکس خوش‌شانس بود، یک راوی رندوم به آن متصل کن
if current_idx in linked_indices:
linked_narrator_id = random.choice(all_transmitter_ids)
new_relatives.append(
TransmitterRelative(
related_to=narrator,
relation_type=rel_type,
relation_value=rel_val, # اعمال دستی به دلیل استفاده از bulk_create
name=shuffled_names[current_idx],
narrator_id=linked_narrator_id
)
)
current_idx += 1
with transaction.atomic():
TransmitterRelative.objects.bulk_create(new_relatives, batch_size=1000)
self.stdout.write(self.style.SUCCESS(f"🎉 Successfully created {len(new_relatives)} relatives for {count} narrators!"))

23
apps/hadis/migrations/0027_transmitterrelative_relation_value_and_more.py

@ -0,0 +1,23 @@
# Generated by Django 5.2.12 on 2026-06-28 14:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hadis', '0026_transmitterrelative'),
]
operations = [
migrations.AddField(
model_name='transmitterrelative',
name='relation_value',
field=models.JSONField(default=list, verbose_name='Relation Value'),
),
migrations.AlterField(
model_name='transmitterrelative',
name='relation_type',
field=models.CharField(choices=[('stepfather', 'Stepfather'), ('stepmother', 'Stepmother'), ('uncles_and_aunts', 'Uncles and aunts'), ('siblings', 'Siblings'), ('parents', 'Parents'), ('nephews_and_nieces', 'Nephews and nieces'), ('children', 'Children'), ('stepchildren', 'Stepchildren'), ('spouses', 'Spouses'), ('cousins', 'Cousins'), ('granduncles_and_grandaunts', 'Granduncles and grandaunts'), ('grandnephews_and_grandnieces', 'Grandnephews and grandnieces'), ('grandchildren_and_great_grandchildren', 'Grandchildren and great-grandchildren'), ('grandparents', 'Grandparents'), ('foster_mother', 'Foster mother'), ('tribe', 'Tribe'), ('foster_children', 'Foster children'), ('in_laws', 'In-laws'), ('students', 'Students'), ('owners_and_masters', 'Owners and masters'), ('other_relatives', 'Other relatives'), ('friends_and_companions', 'Friends and companions'), ('allies', 'Allies'), ('neighbors', 'Neighbors'), ('wards_and_proteges', 'Wards and proteges'), ('cousins_of_parents', 'Cousins of parents'), ('cousins_of_grandparents', 'Cousins of grandparents'), ('cousins_children', 'Cousins children'), ('cousins_grandchildren_and_great_grandchildren', 'Cousins grandchildren and great-grandchildren')], max_length=50, verbose_name='Relation Type'),
),
]

69
apps/hadis/models/transmitter.py

@ -798,10 +798,69 @@ class OriginalTextReferenceImage(models.Model):
priority = models.IntegerField(default=0)
RELATION_TYPE_MAP = {
'stepfather': [{'language_code': 'ru', 'text': 'Отчим'}, {'language_code': 'ar', 'text': 'زوج الأم'}, {'language_code': 'en', 'text': 'Stepfather'}],
'stepmother': [{'language_code': 'ru', 'text': 'Мачеха'}, {'language_code': 'ar', 'text': 'زوجة الأب'}, {'language_code': 'en', 'text': 'Stepmother'}],
'uncles_and_aunts': [{'language_code': 'ru', 'text': 'Дяди и тёти'}, {'language_code': 'ar', 'text': 'الأعمام والعمات والأخوال والخالات'}, {'language_code': 'en', 'text': 'Uncles and aunts'}],
'siblings': [{'language_code': 'ru', 'text': 'Сиблинги'}, {'language_code': 'ar', 'text': 'الإخوة'}, {'language_code': 'en', 'text': 'Siblings'}],
'parents': [{'language_code': 'ru', 'text': 'Родители'}, {'language_code': 'ar', 'text': 'الوالدان'}, {'language_code': 'en', 'text': 'Parents'}],
'nephews_and_nieces': [{'language_code': 'ru', 'text': 'Племянники'}, {'language_code': 'ar', 'text': 'أبناء الإخوة'}, {'language_code': 'en', 'text': 'Nephews and nieces'}],
'children': [{'language_code': 'ru', 'text': 'Дети'}, {'language_code': 'ar', 'text': 'الأولاد'}, {'language_code': 'en', 'text': 'Children'}],
'stepchildren': [{'language_code': 'ru', 'text': 'Пасынки и падчерицы'}, {'language_code': 'ar', 'text': 'الربائب'}, {'language_code': 'en', 'text': 'Stepchildren'}],
'spouses': [{'language_code': 'ru', 'text': 'Супруги'}, {'language_code': 'ar', 'text': 'الأزواج'}, {'language_code': 'en', 'text': 'Spouses'}],
'cousins': [{'language_code': 'ru', 'text': 'Двоюродные братья и сестры'}, {'language_code': 'ar', 'text': 'أبناء العمومة والخؤولة'}, {'language_code': 'en', 'text': 'Cousins'}],
'granduncles_and_grandaunts': [{'language_code': 'ru', 'text': 'Великие дяди и тети'}, {'language_code': 'ar', 'text': 'إخوة الأجداد'}, {'language_code': 'en', 'text': 'Granduncles and grandaunts'}],
'grandnephews_and_grandnieces': [{'language_code': 'ru', 'text': 'Внучатые племянники'}, {'language_code': 'ar', 'text': 'أحفاد الإخوة'}, {'language_code': 'en', 'text': 'Grandnephews and grandnieces'}],
'grandchildren_and_great_grandchildren': [{'language_code': 'ru', 'text': 'Внуки и правнуки'}, {'language_code': 'ar', 'text': 'الأحفاد وأبناء الأحفاد'}, {'language_code': 'en', 'text': 'Grandchildren and great-grandchildren'}],
'grandparents': [{'language_code': 'ru', 'text': 'Прародители'}, {'language_code': 'ar', 'text': 'الأجداد'}, {'language_code': 'en', 'text': 'Grandparents'}],
'foster_mother': [{'language_code': 'ru', 'text': 'Молочная мать'}, {'language_code': 'ar', 'text': 'المرضعة'}, {'language_code': 'en', 'text': 'Foster mother'}],
'tribe': [{'language_code': 'ru', 'text': 'Племя'}, {'language_code': 'ar', 'text': 'القبيلة'}, {'language_code': 'en', 'text': 'Tribe'}],
'foster_children': [{'language_code': 'ru', 'text': 'Молочные дети'}, {'language_code': 'ar', 'text': 'الرضاء'}, {'language_code': 'en', 'text': 'Foster children'}],
'in_laws': [{'language_code': 'ru', 'text': 'Свойственники'}, {'language_code': 'ar', 'text': 'الأصهار والأختان'}, {'language_code': 'en', 'text': 'In-laws'}],
'students': [{'language_code': 'ru', 'text': 'Ученики'}, {'language_code': 'ar', 'text': 'التلاميذ'}, {'language_code': 'en', 'text': 'Students'}],
'owners_and_masters': [{'language_code': 'ru', 'text': 'Хозяева и владельцы'}, {'language_code': 'ar', 'text': 'الملاك'}, {'language_code': 'en', 'text': 'Owners and masters'}],
'other_relatives': [{'language_code': 'ru', 'text': 'Другие родственники'}, {'language_code': 'ar', 'text': 'الأقارب'}, {'language_code': 'en', 'text': 'Other relatives'}],
'friends_and_companions': [{'language_code': 'ru', 'text': 'Друзья и сподвижники'}, {'language_code': 'ar', 'text': 'الأصحاب'}, {'language_code': 'en', 'text': 'Friends and companions'}],
'allies': [{'language_code': 'ru', 'text': 'Союзники'}, {'language_code': 'ar', 'text': 'الحلفاء'}, {'language_code': 'en', 'text': 'Allies'}],
'neighbors': [{'language_code': 'ru', 'text': 'Соседи'}, {'language_code': 'ar', 'text': 'الجيران'}, {'language_code': 'en', 'text': 'Neighbors'}],
'wards_and_proteges': [{'language_code': 'ru', 'text': 'Подопечные и воспитанники'}, {'language_code': 'ar', 'text': 'المحضونون'}, {'language_code': 'en', 'text': 'Wards and proteges'}],
'cousins_of_parents': [{'language_code': 'ru', 'text': 'Двоюродные дяди и тети'}, {'language_code': 'ar', 'text': 'أبناء عمومة الآباء'}, {'language_code': 'en', 'text': 'Cousins of parents'}],
'cousins_of_grandparents': [{'language_code': 'ru', 'text': 'Двоюродные дедушки и бабушки'}, {'language_code': 'ar', 'text': 'أبناء عمومة الأجداد'}, {'language_code': 'en', 'text': 'Cousins of grandparents'}],
'cousins_children': [{'language_code': 'ru', 'text': 'Двоюродные племянники'}, {'language_code': 'ar', 'text': 'أبناء أبناء العمومة'}, {'language_code': 'en', 'text': 'Cousins children'}],
'cousins_grandchildren_and_great_grandchildren': [{'language_code': 'ru', 'text': 'Двоюродные внуки и правнуки'}, {'language_code': 'ar', 'text': 'أحفاد أبناء العمومة'}, {'language_code': 'en', 'text': 'Cousins grandchildren and great-grandchildren'}],
}
class TransmitterRelative(models.Model):
class RelationTypeChoices(models.TextChoices):
FATHER = 'father', _('Father')
SON = 'son', _('Son')
STEPFATHER = 'stepfather', _('Stepfather')
STEPMOTHER = 'stepmother', _('Stepmother')
UNCLES_AND_AUNTS = 'uncles_and_aunts', _('Uncles and aunts')
SIBLINGS = 'siblings', _('Siblings')
PARENTS = 'parents', _('Parents')
NEPHEWS_AND_NIECES = 'nephews_and_nieces', _('Nephews and nieces')
CHILDREN = 'children', _('Children')
STEPCHILDREN = 'stepchildren', _('Stepchildren')
SPOUSES = 'spouses', _('Spouses')
COUSINS = 'cousins', _('Cousins')
GRANDUNCLES_AND_GRANDAUNTS = 'granduncles_and_grandaunts', _('Granduncles and grandaunts')
GRANDNEPHEWS_AND_GRANDNIECES = 'grandnephews_and_grandnieces', _('Grandnephews and grandnieces')
GRANDCHILDREN_AND_GREAT_GRANDCHILDREN = 'grandchildren_and_great_grandchildren', _('Grandchildren and great-grandchildren')
GRANDPARENTS = 'grandparents', _('Grandparents')
FOSTER_MOTHER = 'foster_mother', _('Foster mother')
TRIBE = 'tribe', _('Tribe')
FOSTER_CHILDREN = 'foster_children', _('Foster children')
IN_LAWS = 'in_laws', _('In-laws')
STUDENTS = 'students', _('Students')
OWNERS_AND_MASTERS = 'owners_and_masters', _('Owners and masters')
OTHER_RELATIVES = 'other_relatives', _('Other relatives')
FRIENDS_AND_COMPANIONS = 'friends_and_companions', _('Friends and companions')
ALLIES = 'allies', _('Allies')
NEIGHBORS = 'neighbors', _('Neighbors')
WARDS_AND_PROTEGES = 'wards_and_proteges', _('Wards and proteges')
COUSINS_OF_PARENTS = 'cousins_of_parents', _('Cousins of parents')
COUSINS_OF_GRANDPARENTS = 'cousins_of_grandparents', _('Cousins of grandparents')
COUSINS_CHILDREN = 'cousins_children', _('Cousins children')
COUSINS_GRANDCHILDREN_AND_GREAT_GRANDCHILDREN = 'cousins_grandchildren_and_great_grandchildren', _('Cousins grandchildren and great-grandchildren')
related_to = models.ForeignKey(
Transmitters,
@ -814,6 +873,7 @@ class TransmitterRelative(models.Model):
choices=RelationTypeChoices.choices,
verbose_name=_('Relation Type')
)
relation_value = models.JSONField(default=list, verbose_name=_('Relation Value'))
name = models.CharField(max_length=255, verbose_name=_('Name'))
narrator = models.ForeignKey(
Transmitters,
@ -832,5 +892,10 @@ class TransmitterRelative(models.Model):
verbose_name_plural = _('Transmitter Relatives')
ordering = ['relation_type', 'name']
def save(self, *args, **kwargs):
if self.relation_type and not self.relation_value:
self.relation_value = RELATION_TYPE_MAP.get(self.relation_type, [])
super().save(*args, **kwargs)
def __str__(self):
return f"{self.name} ({self.get_relation_type_display()} of {self.related_to})"

27
apps/hadis/serializers/hadis.py

@ -380,12 +380,13 @@ class TransmitterOpinionSerializer(serializers.ModelSerializer):
""" Serializer for TransmitterOpinions """
scholar_name = LocalizedField()
opinion_text = LocalizedField()
arabic_opinion_text = ArabicLocalizedField(source='opinion_text')
status = serializers.SerializerMethodField()
scholar = serializers.SerializerMethodField()
class Meta:
model = TransmitterOpinion
fields = ['id','transmitter','scholar_name','scholar','opinion_text','status']
fields = ['id','transmitter','scholar_name','scholar','opinion_text','arabic_opinion_text','status']
def get_scholar(self, obj):
request = self.context.get('request')
@ -418,7 +419,7 @@ class TransmitterOriginalTextSerializer(serializers.ModelSerializer):
class Meta:
model = TransmitterOriginalText
fields = ['id', 'title', 'text', 'translation', 'share_link']
fields = ['id', 'title', 'text', 'translation', 'share_link', 'slug']
class TransmitterDetailSerializer(serializers.ModelSerializer):
@ -473,6 +474,7 @@ class TransmitterDetailSerializer(serializers.ModelSerializer):
return None
def get_relatives(self, obj):
request = self.context.get('request')
relatives_qs = obj.relatives.all()
grouped = {}
@ -483,7 +485,6 @@ class TransmitterDetailSerializer(serializers.ModelSerializer):
# Use narrator's localized name if exists and name is empty, otherwise use the direct name
name = rel.name
if rel.narrator and not name:
request = self.context.get('request')
name = get_localized_text(rel.narrator.full_name, request)
item = {
@ -492,15 +493,21 @@ class TransmitterDetailSerializer(serializers.ModelSerializer):
}
if rel_type not in grouped:
grouped[rel_type] = []
grouped[rel_type].append(item)
grouped[rel_type] = {
"relation_value": get_localized_text(rel.relation_value, request) if rel.relation_value else None,
"arabic_relation_value": get_arabic_localized_text(rel.relation_value) if rel.relation_value else None,
"items": []
}
grouped[rel_type]["items"].append(item)
result = []
for rel_type, items in grouped.items():
if rel_type == 'father' and items:
result.append({"father": items[0]})
else:
result.append({rel_type: items})
for rel_type, data in grouped.items():
result.append({
"relation_type": rel_type,
"relation_value": data["relation_value"],
"arabic_relation_value": data["arabic_relation_value"],
"members": data["items"]
})
return result

5
apps/hadis/urls.py

@ -7,7 +7,7 @@ from .views.hadis import (
)
from .views.transmitter import (
TransmitterView ,TransmitterDetailView, TransmitterSyncView,TransmitterOpinionView, TransmitterOriginalTextView, TransmitterFiltersView,
NarratorArgumentsListView , NarratorRelationsView,TransmitterOriginalTextDetailView, TransmitterOriginalTextDetailView
NarratorArgumentsListView , NarratorTeachersView, NarratorStudentsView, TransmitterOriginalTextDetailView
)
from .views.reference import BookDetailView, BookReferencesView, BookReferenceSyncView, BookAttributeView
from .views.reference_v2 import BookReferenceV2DetailView, BookReferenceV2SyncView, BookReferenceV2ListView
@ -95,7 +95,8 @@ 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>/teachers/', NarratorTeachersView.as_view(), name='narrator-teachers'),
path('narrators/<str:narrator_slug>/students/', NarratorStudentsView.as_view(), name='narrator-students'),
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'),

91
apps/hadis/views/transmitter.py

@ -354,63 +354,98 @@ class NarratorArgumentsListView(ListAPIView):
from django.db.models import Exists, OuterRef # 👈 حتما این دو را ایمپورت کنید
class NarratorRelationsView(APIView):
class NarratorTeachersView(ListAPIView):
"""
API view to get the Teachers and Students of a specific narrator
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 and Students",
operation_description="Analyzes transmission chains to find the teachers (order + 1) and students (order - 1) of a narrator.",
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, narrator_slug, *args, **kwargs):
# 1. پیدا کردن راوی اصلی
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)
# 2. ساب‌کوئری برای بررسی اینکه آیا در این سند خاص، این شخص نقش «استاد» را برای راوی اصلی دارد؟
is_teacher_subquery = HadisTransmitter.objects.filter(
transmitter=narrator,
hadis_id=OuterRef('hadis_id'),
chain_index=OuterRef('chain_index'),
order=OuterRef('order') - 1 # استاد یعنی Order او یکی کمتر از راوی تارگت باشد (بالاتر در زنجیره)
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)
# 3. ساب‌کوئری برای بررسی نقش «شاگرد»
is_student_subquery = HadisTransmitter.objects.filter(
transmitter=narrator,
hadis_id=OuterRef('hadis_id'),
chain_index=OuterRef('chain_index'),
order=OuterRef('order') + 1 # شاگرد یعنی Order او یکی بیشتر باشد (پایین‌تر در زنجیره)
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')
queryset = 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
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 Response({
"narrator_slug": narrator.slug,
"teachers_count": len(teachers_data),
"students_count": len(students_data),
"teachers": teachers_data,
"students": students_data
})
return queryset
from ..serializers.hadis import TransmitterOriginalTextDetailSerializer

Loading…
Cancel
Save