import time from django.core.management.base import BaseCommand from django.db import transaction, connection from django.core.cache import cache from apps.hadis.models import HadisCategory class Command(BaseCommand): help = ( 'Diversify topic source_types across parent categories so that ' 'sub-topics under each category have distinct, varied source types ' '(hadith, quran, history, fatwa, quote).' ) def add_arguments(self, parser): parser.add_argument( '--dry-run', action='store_true', help='Simulate diversification without modifying the database.' ) def handle(self, *args, **options): dry_run = options['dry_run'] if dry_run: self.stdout.write(self.style.WARNING("=== RUNNING IN DRY-RUN MODE (No changes will be saved) ===")) else: self.stdout.write(self.style.NOTICE("=== STARTING TOPIC SOURCE DIVERSIFICATION ===")) # Ensure database connection with retry for attempt in range(5): try: connection.ensure_connection() break except Exception as e: self.stdout.write(f"Connection attempt {attempt+1} failed: {e}. Retrying in 2s...") time.sleep(2) source_choices = [ HadisCategory.SourceType.HADITH, HadisCategory.SourceType.QURAN, HadisCategory.SourceType.HISTORY, HadisCategory.SourceType.FATWA, HadisCategory.SourceType.QUOTE, ] num_choices = len(source_choices) parents = HadisCategory.objects.filter(is_topic=False).select_related('sect').order_by('sect_id', 'tree_id', 'lft', 'id') parents_with_topics = [p for p in parents if p.children.filter(is_topic=True).exists()] self.stdout.write(f"Total parent categories with topics: {len(parents_with_topics)}") updated_topics_count = 0 total_topics_count = 0 global_rotation_idx = 0 with transaction.atomic(): for parent in parents_with_topics: topics = list(parent.children.filter(is_topic=True).order_by('order', 'id')) topic_count = len(topics) total_topics_count += topic_count sect_name = getattr(parent.sect, 'sect_type', 'unknown') safe_slug = parent.slug.encode('ascii', 'replace').decode('ascii') self.stdout.write( f"\nParent [{sect_name}] ID={parent.id} ({safe_slug}) - {topic_count} topics:" ) # For this parent's topics, pick distinct source types from the rotation for i, topic in enumerate(topics): new_source = source_choices[(global_rotation_idx + i) % num_choices] old_source = topic.source_type safe_topic_slug = topic.slug.encode('ascii', 'replace').decode('ascii') if topic.slug else 'no-slug' if old_source != new_source: updated_topics_count += 1 self.stdout.write( f" -> Topic ID={topic.id:4d} | '{safe_topic_slug}' : {old_source} -> {new_source}" ) if not dry_run: topic.source_type = new_source topic.save(update_fields=['source_type']) else: self.stdout.write( f" -> Topic ID={topic.id:4d} | '{safe_topic_slug}' : {new_source} (unchanged)" ) # Advance global rotation index global_rotation_idx = (global_rotation_idx + topic_count) % num_choices if dry_run: transaction.set_rollback(True) self.stdout.write("") self.stdout.write(self.style.SUCCESS("=== DIVERSIFICATION SUMMARY ===")) self.stdout.write(f" Parent categories processed: {len(parents_with_topics)}") self.stdout.write(f" Total topics inspected: {total_topics_count}") self.stdout.write(f" Topics updated: {updated_topics_count}") if not dry_run: # Clear cache so new source types are immediately served cache.clear() self.stdout.write(self.style.SUCCESS("Django cache cleared successfully!")) self.stdout.write(self.style.SUCCESS("All topic source types updated successfully!")) else: self.stdout.write(self.style.WARNING("Dry run finished. Run without --dry-run to commit changes."))