feat(main): initial commit
This commit is contained in:
0
core/management/__init__.py
Normal file
0
core/management/__init__.py
Normal file
0
core/management/commands/__init__.py
Normal file
0
core/management/commands/__init__.py
Normal file
209
core/management/commands/seed.py
Normal file
209
core/management/commands/seed.py
Normal file
@@ -0,0 +1,209 @@
|
||||
import random
|
||||
from datetime import time, timedelta
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
from core.models import (
|
||||
AppUser, Library, Genre, ContentRating, MediaSource,
|
||||
Series, MediaItem, Channel, ScheduleTemplate, ScheduleBlock, Airing
|
||||
)
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Seeds the database with mock PYTV data including users, libraries, media, and channels.'
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
self.stdout.write("Flushing existing data...")
|
||||
# Since we cascade everything, deleting users usually drops almost everything
|
||||
AppUser.objects.filter(username__startswith='mock_').delete()
|
||||
Genre.objects.all().delete()
|
||||
ContentRating.objects.all().delete()
|
||||
|
||||
self.stdout.write("Seeding Users...")
|
||||
admin_user = AppUser.objects.create_superuser('mock_admin', 'admin@pytv.local', 'admin')
|
||||
viewer_user = AppUser.objects.create_user('mock_viewer', 'viewer@pytv.local', 'password')
|
||||
|
||||
self.stdout.write("Seeding Genres & Ratings...")
|
||||
g_action = Genre.objects.create(name="Action")
|
||||
g_comedy = Genre.objects.create(name="Comedy")
|
||||
g_drama = Genre.objects.create(name="Drama")
|
||||
g_scifi = Genre.objects.create(name="Sci-Fi")
|
||||
g_promo = Genre.objects.create(name="Promo/Bumper")
|
||||
|
||||
r_pg = ContentRating.objects.create(system_name="TV Parental Guidelines", code="TV-PG", min_age=8)
|
||||
r_14 = ContentRating.objects.create(system_name="TV Parental Guidelines", code="TV-14", min_age=14)
|
||||
r_ma = ContentRating.objects.create(system_name="TV Parental Guidelines", code="TV-MA", min_age=17)
|
||||
|
||||
self.stdout.write("Seeding Library & Media Sources...")
|
||||
lib = Library.objects.create(
|
||||
owner_user=admin_user,
|
||||
name="Main Broadcasting Library",
|
||||
visibility="public",
|
||||
description="The core streaming library for PYTV mock."
|
||||
)
|
||||
|
||||
source_movies = MediaSource.objects.create(
|
||||
library=lib,
|
||||
name="Mocked Local Movies",
|
||||
source_type="local_directory",
|
||||
uri="/mock/movies"
|
||||
)
|
||||
|
||||
source_tv = MediaSource.objects.create(
|
||||
library=lib,
|
||||
name="Mocked Local TV Shows",
|
||||
source_type="local_directory",
|
||||
uri="/mock/tv"
|
||||
)
|
||||
|
||||
source_bumpers = MediaSource.objects.create(
|
||||
library=lib,
|
||||
name="Network Bumpers",
|
||||
source_type="local_directory",
|
||||
uri="/mock/bumpers"
|
||||
)
|
||||
|
||||
self.stdout.write("Seeding Series & Media Items...")
|
||||
|
||||
# Movies
|
||||
m1 = MediaItem.objects.create(
|
||||
media_source=source_movies,
|
||||
title="Space Rangers 3000",
|
||||
item_kind="movie",
|
||||
release_year=1999,
|
||||
runtime_seconds=5400, # 1.5 hours
|
||||
file_path="/mock/movies/space_rangers.mp4",
|
||||
content_rating=r_pg
|
||||
)
|
||||
m1.genres.add(g_action, g_scifi)
|
||||
|
||||
m2 = MediaItem.objects.create(
|
||||
media_source=source_movies,
|
||||
title="The Laughing Policeman",
|
||||
item_kind="movie",
|
||||
release_year=2005,
|
||||
runtime_seconds=6300, # 1.75 hours
|
||||
file_path="/mock/movies/laughing_policeman.mp4",
|
||||
content_rating=r_14
|
||||
)
|
||||
m2.genres.add(g_comedy, g_action)
|
||||
|
||||
# Series
|
||||
s1 = Series.objects.create(title="Neon City Nights", description="Cyberpunk detective drama.", release_year=2024)
|
||||
|
||||
for ep in range(1, 6):
|
||||
ep_item = MediaItem.objects.create(
|
||||
media_source=source_tv,
|
||||
series=s1,
|
||||
title=f"Episode {ep}",
|
||||
item_kind="episode",
|
||||
season_number=1,
|
||||
episode_number=ep,
|
||||
runtime_seconds=2700, # 45 mins
|
||||
file_path=f"/mock/tv/neon_city_nights/s01e0{ep}.mkv",
|
||||
content_rating=r_ma
|
||||
)
|
||||
ep_item.genres.add(g_drama, g_scifi)
|
||||
|
||||
# Bumpers
|
||||
for b in range(1, 4):
|
||||
bump = MediaItem.objects.create(
|
||||
media_source=source_bumpers,
|
||||
title=f"Station Bumper {b}",
|
||||
item_kind="bumper",
|
||||
runtime_seconds=15,
|
||||
file_path=f"/mock/bumpers/ident_{b}.mp4"
|
||||
)
|
||||
bump.genres.add(g_promo)
|
||||
|
||||
self.stdout.write("Seeding Channels and Scheduling Blocks...")
|
||||
ch1 = Channel.objects.create(
|
||||
owner_user=admin_user,
|
||||
library=lib,
|
||||
name="PYTV One",
|
||||
slug="pytv-1",
|
||||
channel_number=1,
|
||||
description="The flagship generic network.",
|
||||
scheduling_mode="template_driven",
|
||||
visibility="public"
|
||||
)
|
||||
|
||||
template = ScheduleTemplate.objects.create(
|
||||
channel=ch1,
|
||||
name="Standard Weekday",
|
||||
timezone_name="UTC",
|
||||
priority=10
|
||||
)
|
||||
|
||||
# Prime time block
|
||||
b_prime = ScheduleBlock.objects.create(
|
||||
schedule_template=template,
|
||||
name="Prime Time Drama",
|
||||
block_type="programming",
|
||||
start_local_time=time(20, 0),
|
||||
end_local_time=time(23, 0),
|
||||
day_of_week_mask=127, # Everyday
|
||||
default_genre=g_drama,
|
||||
rotation_strategy="sequential",
|
||||
pad_strategy="fill_with_interstitials"
|
||||
)
|
||||
|
||||
ch2 = Channel.objects.create(
|
||||
owner_user=admin_user,
|
||||
library=lib,
|
||||
name="Tears of Steel Channel",
|
||||
slug="tears-of-steel",
|
||||
channel_number=2,
|
||||
description="All Sci-Fi all the time.",
|
||||
scheduling_mode="template_driven",
|
||||
visibility="public"
|
||||
)
|
||||
|
||||
template2 = ScheduleTemplate.objects.create(
|
||||
channel=ch2,
|
||||
name="Sci-Fi Everyday",
|
||||
timezone_name="UTC",
|
||||
priority=10
|
||||
)
|
||||
|
||||
ScheduleBlock.objects.create(
|
||||
schedule_template=template2,
|
||||
name="Sci-Fi Block",
|
||||
block_type="programming",
|
||||
start_local_time=time(0, 0),
|
||||
end_local_time=time(23, 59, 59),
|
||||
day_of_week_mask=127,
|
||||
default_genre=g_scifi,
|
||||
rotation_strategy="random",
|
||||
pad_strategy="none"
|
||||
)
|
||||
|
||||
ch3 = Channel.objects.create(
|
||||
owner_user=admin_user,
|
||||
library=lib,
|
||||
name="Sintel Classics",
|
||||
slug="sintel-classics",
|
||||
channel_number=3,
|
||||
description="Classic movies and animation.",
|
||||
scheduling_mode="template_driven",
|
||||
visibility="public"
|
||||
)
|
||||
|
||||
template3 = ScheduleTemplate.objects.create(
|
||||
channel=ch3,
|
||||
name="Comedy and Action",
|
||||
timezone_name="UTC",
|
||||
priority=10
|
||||
)
|
||||
|
||||
ScheduleBlock.objects.create(
|
||||
schedule_template=template3,
|
||||
name="Action Comedy",
|
||||
block_type="programming",
|
||||
start_local_time=time(0, 0),
|
||||
end_local_time=time(23, 59, 59),
|
||||
day_of_week_mask=127,
|
||||
default_genre=g_action,
|
||||
rotation_strategy="random",
|
||||
pad_strategy="none"
|
||||
)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("Successfully seeded the PYTV database with mock data."))
|
||||
60
core/management/commands/state.py
Normal file
60
core/management/commands/state.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from core.models import AppUser, Library, Channel, MediaItem, Airing, ScheduleTemplate
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Displays a beautifully formatted terminal dashboard of the current backend state."
|
||||
|
||||
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):
|
||||
# 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.channel_number or '-'}] {c.name} ({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("\n")
|
||||
Reference in New Issue
Block a user