Files
PYTV/core/services/bootstrap.py
2026-03-20 15:00:24 -04:00

93 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Bootstrap service — idempotent startup initialisation.
Ensures a default media library exists as soon as the first user is available.
Called from three places:
1. AppConfig.ready() every server start (DB already populated)
2. post_migrate signal after `manage.py migrate` runs
3. auth.setup_admin endpoint immediately after the first user is created
"""
import logging
logger = logging.getLogger(__name__)
DEFAULT_LIBRARY_NAME = "Default Library"
# One-shot flag: after the first request triggers the bootstrap, disconnect
# the signal so we don't repeat the DB check on every subsequent request.
_startup_bootstrap_done = False
def ensure_default_library():
"""
Create a default media library if none exists yet.
Idempotent — safe to call multiple times; does nothing when a library
already exists. Returns the newly-created Library, or None if no action
was taken (either a library already exists, or no users exist yet to be
the owner).
"""
from core.models import Library, AppUser
if Library.objects.exists():
return None # Already bootstrapped
# Need an owner — prefer the first superuser, fall back to any user.
owner = (
AppUser.objects.filter(is_superuser=True).order_by("date_joined").first()
or AppUser.objects.order_by("date_joined").first()
)
if owner is None:
# No users yet — setup_admin will call us again once the first user exists.
logger.debug("ensure_default_library: no users yet, skipping.")
return None
library = Library.objects.create(
owner_user=owner,
name=DEFAULT_LIBRARY_NAME,
visibility="public",
description=(
"Default media library. "
"Add media sources here to start building your channels."
),
)
logger.info(
"Bootstrap: created default library '%s' (id=%d) owned by '%s'.",
library.name,
library.id,
owner.username,
)
return library
# ---------------------------------------------------------------------------
# Signal handlers — wired up in CoreConfig.ready()
# ---------------------------------------------------------------------------
def _on_post_migrate(sender, **kwargs):
"""Run bootstrap after every successful migration."""
try:
ensure_default_library()
except Exception as exc: # pragma: no cover
logger.warning("ensure_default_library failed in post_migrate: %s", exc)
def _on_first_request(sender, **kwargs):
"""
One-shot: run bootstrap on the very first HTTP request after server start.
Disconnects itself immediately so subsequent requests pay zero overhead.
"""
global _startup_bootstrap_done
if _startup_bootstrap_done:
return
_startup_bootstrap_done = True
from django.core.signals import request_started
request_started.disconnect(_on_first_request)
try:
ensure_default_library()
except Exception as exc: # pragma: no cover
logger.warning("ensure_default_library failed on first request: %s", exc)