4 changed files with 487 additions and 4 deletions
-
91apps/hadis/management/commands/assign_random_transmitter_families.py
-
265apps/hadis/management/commands/generate_editions_and_volumes.py
-
64apps/hadis/serializers/hadis.py
-
71apps/hadis/serializers/serializers_admin.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!" |
||||
|
) |
||||
|
) |
||||
|
|
||||
@ -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." |
||||
|
) |
||||
|
) |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue