Browse Source

narrators sons and fathers added

master
Mohsen Taba 3 weeks ago
parent
commit
b9cf002fe1
  1. 91
      apps/hadis/management/commands/assign_random_transmitter_families.py
  2. 265
      apps/hadis/management/commands/generate_editions_and_volumes.py
  3. 64
      apps/hadis/serializers/hadis.py
  4. 71
      apps/hadis/serializers/serializers_admin.py

91
apps/hadis/management/commands/assign_random_transmitter_families.py

@ -0,0 +1,91 @@
import random
from django.core.management.base import BaseCommand
from apps.hadis.models.transmitter import Transmitters, TransmitterRelative, RELATION_TYPE_MAP
class Command(BaseCommand):
help = "Assign 1 random father and 2 random sons to every transmitter."
def add_arguments(self, parser):
parser.add_argument(
"--clear",
action="store_true",
help="Clear existing father/relative children relationships before assigning.",
)
def handle(self, *args, **options):
transmitters = list(Transmitters.objects.all())
total_count = len(transmitters)
if total_count < 4:
self.stdout.write(
self.style.ERROR(
f"Not enough transmitters found (found {total_count}, need at least 4)."
)
)
return
self.stdout.write(
self.style.NOTICE(
f"Starting random family assignment for {total_count} transmitters..."
)
)
relatives_to_create = []
updated_count = 0
for tr in transmitters:
# Available candidates excluding self
candidates = [t for t in transmitters if t.id != tr.id]
# 1. Pick 1 random father
father = random.choice(candidates)
tr.father = father
tr.save(update_fields=["father"])
# Remove father from candidates for sons
son_candidates = [t for t in candidates if t.id != father.id]
if len(son_candidates) < 2:
son_candidates = candidates
# 2. Pick 2 random sons
sons = random.sample(son_candidates, 2)
# Clear old children relatives for this transmitter
TransmitterRelative.objects.filter(
related_to=tr, relation_type="children"
).delete()
# Prepare 2 children relatives
for son in sons:
son_name = ""
if son.full_name and isinstance(son.full_name, list) and len(son.full_name) > 0:
first = son.full_name[0]
if isinstance(first, dict):
son_name = first.get("text", "")
if not son_name and hasattr(son, "get_name"):
son_name = son.get_name("en") or son.get_name("ar") or f"Narrator {son.id}"
relatives_to_create.append(
TransmitterRelative(
related_to=tr,
relation_type="children",
relation_value=RELATION_TYPE_MAP.get("children", []),
name=son_name,
narrator=son,
)
)
updated_count += 1
if updated_count % 10 == 0:
self.stdout.write(f"Processed {updated_count}/{total_count}...")
if relatives_to_create:
TransmitterRelative.objects.bulk_create(relatives_to_create)
self.stdout.write(
self.style.SUCCESS(
f"Successfully assigned 1 father and 2 sons to all {updated_count} transmitters!"
)
)

265
apps/hadis/management/commands/generate_editions_and_volumes.py

