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 = '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 deleted_imgs_count, _ = ReferenceImage.objects.all().delete() deleted_refs_count, _ = HadisReference.objects.all().delete() self.stdout.write(self.style.SUCCESS(f'Deleted {deleted_imgs_count} old ReferenceImages.')) self.stdout.write(self.style.SUCCESS(f'Deleted {deleted_refs_count} old HadisReferences.')) self.stdout.write('------------------------') # 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 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 Data books = list(BookReference.objects.all()) hadiths = list(Hadis.objects.all()) if not books or not hadiths: self.stdout.write(self.style.ERROR('Missing Books or Hadiths in DB.')) return self.stdout.write('--- Starting Seeding ---') self.counter_refs = 0 self.counter_imgs = 0 # Track which Hadith IDs have been covered covered_hadith_ids = set() # ========================================== # 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 # 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: 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 {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}"))