remove wagtail and admin website, remove edit option, from hitung ayam, add create flock to settings

This commit is contained in:
Alberto-Audrix committed 2026-08-31 16:00:44 +07:00
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}"
+30
View File
@@ -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")