93 lines
3.1 KiB
Python
93 lines
3.1 KiB
Python
|
|
"""
|
|||
|
|
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)
|
|||
|
|
|