|
|
|
@ -2,22 +2,21 @@ import os |
|
|
|
import random |
|
|
|
from django.core.management.base import BaseCommand |
|
|
|
from django.core.files import File |
|
|
|
from django.core.files.base import ContentFile |
|
|
|
from django.conf import settings |
|
|
|
from django.db import transaction |
|
|
|
|
|
|
|
# REMEMBER: Change 'your_app' to your actual app name |
|
|
|
from apps.hadis.models.hadis import Hadis, HadisReference, ReferenceImage |
|
|
|
from apps.hadis.models.reference import BookReference |
|
|
|
|
|
|
|
class Command(BaseCommand): |
|
|
|
help = 'Wipes old reference data and seeds new HadisReference and ReferenceImages' |
|
|
|
help = 'Seeds HadisReferences ensuring every Book has 2+ hadiths AND every Hadith has 1+ reference.' |
|
|
|
|
|
|
|
def handle(self, *args, **kwargs): |
|
|
|
self.stdout.write(self.style.WARNING('--- Starting Cleanup ---')) |
|
|
|
|
|
|
|
# 1. DELETE PREVIOUS DATA |
|
|
|
# We delete HadisReference. Because of on_delete=models.CASCADE in ReferenceImage, |
|
|
|
# deleting the reference will automatically delete the associated images. |
|
|
|
# However, we can delete both explicitly to be sure and show counts. |
|
|
|
|
|
|
|
deleted_imgs_count, _ = ReferenceImage.objects.all().delete() |
|
|
|
deleted_refs_count, _ = HadisReference.objects.all().delete() |
|
|
|
|
|
|
|
@ -25,69 +24,110 @@ class Command(BaseCommand): |
|
|
|
self.stdout.write(self.style.SUCCESS(f'Deleted {deleted_refs_count} old HadisReferences.')) |
|
|
|
self.stdout.write('------------------------') |
|
|
|
|
|
|
|
# 2. Setup Paths for Seeding |
|
|
|
base_image_path = os.path.join(settings.BASE_DIR, 'seeds', 'images') |
|
|
|
# We look for ref1.png, ref2.png, ref3.png, ref4.png |
|
|
|
image_files = [f'ref{i}.png' for i in range(1, 5)] |
|
|
|
# 2. Setup Paths |
|
|
|
self.base_image_path = os.path.join(settings.BASE_DIR, 'seeds', 'images') |
|
|
|
self.image_files = [f'ref{i}.png' for i in range(1, 5)] |
|
|
|
|
|
|
|
# Verify images exist locally before starting |
|
|
|
for img_name in image_files: |
|
|
|
full_path = os.path.join(base_image_path, img_name) |
|
|
|
# Verify images |
|
|
|
for img_name in self.image_files: |
|
|
|
full_path = os.path.join(self.base_image_path, img_name) |
|
|
|
if not os.path.exists(full_path): |
|
|
|
self.stdout.write(self.style.ERROR(f'CRITICAL ERROR: Image not found at {full_path}')) |
|
|
|
return |
|
|
|
|
|
|
|
# 3. Fetch Parents |
|
|
|
# 3. Fetch Data |
|
|
|
books = list(BookReference.objects.all()) |
|
|
|
hadiths = list(Hadis.objects.all()) |
|
|
|
|
|
|
|
if len(hadiths) < 2: |
|
|
|
self.stdout.write(self.style.ERROR('Not enough Hadiths in DB (need at least 2).')) |
|
|
|
return |
|
|
|
|
|
|
|
if not books: |
|
|
|
self.stdout.write(self.style.ERROR('No BookReferences found in DB.')) |
|
|
|
if not books or not hadiths: |
|
|
|
self.stdout.write(self.style.ERROR('Missing Books or Hadiths in DB.')) |
|
|
|
return |
|
|
|
|
|
|
|
self.stdout.write('--- Starting Seeding ---') |
|
|
|
|
|
|
|
counter_refs = 0 |
|
|
|
counter_imgs = 0 |
|
|
|
self.counter_refs = 0 |
|
|
|
self.counter_imgs = 0 |
|
|
|
|
|
|
|
# Track which Hadith IDs have been covered |
|
|
|
covered_hadith_ids = set() |
|
|
|
|
|
|
|
# 4. Iterate Books (Constraint 1: Each book must relate to at least 2 hadith) |
|
|
|
# ========================================== |
|
|
|
# PHASE 1: Satisfy Book Constraint |
|
|
|
# "Each book must relate to at least 2 hadith" |
|
|
|
# ========================================== |
|
|
|
self.stdout.write("Phase 1: Linking Books to Hadiths...") |
|
|
|
|
|
|
|
for book in books: |
|
|
|
# Pick 2 random hadiths for this book |
|
|
|
selected_hadiths = random.sample(hadiths, 2) |
|
|
|
# We use distinct logic if we want to ensure we don't pick the same one twice for one book |
|
|
|
selected_hadiths = random.sample(hadiths, k=min(len(hadiths), 2)) |
|
|
|
|
|
|
|
for hadis_obj in selected_hadiths: |
|
|
|
# Create the Link (HadisReference) |
|
|
|
hadis_ref = HadisReference.objects.create( |
|
|
|
hadis=hadis_obj, |
|
|
|
book_reference=book, |
|
|
|
description=[ |
|
|
|
{'language_code': 'en', 'text': f'Reference for {book.title[0].get("text", "Book")} - Hadith {hadis_obj.number}'}, |
|
|
|
{'language_code': 'ar', 'text': 'وصف مرجعي'} |
|
|
|
] |
|
|
|
) |
|
|
|
counter_refs += 1 |
|
|
|
|
|
|
|
# 5. Connect Images (Constraint 2: Each hadith ref must have 2 images) |
|
|
|
# We pick 2 random images from our list of 4 |
|
|
|
selected_images = random.sample(image_files, 2) |
|
|
|
|
|
|
|
for priority_index, img_name in enumerate(selected_images): |
|
|
|
img_path = os.path.join(base_image_path, img_name) |
|
|
|
|
|
|
|
with open(img_path, 'rb') as f: |
|
|
|
# Create ReferenceImage |
|
|
|
ref_img = ReferenceImage( |
|
|
|
reference=hadis_ref, |
|
|
|
priority=priority_index # 0 and 1 |
|
|
|
) |
|
|
|
# Save file to the ImageField |
|
|
|
ref_img.thumbnail.save(img_name, File(f), save=True) |
|
|
|
counter_imgs += 1 |
|
|
|
self._create_reference_chain(hadis_obj, book) |
|
|
|
covered_hadith_ids.add(hadis_obj.id) |
|
|
|
|
|
|
|
# ========================================== |
|
|
|
# PHASE 2: Satisfy Hadith Constraint |
|
|
|
# "Each hadith must have a hadith reference object" |
|
|
|
# ========================================== |
|
|
|
self.stdout.write("Phase 2: Checking for orphan Hadiths...") |
|
|
|
|
|
|
|
for hadis_obj in hadiths: |
|
|
|
if hadis_obj.id not in covered_hadith_ids: |
|
|
|
# This hadith was skipped in Phase 1. |
|
|
|
# We must assign it to a Book (Reference). |
|
|
|
# Pick a random book to associate with |
|
|
|
random_book = random.choice(books) |
|
|
|
|
|
|
|
self._create_reference_chain(hadis_obj, random_book) |
|
|
|
covered_hadith_ids.add(hadis_obj.id) |
|
|
|
|
|
|
|
self.stdout.write(self.style.SUCCESS( |
|
|
|
f'DONE: Created {counter_refs} HadisReferences and {counter_imgs} ReferenceImages.' |
|
|
|
)) |
|
|
|
f'DONE: Created {self.counter_refs} HadisReferences (covering {len(covered_hadith_ids)} Hadiths) and {self.counter_imgs} Images.' |
|
|
|
)) |
|
|
|
|
|
|
|
def _create_reference_chain(self, hadis_obj, book_obj): |
|
|
|
""" |
|
|
|
Helper to create the Reference and its Images |
|
|
|
""" |
|
|
|
# Create the HadisReference |
|
|
|
hadis_ref = HadisReference.objects.create( |
|
|
|
hadis=hadis_obj, |
|
|
|
book_reference=book_obj, |
|
|
|
description=[ |
|
|
|
{'language_code': 'en', 'text': f'Reference for {book_obj.title[0].get("text", "Book")} - Hadith {hadis_obj.number}'}, |
|
|
|
{'language_code': 'ar', 'text': 'وصف مرجعي'} |
|
|
|
] |
|
|
|
) |
|
|
|
self.counter_refs += 1 |
|
|
|
|
|
|
|
# Create Images for this Reference |
|
|
|
self._create_images_for_ref(hadis_ref) |
|
|
|
|
|
|
|
def _create_images_for_ref(self, hadis_ref): |
|
|
|
""" |
|
|
|
Helper to create 2 images for a specific HadisReference |
|
|
|
""" |
|
|
|
selected_images = random.sample(self.image_files, 2) |
|
|
|
|
|
|
|
for priority_index, img_name in enumerate(selected_images): |
|
|
|
img_path = os.path.join(self.base_image_path, img_name) |
|
|
|
|
|
|
|
# Read file safely |
|
|
|
with open(img_path, 'rb') as f: |
|
|
|
file_content = f.read() |
|
|
|
|
|
|
|
ref_img = ReferenceImage( |
|
|
|
reference=hadis_ref, |
|
|
|
priority=priority_index |
|
|
|
) |
|
|
|
|
|
|
|
# Assign content without auto-saving |
|
|
|
ref_img.thumbnail.save(img_name, ContentFile(file_content), save=False) |
|
|
|
|
|
|
|
try: |
|
|
|
# Explicit save to handle your custom model logic / signals |
|
|
|
ref_img.save() |
|
|
|
self.counter_imgs += 1 |
|
|
|
except Exception as e: |
|
|
|
self.stdout.write(self.style.ERROR(f"Failed to save image {img_name}: {e}")) |