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)
|
||
|