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