from django.core.management.base import BaseCommand from core.models import AppUser, Library, Channel, MediaItem, Airing, ScheduleTemplate from core.services.scheduler import ScheduleGenerator from django.utils import timezone from datetime import timedelta, date import textwrap class Command(BaseCommand): help = "Displays a beautifully formatted terminal dashboard of the current backend state." def add_arguments(self, parser): parser.add_argument('--channel', type=int, help='Inspect specific channel schedule') parser.add_argument('--test-generate', action='store_true', help='Trigger generation for today if inspecting a channel') def get_color(self, text, code): """Helper to wrap string in bash color codes""" return f"\033[{code}m{text}\033[0m" def handle(self, *args, **options): channel_id = options.get('channel') test_generate = options.get('test_generate') if channel_id: self.inspect_channel(channel_id, test_generate) return # 1. Gather Aggregate Metrics total_users = AppUser.objects.count() total_libraries = Library.objects.count() total_channels = Channel.objects.count() total_media = MediaItem.objects.count() total_templates = ScheduleTemplate.objects.count() # Gather near-term Airing metrics now = timezone.now() tomorrow = now + timedelta(days=1) airings_today = Airing.objects.filter(starts_at__gte=now, starts_at__lt=tomorrow).count() airings_total = Airing.objects.count() self.stdout.write(self.get_color("\n=== PYTV Backend State Dashboard ===", "1;34")) self.stdout.write(f"\n{self.get_color('Core Metrics:', '1;36')}") self.stdout.write(f" Users: {self.get_color(str(total_users), '1;32')}") self.stdout.write(f" Libraries: {self.get_color(str(total_libraries), '1;32')}") self.stdout.write(f" Media Items: {self.get_color(str(total_media), '1;32')}") self.stdout.write(f" Channels: {self.get_color(str(total_channels), '1;32')}") self.stdout.write(f"\n{self.get_color('Scheduling Engine:', '1;36')}") self.stdout.write(f" Active Templates: {self.get_color(str(total_templates), '1;33')}") self.stdout.write(f" Airings (Next 24h): {self.get_color(str(airings_today), '1;33')}") self.stdout.write(f" Airings (Total): {self.get_color(str(airings_total), '1;33')}") # 2. Build Tree View of Channels self.stdout.write(f"\n{self.get_color('Channel Tree:', '1;36')}") channels = Channel.objects.prefetch_related('scheduletemplate_set').all() if not channels: self.stdout.write(" (No channels configured)") for c in channels: status_color = "1;32" if c.is_active else "1;31" status_text = "ACTIVE" if c.is_active else "INACTIVE" self.stdout.write(f"\n 📺 [{c.id}] {c.name} (Ch {c.channel_number or '-'}) ({self.get_color(status_text, status_color)})") # Show templates templates = c.scheduletemplate_set.filter(is_active=True).order_by('-priority') if not templates: self.stdout.write(" ⚠️ No active schedule templates.") else: for t in templates: blocks_count = t.scheduleblock_set.count() self.stdout.write(f" 📄 Template: {t.name} (Priority {t.priority}) -> {blocks_count} Blocks") self.stdout.write(f"\nUse {self.get_color('--channel ', '1;37')} to inspect detailed schedule.\n") def inspect_channel(self, channel_id, test_generate): try: channel = Channel.objects.get(id=channel_id) except Channel.DoesNotExist: self.stdout.write(self.get_color(f"Error: Channel {channel_id} not found.", "1;31")) return if test_generate: self.stdout.write(self.get_color(f"\nTriggering schedule generation for {channel.name}...", "1;33")) generator = ScheduleGenerator(channel) count = generator.generate_for_date(date.today()) self.stdout.write(f"Done. Created {self.get_color(str(count), '1;32')} new airings.") now = timezone.now() end_window = now + timedelta(hours=12) airings = Airing.objects.filter( channel=channel, ends_at__gt=now, starts_at__lt=end_window ).select_related('media_item').order_by('starts_at') self.stdout.write(self.get_color(f"\n=== Schedule for {channel.name} (Next 12h) ===", "1;34")) if not airings: self.stdout.write(self.get_color(" (No airings scheduled in this window)", "1;33")) else: for a in airings: time_str = f"{a.starts_at.strftime('%H:%M')} - {a.ends_at.strftime('%H:%M')}" if a.starts_at <= now <= a.ends_at: self.stdout.write(f" {self.get_color('▶ ON AIR', '1;32')} {self.get_color(time_str, '1;37')} | {a.media_item.title}") else: self.stdout.write(f" {time_str} | {a.media_item.title}") self.stdout.write("\n")