@ -0,0 +1,265 @@
import os
import random
import urllib.request
from django.core.management.base import BaseCommand
from django.db import transaction
from django.core.files.base import ContentFile
from apps.hadis.models import (
BookReference,
BookAuthor,
BookEdition,
BookVolume,
BookResearcher,
BookEditor,
)
class Command(BaseCommand):
help = 'Generates randomized Editions and Volumes (with sample cover image and PDF file) for all BookReferences.'
def add_arguments(self, parser):
parser.add_argument(
'--clear',
action='store_true',
default=True,
help='Clear existing BookEditions and BookVolumes before generating (default: True)',
)
parser.add_argument(
'--keep-existing',
action='store_true',
help='Do not clear existing data, only add/modify',
)
def _fetch_sample_assets(self):
"""
Downloads a lightweight Islamic cover image and sample PDF from the web.
Falls back to generated placeholder bytes if network request fails.
"""
self.stdout.write("Downloading sample Islamic book image and sample PDF...")
image_url = "https://images.unsplash.com/photo-1609599006353-e629aaabfeae?w=500&auto=format&fit=crop&q=80"
image_bytes = None
try:
req = urllib.request.Request(image_url, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'})
with urllib.request.urlopen(req, timeout=15) as resp:
image_bytes = resp.read()
self.stdout.write(self.style.SUCCESS(f"Downloaded sample cover image ({len(image_bytes)} bytes)."))
except Exception as e:
self.stdout.write(self.style.WARNING(f"Failed to download image ({e}), generating a local fallback image..."))
from PIL import Image, ImageDraw
import io
img = Image.new('RGB', (400, 600), color='#1e293b')
draw = ImageDraw.Draw(img)
draw.rectangle([20, 20, 380, 580], outline='#e2e8f0', width=3)
buf = io.BytesIO()
img.save(buf, format='JPEG')
image_bytes = buf.getvalue()
pdf_url = "https://raw.githubusercontent.com/mozilla/pdf.js/master/examples/learning/helloworld.pdf"
pdf_bytes = None
try:
req = urllib.request.Request(pdf_url, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'})
with urllib.request.urlopen(req, timeout=15) as resp:
pdf_bytes = resp.read()
self.stdout.write(self.style.SUCCESS(f"Downloaded sample PDF document ({len(pdf_bytes)} bytes)."))
except Exception as e:
self.stdout.write(self.style.WARNING(f"Failed to download PDF ({e}), generating a fallback PDF..."))
from reportlab.pdfgen import canvas
import io
buf = io.BytesIO()
p = canvas.Canvas(buf)
p.drawString(100, 750, "Sample Islamic Hadith Reference Document")
p.drawString(100, 720, "This is a lightweight sample volume file.")
p.showPage()
p.save()
pdf_bytes = buf.getvalue()
return image_bytes, pdf_bytes
def handle(self, *args, **options):
clear_existing = not options.get('keep_existing', False)
references = list(BookReference.objects.all())
authors = list(BookAuthor.objects.all())
if not references:
self.stdout.write(self.style.ERROR("No BookReference found in the database."))
return
self.stdout.write(f"Found {len(references)} BookReferences and {len(authors)} BookAuthors.")
if clear_existing:
self.stdout.write(self.style.WARNING("Clearing existing BookEditions and BookVolumes..."))
BookVolume.objects.all().delete()
BookEdition.objects.all().delete()
BookResearcher.objects.all().delete()
BookEditor.objects.all().delete()
# Download or prepare sample assets
image_bytes, pdf_bytes = self._fetch_sample_assets()
# Publishers pool
publishers_pool = [
[
{"language_code": "fa", "text": "دار الحديث"},
{"language_code": "ar", "text": "دار الحديث للطباعة والنشر"},
{"language_code": "en", "text": "Dar al-Hadith Publications"},
],
[
{"language_code": "fa", "text": "مؤسسة آل البيت (ع) لإحياء التراث"},
{"language_code": "ar", "text": "مؤسسة آل البيت عليهم السلام لإحياء التراث"},
{"language_code": "en", "text": "Aal al-Bayt Institute for Heritage Revival"},
],
[
{"language_code": "fa", "text": "دار الكتب الإسلامية"},
{"language_code": "ar", "text": "دار الكتب الإسلامية"},
{"language_code": "en", "text": "Dar al-Kutub al-Islamiyya"},
],
[
{"language_code": "fa", "text": "منشورات الشريف الرضي"},
{"language_code": "ar", "text": "منشورات الشريف الرضي"},
{"language_code": "en", "text": "Al-Sharif al-Razi Publications"},
],
[
{"language_code": "fa", "text": "مؤسسة النشر الإسلامي"},
{"language_code": "ar", "text": "مؤسسة النشر الإسلامي التابعة لجماعة المدرسين"},
{"language_code": "en", "text": "Islamic Publishing Foundation"},
],
[
{"language_code": "fa", "text": "مؤسسة البلاغ"},
{"language_code": "ar", "text": "مؤسسة البلاغ للطباعة والنشر والتوزيع"},
{"language_code": "en", "text": "Al-Balagh Foundation"},
],
]
cities_pool = [
[{"language_code": "fa", "text": "قم"}, {"language_code": "ar", "text": "قم المقدسة"}, {"language_code": "en", "text": "Qom"}],
[{"language_code": "fa", "text": "بیروت"}, {"language_code": "ar", "text": "بيروت"}, {"language_code": "en", "text": "Beirut"}],
[{"language_code": "fa", "text": "نجف"}, {"language_code": "ar", "text": "النجف الأشرف"}, {"language_code": "en", "text": "Najaf"}],
[{"language_code": "fa", "text": "تهران"}, {"language_code": "ar", "text": "طهران"}, {"language_code": "en", "text": "Tehran"}],
[{"language_code": "fa", "text": "مشهد"}, {"language_code": "ar", "text": "مشهد المقدسة"}, {"language_code": "en", "text": "Mashhad"}],
]
countries_pool = [
[{"language_code": "fa", "text": "ایران"}, {"language_code": "ar", "text": "إيران"}, {"language_code": "en", "text": "Iran"}],
[{"language_code": "fa", "text": "لبنان"}, {"language_code": "ar", "text": "لبنان"}, {"language_code": "en", "text": "Lebanon"}],
[{"language_code": "fa", "text": "عراق"}, {"language_code": "ar", "text": "العراق"}, {"language_code": "en", "text": "Iraq"}],
]
edition_names = ["چاپ اول", "چاپ دوم", "چاپ سوم", "طبعة ثانية منقحة", "طبعة أولى محققة", "First Revised Edition", "Second Edition"]
years_pool = ["1418 هـ.ق", "1422 هـ.ق", "1429 هـ.ق", "1436 هـ.ق", "1441 هـ.ق", "1385 ش", "1392 ش", "1398 ش", "1402 ش"]
total_editions_created = 0
total_volumes_created = 0
total_researchers_created = 0
total_editors_created = 0
self.stdout.write("Generating Editions and Volumes for BookReferences...")
with transaction.atomic():
for ref in references:
# Random choice: whether this reference has editions or only direct volumes
has_editions = random.choice([True, False])
ref.has_editions = has_editions
ref.save(update_fields=['has_editions'])
if has_editions:
num_editions = random.randint(1, 3)
for ed_idx in range(1, num_editions + 1):
num_volumes = random.randint(3, 5)
pub = random.choice(publishers_pool)
city = random.choice(cities_pool)
country = random.choice(countries_pool)
ed_num = random.choice(edition_names)
year = random.choice(years_pool)
isbn = f"978-964-{random.randint(100, 999)}-{random.randint(10, 99)}-{random.randint(0, 9)}"
edition = BookEdition.objects.create(
book_reference=ref,
publisher=pub,
city_of_publication=city,
country_of_publication=country,
edition_number=f"{ed_num} ({ed_idx})",
year_of_publication=year,
number_of_volumes=num_volumes,
isbn=isbn,
notes=[
{"language_code": "fa", "text": f"نسخه معتبر تصحیح شده با مقابله با نسخ خطی {ed_idx}"},
{"language_code": "en", "text": f"Verified critical edition compared against manuscripts {ed_idx}"}
],
source_url=f"https://lib.eshia.ir/{ref.id}/{edition_names[0]}"
)
total_editions_created += 1
# Assign random researchers and editors from existing authors
if authors:
sample_researchers = random.sample(authors, min(len(authors), random.randint(1, 2)))
for author in sample_researchers:
BookResearcher.objects.create(
book_edition=edition,
author=author
)
total_researchers_created += 1
sample_editors = random.sample(authors, min(len(authors), random.randint(1, 2)))
for author in sample_editors:
BookEditor.objects.create(
book_edition=edition,
author=author
)
total_editors_created += 1
# Create 3 to 5 volumes for this edition
for vol_idx in range(1, num_volumes + 1):
vol = BookVolume(
book_reference=ref,
edition=edition,
title=f"جلد {vol_idx}",
)
vol.image.save(
f"ref_{ref.id}_ed_{edition.id}_vol_{vol_idx}_cover.jpg",
ContentFile(image_bytes),
save=False
)
vol.file.save(
f"ref_{ref.id}_ed_{edition.id}_vol_{vol_idx}.pdf",
ContentFile(pdf_bytes),
save=False
)
vol.save()
total_volumes_created += 1
else:
# Direct volumes without editions
num_volumes = random.randint(3, 5)
for vol_idx in range(1, num_volumes + 1):
vol = BookVolume(
book_reference=ref,
edition=None,
title=f"جلد {vol_idx}",
)
vol.image.save(
f"ref_{ref.id}_vol_{vol_idx}_cover.jpg",
ContentFile(image_bytes),
save=False
)
vol.file.save(
f"ref_{ref.id}_vol_{vol_idx}.pdf",
ContentFile(pdf_bytes),
save=False
)
vol.save()
total_volumes_created += 1
self.stdout.write(
self.style.SUCCESS(
f"\n--- Seeding Completed Successfully ---\n"
f"Processed {len(references)} BookReferences:\n"
f" - Total BookEditions created: {total_editions_created}\n"
f" - Total BookVolumes created: {total_volumes_created}\n"
f" - Total Researchers assigned: {total_researchers_created}\n"
f" - Total Editors assigned: {total_editors_created}\n"
f" - Cover image & PDF attached to all volumes."
)
)

64
apps/hadis/serializers/hadis.py

@ -585,6 +585,8 @@ class TransmitterDetailSerializer(serializers.ModelSerializer):
description = LocalizedField()
reliability = serializers.SerializerMethodField()
relatives = serializers.SerializerMethodField()
father = serializers.SerializerMethodField()
sons = serializers.SerializerMethodField()
share_link = serializers.CharField(read_only=True)
class Meta:
@ -596,9 +598,69 @@ class TransmitterDetailSerializer(serializers.ModelSerializer):
'death_year_hijri','age_at_death','reliability',
'madhhab',"in_sahih_muslim","in_sahih_bukhari",
"description",'generation','share_link',
'tadlis', 'ikhtilat', 'companion_type', 'relatives'
'tadlis', 'ikhtilat', 'companion_type', 'relatives',
'father', 'sons'
]
def get_father(self, obj):
"""Serialize the father foreign key or relative"""
father_obj = obj.father
request = self.context.get('request')
if not father_obj:
parent_rel = obj.relatives.filter(relation_type__in=['parents', 'stepfather']).first()
if parent_rel:
if parent_rel.narrator:
father_obj = parent_rel.narrator
elif parent_rel.name:
return {
'id': None,
'name': parent_rel.name,
'arabic_name': parent_rel.name,
'slug': None,
}
if father_obj:
return {
'id': father_obj.id,
'name': get_localized_text(father_obj.full_name, request) or (father_obj.get_name('en') if hasattr(father_obj, 'get_name') else None) or '',
'arabic_name': get_arabic_localized_text(father_obj.full_name) or (father_obj.get_name('ar') if hasattr(father_obj, 'get_name') else None),
'slug': father_obj.slug,
}
return None
def get_sons(self, obj):
"""Serialize all sons (from children_as_father FK and TransmitterRelative)"""
request = self.context.get('request')
result = []
seen_ids = set()
for child in obj.children_as_father.all():
seen_ids.add(child.id)
result.append({
'id': child.id,
'name': get_localized_text(child.full_name, request) or (child.get_name('en') if hasattr(child, 'get_name') else None) or '',
'arabic_name': get_arabic_localized_text(child.full_name) or (child.get_name('ar') if hasattr(child, 'get_name') else None),
'slug': child.slug,
})
for rel in obj.relatives.filter(relation_type='children'):
if rel.narrator and rel.narrator.id not in seen_ids:
seen_ids.add(rel.narrator.id)
result.append({
'id': rel.narrator.id,
'name': get_localized_text(rel.narrator.full_name, request) or (rel.narrator.get_name('en') if hasattr(rel.narrator, 'get_name') else None) or '',
'arabic_name': get_arabic_localized_text(rel.narrator.full_name) or (rel.narrator.get_name('ar') if hasattr(rel.narrator, 'get_name') else None),
'slug': rel.narrator.slug,
})
elif not rel.narrator and rel.name:
result.append({
'id': None,
'name': rel.name,
'arabic_name': rel.name,
'slug': None,
})
return result
def get_reliability(self, obj):
"""Serialize the reliability foreign key"""
if obj.reliability:

71
apps/hadis/serializers/serializers_admin.py

@ -1037,6 +1037,18 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer):
queryset=TransmitterReliability.objects.all(),
required=False,
)
father = serializers.PrimaryKeyRelatedField(
queryset=Transmitters.objects.all(),
required=False,
allow_null=True,
)
father_detail = serializers.SerializerMethodField(read_only=True)
sons = serializers.SerializerMethodField(read_only=True)
sons_ids = serializers.ListField(
child=serializers.IntegerField(),
required=False,
write_only=True,
)
share_link = serializers.CharField(read_only=True)
class Meta:
@ -1065,11 +1077,46 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer):
"description",
"thumbnail",
"remove_thumbnail",
"father",
"father_detail",
"sons",
"sons_ids",
"share_link",
"created_at",
"updated_at",
]
read_only_fields = ["id", "slug", "share_link", "created_at", "updated_at", "reliability_detail"]
read_only_fields = ["id", "slug", "share_link", "created_at", "updated_at", "reliability_detail", "father_detail", "sons"]
def get_father_detail(self, obj):
if obj.father:
name = obj.father.get_name('en') if hasattr(obj.father, 'get_name') else None
if not name and obj.father.full_name and isinstance(obj.father.full_name, list) and len(obj.father.full_name) > 0:
first = obj.father.full_name[0]
if isinstance(first, dict):
name = first.get('text', '')
return {
"id": obj.father.id,
"full_name": obj.father.full_name,
"name": name or '',
"slug": obj.father.slug,
}
return None
def get_sons(self, obj):
result = []
for son in obj.children_as_father.all():
name = son.get_name('en') if hasattr(son, 'get_name') else None
if not name and son.full_name and isinstance(son.full_name, list) and len(son.full_name) > 0:
first = son.full_name[0]
if isinstance(first, dict):
name = first.get('text', '')
result.append({
"id": son.id,
"full_name": son.full_name,
"name": name or '',
"slug": son.slug,
})
return result
def to_internal_value(self, data):
import json
@ -1100,22 +1147,40 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer):
"birth_year_hijri",
"death_year_hijri",
"companion_type",
"father",
]:
if field in data and (data[field] == "" or data[field] is None or data[field] == "null" or data[field] == "undefined" or (isinstance(data[field], str) and not data[field].strip())):
data[field] = None
if "sons_ids" in data:
val = data["sons_ids"]
if isinstance(val, str):
try:
data["sons_ids"] = json.loads(val)
except ValueError:
pass
return super().to_internal_value(data)
def create(self, validated_data):
validated_data.pop("remove_thumbnail", False)
return super().create(validated_data)
sons_ids = validated_data.pop("sons_ids", None)
instance = super().create(validated_data)
if sons_ids is not None:
Transmitters.objects.filter(id__in=sons_ids).update(father=instance)
return instance
def update(self, instance, validated_data):
remove_thumbnail = validated_data.pop("remove_thumbnail", False)
sons_ids = validated_data.pop("sons_ids", None)
if remove_thumbnail and instance.thumbnail:
instance.thumbnail.delete(save=False)
instance.thumbnail = None
return super().update(instance, validated_data)
instance = super().update(instance, validated_data)
if sons_ids is not None:
instance.children_as_father.exclude(id__in=sons_ids).update(father=None)
Transmitters.objects.filter(id__in=sons_ids).update(father=instance)
return instance
class AdminBookSubjectAreaSerializer(serializers.ModelSerializer):

Loading…
Cancel
Save