129 lines
4.3 KiB
Python
129 lines
4.3 KiB
Python
from django.conf import settings
|
|
from django.core.management.base import BaseCommand, CommandError
|
|
from django.db import transaction
|
|
|
|
from apps.accounts.models import ApiKey, User
|
|
|
|
|
|
def ensure_bootstrap_user(
|
|
*,
|
|
user_name: str,
|
|
password: str,
|
|
status: str,
|
|
is_staff: bool,
|
|
is_superuser: bool,
|
|
force_password: bool,
|
|
stdout,
|
|
) -> User:
|
|
user, created = User.objects.get_or_create(
|
|
user_name=user_name,
|
|
defaults={
|
|
"display_name": user_name,
|
|
"status": status,
|
|
"is_staff": is_staff,
|
|
"is_superuser": is_superuser,
|
|
},
|
|
)
|
|
|
|
if not created and user.status != status:
|
|
raise CommandError(
|
|
f"User {user_name!r} already exists with status {user.status!r}; "
|
|
f"cannot bootstrap as {status!r}"
|
|
)
|
|
|
|
should_set_password = created or not user.has_usable_password() or force_password
|
|
if not (user.display_name or "").strip():
|
|
user.display_name = user_name
|
|
if should_set_password:
|
|
user.set_password(password)
|
|
user.status = status
|
|
user.is_staff = is_staff
|
|
user.is_superuser = is_superuser
|
|
user.save()
|
|
action = "Created" if created else "Updated password for"
|
|
stdout.write(f"{action} {status} user {user_name}")
|
|
else:
|
|
user.status = status
|
|
user.is_staff = is_staff
|
|
user.is_superuser = is_superuser
|
|
user.save(
|
|
update_fields=["display_name", "status", "is_staff", "is_superuser", "updated_at"]
|
|
)
|
|
stdout.write(f"User {user_name} already exists (password unchanged)")
|
|
|
|
return user
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = (
|
|
"Create production login accounts only (no demo sites/cycles/data). "
|
|
"Uses BOOTSTRAP_ADMIN_* and optional BOOTSTRAP_STAFF_* from settings."
|
|
)
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument(
|
|
"--force-password",
|
|
action="store_true",
|
|
help="Reset password even when the user already has one",
|
|
)
|
|
|
|
@transaction.atomic
|
|
def handle(self, *args, **options):
|
|
force_password = options["force_password"]
|
|
|
|
admin_user_name = (settings.BOOTSTRAP_ADMIN_USER or "admin").strip()
|
|
admin_password = settings.BOOTSTRAP_ADMIN_PASSWORD or ""
|
|
if not admin_password:
|
|
raise CommandError(
|
|
"BOOTSTRAP_ADMIN_PASSWORD is required. Set it in .env before running bootstrap_admin."
|
|
)
|
|
|
|
admin = ensure_bootstrap_user(
|
|
user_name=admin_user_name,
|
|
password=admin_password,
|
|
status=User.STATUS_SUPERADMIN,
|
|
is_staff=True,
|
|
is_superuser=True,
|
|
force_password=force_password,
|
|
stdout=self.stdout,
|
|
)
|
|
|
|
staff_user_name = (settings.BOOTSTRAP_STAFF_USER or "").strip()
|
|
staff_password = settings.BOOTSTRAP_STAFF_PASSWORD or ""
|
|
if staff_user_name:
|
|
if staff_user_name == admin_user_name:
|
|
raise CommandError(
|
|
"BOOTSTRAP_STAFF_USER must differ from BOOTSTRAP_ADMIN_USER."
|
|
)
|
|
if not staff_password:
|
|
raise CommandError(
|
|
"BOOTSTRAP_STAFF_PASSWORD is required when BOOTSTRAP_STAFF_USER is set."
|
|
)
|
|
ensure_bootstrap_user(
|
|
user_name=staff_user_name,
|
|
password=staff_password,
|
|
status=User.STATUS_ACTIVE,
|
|
is_staff=True,
|
|
is_superuser=False,
|
|
force_password=force_password,
|
|
stdout=self.stdout,
|
|
)
|
|
|
|
bootstrap_key = settings.SITE_API_KEY
|
|
if bootstrap_key:
|
|
api_key, _ = ApiKey.generate(admin, "site")
|
|
api_key.prefix = bootstrap_key[:8]
|
|
api_key.key_hash = ApiKey.hash_key(bootstrap_key)
|
|
api_key.save(update_fields=["prefix", "key_hash", "updated_at"])
|
|
self.stdout.write(f"Site API key installed (prefix={api_key.prefix})")
|
|
|
|
accounts = admin_user_name
|
|
if staff_user_name:
|
|
accounts = f"{admin_user_name}, {staff_user_name}"
|
|
self.stdout.write(
|
|
self.style.SUCCESS(
|
|
f"Bootstrap OK: login={accounts} "
|
|
"(no demo farm data — use Pengaturan to add sites/kandang/cycles)"
|
|
)
|
|
)
|