|
|
@ -1,146 +1,53 @@ |
|
|
import random |
|
|
import random |
|
|
from django.core.management.base import BaseCommand |
|
|
from django.core.management.base import BaseCommand |
|
|
from django.db import transaction |
|
|
|
|
|
# Adjust the import path if your app name is different |
|
|
|
|
|
from apps.hadis.models import Hadis, HadisCategory, HadisSect |
|
|
|
|
|
|
|
|
from apps.hadis.models import Hadis, HadisCategory |
|
|
|
|
|
|
|
|
class Command(BaseCommand): |
|
|
class Command(BaseCommand): |
|
|
help = 'Seeds HadisCategories with rich Islamic data and links existing Hadiths' |
|
|
|
|
|
|
|
|
help = 'Part 2: Assigns Hadiths to Leaves and cleans empty categories' |
|
|
|
|
|
|
|
|
def handle(self, *args, **options): |
|
|
def handle(self, *args, **options): |
|
|
self.stdout.write(self.style.WARNING('--- Starting Islamic Category Seeding ---')) |
|
|
|
|
|
|
|
|
|
|
|
# 1. Fetch or Create Sects |
|
|
|
|
|
shia_sect, _ = HadisSect.objects.get_or_create( |
|
|
|
|
|
sect_type=HadisSect.SectType.SHIA, |
|
|
|
|
|
defaults={'title': [{'text': 'Shia (Twelver)', 'language_code': 'en'}], 'is_active': True} |
|
|
|
|
|
) |
|
|
|
|
|
sunni_sect, _ = HadisSect.objects.get_or_create( |
|
|
|
|
|
sect_type=HadisSect.SectType.SUNNI, |
|
|
|
|
|
defaults={'title': [{'text': 'Sunni (Ahlus Sunnah)', 'language_code': 'en'}], 'is_active': True} |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
# 2. Rich Islamic Data Dictionary |
|
|
|
|
|
ISLAMIC_DATA = { |
|
|
|
|
|
HadisCategory.SourceType.QURAN: { |
|
|
|
|
|
HadisSect.SectType.SHIA: [ |
|
|
|
|
|
("Quranic Sciences", "Thematic Exegesis", ["Verses of Wilayah", "Ahlulbayt in Quran", "Moral Teachings in Surah Yusuf"]), |
|
|
|
|
|
("Tafsir Studies", "Interpretation Principles", ["Tafsir al-Mizan Topics", "Esoteric Meanings (Ta'wil)"]) |
|
|
|
|
|
], |
|
|
|
|
|
HadisSect.SectType.SUNNI: [ |
|
|
|
|
|
("Quranic Studies", "Tafsir Methodology", ["Asbab al-Nuzul", "Stories of the Prophets", "Legislative Verses (Ahkam)"]), |
|
|
|
|
|
("Recitation (Tajweed)", "Qira'at", ["Hafs an Asim", "Warsh an Nafi", "Rules of Nun Sakinah"]) |
|
|
|
|
|
] |
|
|
|
|
|
}, |
|
|
|
|
|
HadisCategory.SourceType.HADITH: { |
|
|
|
|
|
HadisSect.SectType.SHIA: [ |
|
|
|
|
|
("The Four Books", "Usul al-Kafi", ["Book of Intellect", "Book of Divine Proof", "Book of Belief"]), |
|
|
|
|
|
("Nahj al-Balagha", "Sermons of Imam Ali", ["The Shiqshiqiyyah Sermon", "Letter to Malik al-Ashtar", "Aphorisms of Wisdom"]) |
|
|
|
|
|
], |
|
|
|
|
|
HadisSect.SectType.SUNNI: [ |
|
|
|
|
|
("The Six Books", "Sahih al-Bukhari", ["Book of Revelation", "Book of Belief", "Book of Knowledge"]), |
|
|
|
|
|
("Sunan Collections", "Sunan Abu Dawood", ["Book of Prayer", "Book of Zakat", "Book of Jihad"]) |
|
|
|
|
|
] |
|
|
|
|
|
}, |
|
|
|
|
|
HadisCategory.SourceType.HISTORY: { |
|
|
|
|
|
HadisSect.SectType.SHIA: [ |
|
|
|
|
|
("Life of the Infallibles", "The Tragedy of Karbala", ["Day of Ashura", "The Sermon of Zaynab (SA)", "Journey of Captives"]), |
|
|
|
|
|
("Occultation (Ghaybah)", "Major Occultation", ["Signs of Reappearance", "Duties of Believers", "Deputies of the Imam"]) |
|
|
|
|
|
], |
|
|
|
|
|
HadisSect.SectType.SUNNI: [ |
|
|
|
|
|
("Seerah of the Prophet", "Meccan Period", ["The First Revelation", "Migration to Abyssinia", "The Year of Sorrow"]), |
|
|
|
|
|
("The Rashidun Caliphate", "Conquests and Expansion", ["Caliphate of Abu Bakr", "Caliphate of Umar", "Battles of Yarmouk"]) |
|
|
|
|
|
] |
|
|
|
|
|
}, |
|
|
|
|
|
HadisCategory.SourceType.FATWA: { |
|
|
|
|
|
HadisSect.SectType.SHIA: [ |
|
|
|
|
|
("Jurisprudence (Fiqh)", "Acts of Worship", ["Rules of Taqlid", "Khums and Zakat", "Rules of Salatul Layl"]), |
|
|
|
|
|
("Modern Legal Issues", "Medical Jurisprudence", ["Organ Donation", "Assisted Reproduction", "Gender Reassignment"]) |
|
|
|
|
|
], |
|
|
|
|
|
HadisSect.SectType.SUNNI: [ |
|
|
|
|
|
("Fiqh Schools", "Shafi'i School", ["Rules of Prayer", "Inheritance Laws", "Marriage Contracts"]), |
|
|
|
|
|
("Contemporary Fatawa", "Islamic Finance", ["Prohibition of Riba", "Islamic Banking Principles", "Crypto-currency Rulings"]) |
|
|
|
|
|
] |
|
|
|
|
|
}, |
|
|
|
|
|
HadisCategory.SourceType.QUOTE: { |
|
|
|
|
|
HadisSect.SectType.SHIA: [ |
|
|
|
|
|
("Wisdom of the Imams", "Supplications (Du'a)", ["Du'a Kumayl Themes", "Whispered Prayers (Munajat)", "Du'a Arafah"]), |
|
|
|
|
|
("Mysticism (Irfan)", "Spiritual Wayfaring", ["Combat with the Self", "Degrees of Piety", "Love for the Divine"]) |
|
|
|
|
|
], |
|
|
|
|
|
HadisSect.SectType.SUNNI: [ |
|
|
|
|
|
("Sayings of the Companions", "Wisdom of Abu Bakr & Umar", ["Justice in Governance", "Fear of Allah", "Humility"]), |
|
|
|
|
|
("Sufi Wisdom", "Spiritual Purification", ["Tazkiyat al-Nafs", "Remembrance of Death", "Reliance on Allah (Tawakkul)"]) |
|
|
|
|
|
] |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
created_leaves = [] |
|
|
|
|
|
|
|
|
|
|
|
with transaction.atomic(): |
|
|
|
|
|
self.stdout.write('Building Rich Islamic Category Tree...') |
|
|
|
|
|
|
|
|
|
|
|
for source_code, sect_data in ISLAMIC_DATA.items(): |
|
|
|
|
|
for sect_type_enum, categories_list in sect_data.items(): |
|
|
|
|
|
|
|
|
|
|
|
current_sect = shia_sect if sect_type_enum == HadisSect.SectType.SHIA else sunni_sect |
|
|
|
|
|
|
|
|
|
|
|
for root_title, sub_title, leaf_titles in categories_list: |
|
|
|
|
|
|
|
|
|
|
|
# --- Level 1: Root --- |
|
|
|
|
|
# ADDED: description to defaults |
|
|
|
|
|
root_cat, _ = HadisCategory.objects.get_or_create( |
|
|
|
|
|
sect=current_sect, |
|
|
|
|
|
source_type=source_code, |
|
|
|
|
|
title=[{'text': root_title, 'language_code': 'en'}], |
|
|
|
|
|
defaults={ |
|
|
|
|
|
'parent': None, |
|
|
|
|
|
'description': [{'text': f'Root category for {root_title}', 'language_code': 'en'}] |
|
|
|
|
|
} |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
# --- Level 2: Sub --- |
|
|
|
|
|
# ADDED: description to defaults |
|
|
|
|
|
sub_cat, _ = HadisCategory.objects.get_or_create( |
|
|
|
|
|
sect=current_sect, |
|
|
|
|
|
source_type=source_code, |
|
|
|
|
|
title=[{'text': sub_title, 'language_code': 'en'}], |
|
|
|
|
|
defaults={ |
|
|
|
|
|
'parent': root_cat, |
|
|
|
|
|
'description': [{'text': f'Sub-category for {sub_title}', 'language_code': 'en'}] |
|
|
|
|
|
} |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
# --- Level 3: Leaves --- |
|
|
|
|
|
for leaf_title in leaf_titles: |
|
|
|
|
|
# ADDED: description to defaults |
|
|
|
|
|
leaf_cat, _ = HadisCategory.objects.get_or_create( |
|
|
|
|
|
sect=current_sect, |
|
|
|
|
|
source_type=source_code, |
|
|
|
|
|
title=[{'text': leaf_title, 'language_code': 'en'}], |
|
|
|
|
|
defaults={ |
|
|
|
|
|
'parent': sub_cat, |
|
|
|
|
|
'description': [{'text': f'Leaf category for {leaf_title}', 'language_code': 'en'}] |
|
|
|
|
|
} |
|
|
|
|
|
) |
|
|
|
|
|
created_leaves.append(leaf_cat) |
|
|
|
|
|
self.stdout.write(f"Created Leaf: {leaf_title} ({current_sect.get_title('en')})") |
|
|
|
|
|
|
|
|
|
|
|
self.stdout.write(self.style.SUCCESS(f'Successfully structured {len(created_leaves)} leaf categories.')) |
|
|
|
|
|
|
|
|
|
|
|
# 3. Connect Hadiths to Leaves |
|
|
|
|
|
|
|
|
self.stdout.write(self.style.WARNING('--- Part 2: Starting Hadith Assignment ---')) |
|
|
|
|
|
|
|
|
|
|
|
# 1. FETCH LEAF CATEGORIES |
|
|
|
|
|
# A leaf is a category that has NO children |
|
|
|
|
|
# We assume Script 1 has run, so we just query the DB. |
|
|
|
|
|
# Note: Related name for children defaults to 'children' usually, check your model if different. |
|
|
|
|
|
# If your model doesn't have a 'children' reverse relation explicitly, |
|
|
|
|
|
# Django usually provides 'hadiscategory_set' or similar. |
|
|
|
|
|
# Standard approach for adjacency list parent: |
|
|
|
|
|
|
|
|
|
|
|
leaf_categories = [] |
|
|
|
|
|
all_cats = HadisCategory.objects.all() |
|
|
|
|
|
|
|
|
|
|
|
self.stdout.write("identifying leaf categories...") |
|
|
|
|
|
for cat in all_cats: |
|
|
|
|
|
if not HadisCategory.objects.filter(parent=cat).exists(): |
|
|
|
|
|
leaf_categories.append(cat) |
|
|
|
|
|
|
|
|
|
|
|
if not leaf_categories: |
|
|
|
|
|
self.stdout.write(self.style.ERROR("No leaf categories found! Did you run Part 1?")) |
|
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
self.stdout.write(f"Found {len(leaf_categories)} leaf categories.") |
|
|
|
|
|
|
|
|
|
|
|
# 2. FETCH HADITHS |
|
|
all_hadiths = list(Hadis.objects.all()) |
|
|
all_hadiths = list(Hadis.objects.all()) |
|
|
total_hadiths = len(all_hadiths) |
|
|
total_hadiths = len(all_hadiths) |
|
|
|
|
|
|
|
|
if total_hadiths < 3: |
|
|
if total_hadiths < 3: |
|
|
self.stdout.write(self.style.ERROR('Not enough Hadiths in DB. Please seed Hadiths first (need at least 3).')) |
|
|
|
|
|
|
|
|
self.stdout.write(self.style.ERROR('Not enough Hadiths in DB (need at least 3).')) |
|
|
return |
|
|
return |
|
|
|
|
|
|
|
|
self.stdout.write(f'Distributing {total_hadiths} Hadiths among leaves...') |
|
|
|
|
|
|
|
|
# 3. DISTRIBUTE |
|
|
|
|
|
self.stdout.write(f'Distributing {total_hadiths} Hadiths among leaves (Target: 3 per leaf)...') |
|
|
|
|
|
|
|
|
random.shuffle(all_hadiths) |
|
|
random.shuffle(all_hadiths) |
|
|
hadith_index = 0 |
|
|
hadith_index = 0 |
|
|
|
|
|
assigned_count = 0 |
|
|
|
|
|
|
|
|
for leaf in created_leaves: |
|
|
|
|
|
|
|
|
# Loop through leaves and assign |
|
|
|
|
|
for leaf in leaf_categories: |
|
|
|
|
|
# Cycle logic: restart index if we run out of unique hadiths |
|
|
if hadith_index + 3 > total_hadiths: |
|
|
if hadith_index + 3 > total_hadiths: |
|
|
hadith_index = 0 |
|
|
hadith_index = 0 |
|
|
|
|
|
|
|
|
@ -150,5 +57,39 @@ class Command(BaseCommand): |
|
|
for hadis_obj in batch: |
|
|
for hadis_obj in batch: |
|
|
hadis_obj.category = leaf |
|
|
hadis_obj.category = leaf |
|
|
hadis_obj.save() |
|
|
hadis_obj.save() |
|
|
|
|
|
assigned_count += 1 |
|
|
|
|
|
|
|
|
|
|
|
# Print a dot every 5 leaves to show progress without spamming |
|
|
|
|
|
if leaf_categories.index(leaf) % 5 == 0: |
|
|
|
|
|
self.stdout.write(".", ending="") |
|
|
|
|
|
|
|
|
|
|
|
self.stdout.write(f"\nAssigned {assigned_count} hadith links.") |
|
|
|
|
|
|
|
|
|
|
|
# 4. CLEANUP EMPTY CATEGORIES |
|
|
|
|
|
# Constraint: "every category is either have hadiths , or childs" |
|
|
|
|
|
self.stdout.write("Running cleanup for empty categories...") |
|
|
|
|
|
|
|
|
|
|
|
empty_deleted_count = 0 |
|
|
|
|
|
|
|
|
|
|
|
# Re-fetch all categories to ensure fresh state |
|
|
|
|
|
all_cats_check = HadisCategory.objects.all() |
|
|
|
|
|
|
|
|
|
|
|
for cat in all_cats_check: |
|
|
|
|
|
# Check for children |
|
|
|
|
|
has_children = HadisCategory.objects.filter(parent=cat).exists() |
|
|
|
|
|
|
|
|
|
|
|
# Check for hadiths |
|
|
|
|
|
# Ensure 'category' matches your field name in Hadis model |
|
|
|
|
|
has_hadiths = Hadis.objects.filter(category=cat).exists() |
|
|
|
|
|
|
|
|
|
|
|
if not has_children and not has_hadiths: |
|
|
|
|
|
# self.stdout.write(f"Deleting empty: {cat.id}") # Optional debug |
|
|
|
|
|
cat.delete() |
|
|
|
|
|
empty_deleted_count += 1 |
|
|
|
|
|
|
|
|
|
|
|
if empty_deleted_count > 0: |
|
|
|
|
|
self.stdout.write(self.style.WARNING(f"Cleaned up {empty_deleted_count} empty categories.")) |
|
|
|
|
|
else: |
|
|
|
|
|
self.stdout.write(self.style.SUCCESS("No empty categories found.")) |
|
|
|
|
|
|
|
|
self.stdout.write(self.style.SUCCESS('--- Islamic Category Seeding Complete ---')) |
|
|
|
|
|
|
|
|
self.stdout.write(self.style.SUCCESS('--- Part 2 Complete ---')) |