import os import sys import argparse import django sys.stdout.reconfigure(encoding='utf-8') sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.develop') django.setup() from django.db import transaction from django.db.models import Count from apps.hadis.models import HadisCategory, Hadis def get_text(val): if isinstance(val, list): for item in val: if isinstance(item, dict): return item.get('text') or item.get('title') or str(item) elif isinstance(val, dict): return val.get('text') or val.get('title') or str(val) return str(val or "") def resolve_category_conflicts(dry_run=True): print("=" * 65) print(" CATEGORY CONFLICT RESOLVER (HADITHS vs SUBCATEGORIES) ") print(f" Mode: {'DRY RUN (Simulation only, no DB changes)' if dry_run else 'EXECUTE (Applying changes to DB)'}") print("=" * 65) conflicted_categories = HadisCategory.objects.annotate( hadis_count=Count('hadis', distinct=True), children_count=Count('children', distinct=True) ).filter(hadis_count__gt=0, children_count__gt=0) total_conflicts = conflicted_categories.count() print(f"\nFound {total_conflicts} categories violating the rule (having both Hadiths & Subcategories).\n") if total_conflicts == 0: print("✅ No conflicts found. All categories adhere to the business rule.") return deleted_subcats_count = 0 reassigned_hadis_count = 0 with transaction.atomic(): for idx, parent_cat in enumerate(conflicted_categories, 1): parent_title = get_text(parent_cat.title) print(f"[{idx}/{total_conflicts}] Category #{parent_cat.id} ('{parent_title}') | Slug: {parent_cat.slug}") print(f" - Direct Hadiths: {parent_cat.hadis_count}") print(f" - Subcategories count: {parent_cat.children_count}") # Get all descendants descendants = list(parent_cat.get_descendants().order_by('-level')) print(f" - Total subcategories/descendants to remove: {len(descendants)}") for subcat in descendants: subcat_title = get_text(subcat.title) sub_hadis = subcat.hadis_set.all() sub_hadis_count = sub_hadis.count() if sub_hadis_count > 0: print(f" ⚠️ Subcategory #{subcat.id} ('{subcat_title}') has {sub_hadis_count} Hadiths!") print(f" -> Reassigning these {sub_hadis_count} Hadiths to parent Category #{parent_cat.id} before deletion.") if not dry_run: sub_hadis.update(category=parent_cat) reassigned_hadis_count += sub_hadis_count print(f" 🗑️ Removing Subcategory #{subcat.id} ('{subcat_title}')") if not dry_run: subcat.delete() deleted_subcats_count += 1 if dry_run: print("\n[DRY RUN SUMMARY]") print(f" - Categories inspected: {total_conflicts}") print(f" - Subcategories that would be deleted: {deleted_subcats_count}") print(f" - Hadiths that would be safeguarded/reassigned: {reassigned_hadis_count}") print(" -> No changes were written to the database.") else: print("\nRebuilding MPTT category tree structure...") HadisCategory.objects.rebuild() print("✅ MPTT category tree successfully rebuilt.") print("\n[EXECUTION SUMMARY]") print(f" - Conflicted categories resolved: {total_conflicts}") print(f" - Subcategories deleted: {deleted_subcats_count}") print(f" - Hadiths safeguarded: {reassigned_hadis_count}") print("✅ All changes committed successfully.") if __name__ == '__main__': parser = argparse.ArgumentParser(description="Resolve categories having both Hadiths and Subcategories") parser.add_argument('--execute', action='store_true', help='Execute changes on database (default is dry-run)') args = parser.parse_args() resolve_category_conflicts(dry_run=not args.execute)