remove wagtail and admin website, remove edit option, from hitung ayam, add create flock to settings
This commit is contained in:
1 parent
74dd3faf60
commit
30a736641f
39 files changed
+634
-819
No files matched your search
@@ -5,62 +5,119 @@ 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={
|
||||
"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 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=["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 superadmin login only (no demo sites/cycles/data). "
|
||||
"Uses BOOTSTRAP_ADMIN_USER and BOOTSTRAP_ADMIN_PASSWORD from settings."
|
||||
"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 admin user already has one",
|
||||
help="Reset password even when the user already has one",
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
user_name = (settings.BOOTSTRAP_ADMIN_USER or "admin").strip()
|
||||
password = settings.BOOTSTRAP_ADMIN_PASSWORD or ""
|
||||
if not password:
|
||||
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."
|
||||
)
|
||||
|
||||
user, created = User.objects.get_or_create(
|
||||
user_name=user_name,
|
||||
defaults={
|
||||
"status": User.STATUS_SUPERADMIN,
|
||||
"is_staff": True,
|
||||
"is_superuser": True,
|
||||
},
|
||||
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,
|
||||
)
|
||||
|
||||
should_set_password = created or not user.has_usable_password() or options["force_password"]
|
||||
if should_set_password:
|
||||
user.set_password(password)
|
||||
user.status = User.STATUS_SUPERADMIN
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
user.save()
|
||||
action = "Created" if created else "Updated password for"
|
||||
self.stdout.write(f"{action} superadmin {user_name}")
|
||||
else:
|
||||
user.status = User.STATUS_SUPERADMIN
|
||||
user.is_superuser = True
|
||||
user.save(update_fields=["status", "is_superuser", "updated_at"])
|
||||
self.stdout.write(f"Superadmin {user_name} already exists (password unchanged)")
|
||||
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.BOOTSTRAP_API_KEY
|
||||
if bootstrap_key:
|
||||
api_key, _ = ApiKey.generate(user, "bootstrap")
|
||||
api_key, _ = ApiKey.generate(admin, "bootstrap")
|
||||
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"Bootstrap 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={user_name} (no demo farm data — use Pengaturan to add sites/kandang/cycles)"
|
||||
f"Bootstrap OK: login={accounts} "
|
||||
"(no demo farm data — use Pengaturan to add sites/kandang/cycles)"
|
||||
)
|
||||
)
|
||||
@@ -133,7 +133,7 @@ class Command(BaseCommand):
|
||||
real_by_date = self.load_real_rows(Path(iot_json))
|
||||
|
||||
cycles = Cycle.objects.filter(
|
||||
start_date=cycle_start, flocks__isnull=False
|
||||
start_date=cycle_start, kandang__flocks__isnull=False
|
||||
).distinct()
|
||||
if not cycles:
|
||||
raise CommandError(f"No cycles starting {cycle_start} with flocks")
|
||||
@@ -141,7 +141,7 @@ class Command(BaseCommand):
|
||||
seeded = 0
|
||||
for cycle in cycles:
|
||||
kandang_name = cycle.kandang.kandang_name
|
||||
for flock in cycle.flocks.order_by("flock_id"):
|
||||
for flock in cycle.kandang.flocks.order_by("flock_id"):
|
||||
profile = self.profile_for(kandang_name, flock.flock_name)
|
||||
rng = random.Random(options["jitter_seed"] + flock.pk)
|
||||
rows = self.build_flock_rows(flock, cycle, real_by_date, profile, rng)
|
||||
|
||||
@@ -206,8 +206,11 @@ class Command(BaseCommand):
|
||||
ChickenWeight.objects.filter(cycle=existing).delete()
|
||||
FeedSacks.objects.filter(cycle=existing).delete()
|
||||
ManualInput.objects.filter(cycle=existing).delete()
|
||||
IotPanel.objects.filter(flock__cycle=existing).delete()
|
||||
Flock.objects.filter(cycle=existing).delete()
|
||||
IotPanel.objects.filter(
|
||||
flock__kandang=kandang,
|
||||
date__gte=existing.start_date,
|
||||
date__lte=existing.end_date,
|
||||
).delete()
|
||||
existing.delete()
|
||||
self.stdout.write("Reset previous Kandang 1 cycle")
|
||||
|
||||
@@ -232,7 +235,7 @@ class Command(BaseCommand):
|
||||
|
||||
flocks_by_name: dict[str, Flock] = {}
|
||||
for name in FLOCK_NAMES:
|
||||
flock, _ = Flock.objects.get_or_create(flock_name=name, cycle=cycle)
|
||||
flock, _ = Flock.objects.get_or_create(flock_name=name, kandang=kandang)
|
||||
flocks_by_name[name] = flock
|
||||
|
||||
dates = [START_DATE + timedelta(days=i) for i in range(TOTAL_DAYS)]
|
||||
@@ -426,7 +429,7 @@ class Command(BaseCommand):
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"Seeded cycle={cycle.pk} site={site.pk} kandang={kandang.pk} "
|
||||
f"flocks={Flock.objects.filter(cycle=cycle).count()} "
|
||||
f"flocks={Flock.objects.filter(kandang=kandang).count()} "
|
||||
f"cc={len(cc_by_date)} cw={len(cw_by_date)} fs={len(fs_by_date)} "
|
||||
f"manual={len(manual_by_date)} kpi={KPI.objects.filter(cycle=cycle).count()} "
|
||||
f"iot_panel={panel_count} closeout_fcr={last_kpi.fcr if last_kpi else None} "
|
||||
|
||||
@@ -96,8 +96,11 @@ class Command(BaseCommand):
|
||||
ChickenWeight.objects.filter(cycle=existing).delete()
|
||||
FeedSacks.objects.filter(cycle=existing).delete()
|
||||
ManualInput.objects.filter(cycle=existing).delete()
|
||||
IotPanel.objects.filter(flock__cycle=existing).delete()
|
||||
Flock.objects.filter(cycle=existing).delete()
|
||||
IotPanel.objects.filter(
|
||||
flock__kandang=kandang,
|
||||
date__gte=existing.start_date,
|
||||
date__lte=existing.end_date,
|
||||
).delete()
|
||||
existing.delete()
|
||||
self.stdout.write("Reset previous Kandang 2 cycle")
|
||||
|
||||
@@ -122,7 +125,7 @@ class Command(BaseCommand):
|
||||
|
||||
flocks_by_name: dict[str, Flock] = {}
|
||||
for name in ("Lantai 1", "Lantai 2"):
|
||||
flock, _ = Flock.objects.get_or_create(flock_name=name, cycle=cycle)
|
||||
flock, _ = Flock.objects.get_or_create(flock_name=name, kandang=kandang)
|
||||
flocks_by_name[name] = flock
|
||||
|
||||
cc_by_date = {}
|
||||
@@ -288,7 +291,7 @@ class Command(BaseCommand):
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"Seeded cycle={cycle.pk} site={site.pk} kandang={kandang.pk} "
|
||||
f"flocks={Flock.objects.filter(cycle=cycle).count()} "
|
||||
f"flocks={Flock.objects.filter(kandang=kandang).count()} "
|
||||
f"cc={len(cc_by_date)} cw={len(cw_by_date)} fs={len(fs_by_date)} "
|
||||
f"manual={len(manual_by_date)} kpi={KPI.objects.filter(cycle=cycle).count()} "
|
||||
f"iot_panel={panel_count} closeout_fcr={last_kpi.fcr if last_kpi else None}"
|
||||
|
||||
@@ -9,6 +9,8 @@ from django.core.management import call_command
|
||||
@override_settings(
|
||||
BOOTSTRAP_ADMIN_USER="admin",
|
||||
BOOTSTRAP_ADMIN_PASSWORD="Pr04dm1n",
|
||||
BOOTSTRAP_STAFF_USER="",
|
||||
BOOTSTRAP_STAFF_PASSWORD="",
|
||||
)
|
||||
class BootstrapAdminTests(TestCase):
|
||||
def test_creates_superadmin_without_demo_data(self):
|
||||
@@ -23,3 +25,31 @@ class BootstrapAdminTests(TestCase):
|
||||
with self.settings(BOOTSTRAP_ADMIN_PASSWORD=""):
|
||||
with self.assertRaises(CommandError):
|
||||
call_command("bootstrap_admin")
|
||||
|
||||
@override_settings(
|
||||
BOOTSTRAP_STAFF_USER="staff",
|
||||
BOOTSTRAP_STAFF_PASSWORD="St4ff!",
|
||||
)
|
||||
def test_creates_staff_when_configured(self):
|
||||
call_command("bootstrap_admin")
|
||||
staff = User.objects.get(user_name="staff")
|
||||
self.assertEqual(staff.status, User.STATUS_ACTIVE)
|
||||
self.assertTrue(staff.is_staff)
|
||||
self.assertFalse(staff.is_superuser)
|
||||
self.assertTrue(staff.check_password("St4ff!"))
|
||||
|
||||
@override_settings(
|
||||
BOOTSTRAP_STAFF_USER="staff",
|
||||
BOOTSTRAP_STAFF_PASSWORD="",
|
||||
)
|
||||
def test_requires_staff_password_when_user_set(self):
|
||||
with self.assertRaises(CommandError):
|
||||
call_command("bootstrap_admin")
|
||||
|
||||
@override_settings(
|
||||
BOOTSTRAP_STAFF_USER="admin",
|
||||
BOOTSTRAP_STAFF_PASSWORD="St4ff!",
|
||||
)
|
||||
def test_rejects_staff_same_as_admin(self):
|
||||
with self.assertRaises(CommandError):
|
||||
call_command("bootstrap_admin")
|
||||
Reference in new issue
Block a user