import os import sys import random import django # Setup environment sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.stdout.reconfigure(encoding='utf-8') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.base') django.setup() from django.db import transaction from django.db.models.signals import post_save, post_delete, m2m_changed from apps.hadis.signals import clear_hadis_cache, clear_hadis_cache_on_m2m, invalidate_hadis_cache from apps.hadis.models import ( BookReference, BookEdition, BookVolume, HadisReference, HadisInterpretation, InterpretationReference, HadisCorrection, CorrectionReference ) from apps.hadis.models.transmitter import OriginalTextReference # Disconnect cache signals during bulk population for high performance post_save.disconnect(clear_hadis_cache) post_delete.disconnect(clear_hadis_cache) m2m_changed.disconnect(clear_hadis_cache_on_m2m) SAMPLE_PUBLISHERS = [ [ {'text': 'دار الحديث', 'language_code': 'fa'}, {'text': 'دار الحديث للطباعة والنشر', 'language_code': 'ar'}, {'text': 'Dar al-Hadith Publications', 'language_code': 'en'} ], [ {'text': 'دار الكتب الإسلامية', 'language_code': 'fa'}, {'text': 'دار الكتب الإسلامية', 'language_code': 'ar'}, {'text': 'Dar al-Kutub al-Islamiyya', 'language_code': 'en'} ], [ {'text': 'مؤسسة النشر الإسلامي', 'language_code': 'fa'}, {'text': 'مؤسسة النشر الإسلامي التابعة لجماعة المدرسين', 'language_code': 'ar'}, {'text': 'Islamic Publishing Foundation', 'language_code': 'en'} ], [ {'text': 'مؤسسة البلاغ', 'language_code': 'fa'}, {'text': 'مؤسسة البلاغ للطباعة والنشر والتوزيع', 'language_code': 'ar'}, {'text': 'Al-Balagh Foundation', 'language_code': 'en'} ], [ {'text': 'دار الكتب العلمية', 'language_code': 'fa'}, {'text': 'دار الكتب العلمية', 'language_code': 'ar'}, {'text': 'Dar al-Kutub al-Ilmiyya', 'language_code': 'en'} ], [ {'text': 'مؤسسة آل البيت (ع) لإحياء التراث', 'language_code': 'fa'}, {'text': 'مؤسسة آل البيت عليهم السلام لإحياء التراث', 'language_code': 'ar'}, {'text': 'Aal al-Bayt Institute', 'language_code': 'en'} ], ] def is_empty_value(val): if val is None: return True s = str(val).strip() return s in ['', '0', '00', 'None', 'null', 'nan'] def ensure_book_editions_and_volumes(): """Ensure every BookReference has at least 1 BookEdition and 1 BookVolume.""" all_books = list(BookReference.objects.prefetch_related('editions', 'volumes').all()) print(f"Checking {len(all_books)} books for editions and volumes...", flush=True) editions_to_create = [] books_needing_editions = [] for book in all_books: editions = list(book.editions.all()) if not editions: pub = random.choice(SAMPLE_PUBLISHERS) editions_to_create.append(BookEdition( book_reference=book, publisher=pub, edition_number='طبعة أولى (1)', year_of_publication='1441', number_of_volumes=4 )) books_needing_editions.append(book) if editions_to_create: created = BookEdition.objects.bulk_create(editions_to_create) print(f"Created {len(created)} default editions.", flush=True) else: print("All books already have editions.", flush=True) # Re-fetch or assign volumes all_books = list(BookReference.objects.prefetch_related('editions', 'volumes').all()) volumes_to_create = [] for book in all_books: volumes = list(book.volumes.all()) if not volumes: editions = list(book.editions.all()) ed = editions[0] if editions else None for vol_num in range(1, 5): volumes_to_create.append(BookVolume( book_reference=book, edition=ed, title=f"جلد {vol_num}" )) if volumes_to_create: created_vols = BookVolume.objects.bulk_create(volumes_to_create) print(f"Created {len(created_vols)} default volumes.", flush=True) else: print("All books already have volumes.", flush=True) def process_reference_queryset(model_class, model_name, all_books_map): """Update edition, book_volume, volume, pages, and hadith_number on all instances using bulk_update.""" qs = model_class.objects.select_related('book_reference', 'edition', 'book_volume').all() total = qs.count() print(f"\nProcessing {total} records of {model_name}...", flush=True) if total == 0: return updated_count = 0 all_book_ids = list(all_books_map.keys()) if not all_book_ids: print("Error: No books available in database!", flush=True) return items_to_update = [] has_hadith_number = hasattr(model_class, 'hadith_number') update_fields = ['book_reference', 'edition', 'book_volume', 'volume', 'pages'] if has_hadith_number: update_fields.append('hadith_number') for ref in qs: changed = False # 1. Ensure book_reference is attached book = ref.book_reference if not book: chosen_book_id = random.choice(all_book_ids) book = all_books_map[chosen_book_id]['book'] ref.book_reference = book changed = True book_data = all_books_map.get(book.id) if not book_data: continue book_editions = book_data['editions'] book_volumes = book_data['volumes'] # 2. Ensure edition (Publisher) is set if not ref.edition and book_editions: ref.edition = random.choice(book_editions) changed = True # 3. Ensure book_volume and volume text are set if not ref.book_volume and book_volumes: chosen_vol = random.choice(book_volumes) ref.book_volume = chosen_vol ref.volume = chosen_vol.title or f"جلد {random.randint(1, 4)}" changed = True elif not ref.volume: ref.volume = f"جلد {random.randint(1, 4)}" changed = True # 4. Ensure pages is set to a valid random page number if is_empty_value(ref.pages): ref.pages = str(random.randint(15, 480)) changed = True # 5. Ensure hadith_number is set to a valid random hadith number if has_hadith_number: if is_empty_value(ref.hadith_number): ref.hadith_number = str(random.randint(45, 3450)) changed = True if changed: items_to_update.append(ref) if len(items_to_update) >= 300: model_class.objects.bulk_update(items_to_update, update_fields) updated_count += len(items_to_update) print(f" - Updated {updated_count}/{total} records...", flush=True) items_to_update = [] if items_to_update: model_class.objects.bulk_update(items_to_update, update_fields) updated_count += len(items_to_update) print(f"Completed {model_name}: {updated_count} / {total} records updated.", flush=True) def main(): print("==================================================", flush=True) print("STARTING REFERENCE METADATA POPULATION", flush=True) print("==================================================", flush=True) with transaction.atomic(): # Step 1: Ensure all books have editions and volumes ensure_book_editions_and_volumes() # Step 2: Cache book relations in memory for fast lookup all_books_map = {} for b in BookReference.objects.prefetch_related('editions', 'volumes').all(): all_books_map[b.id] = { 'book': b, 'editions': list(b.editions.all()), 'volumes': list(b.volumes.all()), } print(f"Cached {len(all_books_map)} books with their editions and volumes.", flush=True) # Step 3: Update HadisReference (Arguments) process_reference_queryset(HadisReference, "HadisReference (Arguments)", all_books_map) # Step 4: Update InterpretationReference (Interpretations) process_reference_queryset(InterpretationReference, "InterpretationReference (Interpretations)", all_books_map) # Step 5: Update OriginalTextReference (Original Texts) process_reference_queryset(OriginalTextReference, "OriginalTextReference (Original Texts)", all_books_map) # Step 6: Update CorrectionReference (Corrections) process_reference_queryset(CorrectionReference, "CorrectionReference (Corrections)", all_books_map) # Invalidate cache once at the end print("\nInvalidating API cache...", flush=True) try: invalidate_hadis_cache() print("API cache invalidated successfully.", flush=True) except Exception as e: print(f"Cache invalidation note: {e}", flush=True) print("\n==================================================", flush=True) print("SUCCESS: ALL REFERENCES SUCCESSFULLY UPDATED!", flush=True) print("==================================================", flush=True) if __name__ == '__main__': main()