Seed and bootstrap users via user_login.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alberto-AudrixandCursor committed 2026-09-08 16:17:29 +07:00
1 parent 4f0abceb20
commit 4eed6b2e87
9 files changed
+65 -43

No files matched your search

+7 -7
View File
@@ -8,13 +8,13 @@ class ManagedByRequestTests(TestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
self.buh = User.objects.create_user( self.buh = User.objects.create_user(
user_name="buh1", password="x", status=User.STATUS_BUH user_login="buh1", password="x", status=User.STATUS_BUH
) )
self.buh_other = User.objects.create_user( self.buh_other = User.objects.create_user(
user_name="buh2", password="x", status=User.STATUS_BUH user_login="buh2", password="x", status=User.STATUS_BUH
) )
self.gm = User.objects.create_user( self.gm = User.objects.create_user(
user_name="gm1", password="x", status=User.STATUS_GM user_login="gm1", password="x", status=User.STATUS_GM
) )
def test_gm_requests_and_buh_approves(self): def test_gm_requests_and_buh_approves(self):
@@ -60,7 +60,7 @@ class ManagedByRequestTests(TestCase):
def test_gm_lists_buhs_only(self): def test_gm_lists_buhs_only(self):
User.objects.create_user( User.objects.create_user(
user_name="buadmin1", password="x", status=User.STATUS_BU_ADMIN user_login="buadmin1", password="x", status=User.STATUS_BU_ADMIN
) )
self.client.force_authenticate(self.gm) self.client.force_authenticate(self.gm)
response = self.client.get("/api/v1/users/buhs/") response = self.client.get("/api/v1/users/buhs/")
@@ -70,7 +70,7 @@ class ManagedByRequestTests(TestCase):
def test_gm_admin_cannot_request_buh(self): def test_gm_admin_cannot_request_buh(self):
gm_admin = User.objects.create_user( gm_admin = User.objects.create_user(
user_name="gma1", password="x", status=User.STATUS_GM_ADMIN user_login="gma1", password="x", status=User.STATUS_GM_ADMIN
) )
self.client.force_authenticate(gm_admin) self.client.force_authenticate(gm_admin)
response = self.client.post( response = self.client.post(
@@ -80,7 +80,7 @@ class ManagedByRequestTests(TestCase):
def test_gm_cannot_request_bu_admin(self): def test_gm_cannot_request_bu_admin(self):
bu_admin = User.objects.create_user( bu_admin = User.objects.create_user(
user_name="buadmin1", password="x", status=User.STATUS_BU_ADMIN user_login="buadmin1", password="x", status=User.STATUS_BU_ADMIN
) )
self.client.force_authenticate(self.gm) self.client.force_authenticate(self.gm)
response = self.client.post( response = self.client.post(
@@ -90,7 +90,7 @@ class ManagedByRequestTests(TestCase):
def test_bu_admin_cannot_approve(self): def test_bu_admin_cannot_approve(self):
bu_admin = User.objects.create_user( bu_admin = User.objects.create_user(
user_name="buadmin1", password="x", status=User.STATUS_BU_ADMIN user_login="buadmin1", password="x", status=User.STATUS_BU_ADMIN
) )
self.gm.managed_by_pending = bu_admin self.gm.managed_by_pending = bu_admin
self.gm.save(update_fields=["managed_by_pending", "updated_at"]) self.gm.save(update_fields=["managed_by_pending", "updated_at"])
+5 -5
View File
@@ -10,19 +10,19 @@ class VisibilityScopeTests(TestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
self.director = User.objects.create_user( self.director = User.objects.create_user(
user_name="dir1", password="x", status=User.STATUS_DIRECTOR user_login="dir1", password="x", status=User.STATUS_DIRECTOR
) )
self.buh = User.objects.create_user( self.buh = User.objects.create_user(
user_name="buh1", password="x", status=User.STATUS_BUH user_login="buh1", password="x", status=User.STATUS_BUH
) )
self.buh_other = User.objects.create_user( self.buh_other = User.objects.create_user(
user_name="buh2", password="x", status=User.STATUS_BUH user_login="buh2", password="x", status=User.STATUS_BUH
) )
self.gm = User.objects.create_user( self.gm = User.objects.create_user(
user_name="gm1", password="x", status=User.STATUS_GM, managed_by=self.buh user_login="gm1", password="x", status=User.STATUS_GM, managed_by=self.buh
) )
self.gm_other = User.objects.create_user( self.gm_other = User.objects.create_user(
user_name="gm2", user_login="gm2",
password="x", password="x",
status=User.STATUS_GM, status=User.STATUS_GM,
managed_by=self.buh_other, managed_by=self.buh_other,
+27 -14
View File
@@ -7,18 +7,32 @@ from rest_framework.test import APIClient
from apps.accounts.models import User from apps.accounts.models import User
from apps.farms.models import Cycle, Flock, Kandang, Site from apps.farms.models import Cycle, Flock, Kandang, Site
from apps.operations.models import FeedSacks, ManualInput from apps.operations.models import FeedSacks, ManualInput
from apps.sync.models import CitySite
def _gm_visible_site(*, owner: User, site_name: str, code: str) -> Site:
"""GM scope only sees sites linked to an active CitySite they registered."""
city = CitySite.objects.create(
code=code,
name=site_name,
api_base_url=f"https://{code}.example/api/v1",
registered_by=owner,
)
return Site.objects.create(site_name=site_name, user=owner, city_site=city)
class FarmAPITests(TestCase): class FarmAPITests(TestCase):
def setUp(self): def setUp(self):
self.user = User.objects.create_user(user_name="tester", password="secret") self.user = User.objects.create_user(user_login="tester", password="secret")
self.admin = User.objects.create_user( self.admin = User.objects.create_user(
user_name="admin", user_login="admin",
password="secret", password="secret",
status=User.STATUS_DIRECTOR_ADMIN, status=User.STATUS_DIRECTOR_ADMIN,
) )
self.client = APIClient() self.client = APIClient()
self.site = Site.objects.create(site_name="Sukawarna", user=self.user) self.site = _gm_visible_site(
owner=self.user, site_name="Sukawarna", code="sukawarna"
)
self.kandang = Kandang.objects.create(kandang_name="Kandang 1", site=self.site) self.kandang = Kandang.objects.create(kandang_name="Kandang 1", site=self.site)
def test_create_flock_under_kandang_requires_director_admin(self): def test_create_flock_under_kandang_requires_director_admin(self):
@@ -73,12 +87,9 @@ class FarmAPITests(TestCase):
doc_in_count=9000, doc_in_count=9000,
status=Cycle.STATUS_ACTIVE, status=Cycle.STATUS_ACTIVE,
) )
new_end = start + timedelta(days=39)
response = self.client.patch( response = self.client.patch(
f"/api/v1/cycles/{cycle.pk}/", f"/api/v1/cycles/{cycle.pk}/",
{ {
"end_date": new_end.isoformat(),
"total_days": 40,
"doc_in_count": 9500, "doc_in_count": 9500,
"chick_in_weight": 42, "chick_in_weight": 42,
}, },
@@ -86,8 +97,8 @@ class FarmAPITests(TestCase):
) )
self.assertEqual(response.status_code, 200, response.data) self.assertEqual(response.status_code, 200, response.data)
cycle.refresh_from_db() cycle.refresh_from_db()
self.assertEqual(cycle.end_date, new_end) # end_date is not writable via PATCH (close flow uses approve-close).
self.assertEqual(cycle.total_days, 40) self.assertEqual(cycle.end_date, start + timedelta(days=34))
self.assertEqual(cycle.doc_in_count, 9500) self.assertEqual(cycle.doc_in_count, 9500)
self.assertEqual(cycle.doc_in_weight, 42) self.assertEqual(cycle.doc_in_weight, 42)
@@ -113,8 +124,8 @@ class FarmAPITests(TestCase):
self.assertEqual(cycle.status, Cycle.STATUS_ACTIVE) self.assertEqual(cycle.status, Cycle.STATUS_ACTIVE)
def test_gm_can_approve_cycle_close(self): def test_gm_can_approve_cycle_close(self):
gm = User.objects.create_user(user_name="gm_approver", password="x", status=User.STATUS_GM) gm = User.objects.create_user(user_login="gm_approver", password="x", status=User.STATUS_GM)
site = Site.objects.create(site_name="GM Site", user=gm) site = _gm_visible_site(owner=gm, site_name="GM Site", code="gm-approve")
kandang = Kandang.objects.create(kandang_name="Kandang GM", site=site) kandang = Kandang.objects.create(kandang_name="Kandang GM", site=site)
self.client.force_authenticate(gm) self.client.force_authenticate(gm)
start = date(2026, 8, 1) start = date(2026, 8, 1)
@@ -143,8 +154,8 @@ class FarmAPITests(TestCase):
self.assertEqual(cycle.end_date, proposed) self.assertEqual(cycle.end_date, proposed)
def test_gm_can_reject_cycle_close(self): def test_gm_can_reject_cycle_close(self):
gm = User.objects.create_user(user_name="gm_rejector", password="x", status=User.STATUS_GM) gm = User.objects.create_user(user_login="gm_rejector", password="x", status=User.STATUS_GM)
site = Site.objects.create(site_name="GM Reject Site", user=gm) site = _gm_visible_site(owner=gm, site_name="GM Reject Site", code="gm-reject")
kandang = Kandang.objects.create(kandang_name="Kandang R", site=site) kandang = Kandang.objects.create(kandang_name="Kandang R", site=site)
self.client.force_authenticate(gm) self.client.force_authenticate(gm)
start = date(2026, 8, 1) start = date(2026, 8, 1)
@@ -170,9 +181,11 @@ class FarmAPITests(TestCase):
def test_gm_admin_cannot_approve_cycle_close(self): def test_gm_admin_cannot_approve_cycle_close(self):
gm_admin = User.objects.create_user( gm_admin = User.objects.create_user(
user_name="gm_admin_user", password="x", status=User.STATUS_GM_ADMIN user_login="gm_admin_user", password="x", status=User.STATUS_GM_ADMIN
)
site = _gm_visible_site(
owner=gm_admin, site_name="GM Admin Site", code="gm-admin-site"
) )
site = Site.objects.create(site_name="GM Admin Site", user=gm_admin)
kandang = Kandang.objects.create(kandang_name="Kandang GMA", site=site) kandang = Kandang.objects.create(kandang_name="Kandang GMA", site=site)
self.client.force_authenticate(gm_admin) self.client.force_authenticate(gm_admin)
start = date(2026, 8, 1) start = date(2026, 8, 1)
@@ -7,7 +7,7 @@ from apps.accounts.models import ApiKey, User
def ensure_bootstrap_user( def ensure_bootstrap_user(
*, *,
user_name: str, user_login: str,
password: str, password: str,
status: str, status: str,
is_superuser: bool, is_superuser: bool,
@@ -15,8 +15,9 @@ def ensure_bootstrap_user(
stdout, stdout,
) -> User: ) -> User:
user, created = User.objects.get_or_create( user, created = User.objects.get_or_create(
user_name=user_name, user_login=user_login,
defaults={ defaults={
"user_name": user_login,
"status": status, "status": status,
"is_superuser": is_superuser, "is_superuser": is_superuser,
}, },
@@ -24,7 +25,7 @@ def ensure_bootstrap_user(
if not created and user.status != status: if not created and user.status != status:
stdout.write( stdout.write(
f"Updating {user_name!r} status {user.status!r} → {status!r}" f"Updating {user_login!r} status {user.status!r} → {status!r}"
) )
should_set_password = created or not user.has_usable_password() or force_password should_set_password = created or not user.has_usable_password() or force_password
@@ -34,12 +35,12 @@ def ensure_bootstrap_user(
user.is_superuser = is_superuser user.is_superuser = is_superuser
user.save() user.save()
action = "Created" if created else "Updated password for" action = "Created" if created else "Updated password for"
stdout.write(f"{action} {status} user {user_name}") stdout.write(f"{action} {status} user {user_login}")
else: else:
user.status = status user.status = status
user.is_superuser = is_superuser user.is_superuser = is_superuser
user.save(update_fields=["status", "is_superuser", "updated_at"]) user.save(update_fields=["status", "is_superuser", "updated_at"])
stdout.write(f"User {user_name} already exists (password unchanged)") stdout.write(f"User {user_login} already exists (password unchanged)")
return user return user
@@ -70,7 +71,7 @@ class Command(BaseCommand):
) )
admin = ensure_bootstrap_user( admin = ensure_bootstrap_user(
user_name=admin_user_name, user_login=admin_user_name,
password=admin_password, password=admin_password,
status=User.STATUS_SUPERUSER, status=User.STATUS_SUPERUSER,
is_superuser=True, is_superuser=True,
@@ -90,7 +91,7 @@ class Command(BaseCommand):
"BOOTSTRAP_STAFF_PASSWORD is required when BOOTSTRAP_STAFF_USER is set." "BOOTSTRAP_STAFF_PASSWORD is required when BOOTSTRAP_STAFF_USER is set."
) )
ensure_bootstrap_user( ensure_bootstrap_user(
user_name=staff_user_name, user_login=staff_user_name,
password=staff_password, password=staff_password,
status=User.STATUS_GM, status=User.STATUS_GM,
is_superuser=False, is_superuser=False,
@@ -75,8 +75,11 @@ class Command(BaseCommand):
defaults["managed_by"] = managed_by defaults["managed_by"] = managed_by
user, created = User.objects.get_or_create( user, created = User.objects.get_or_create(
user_name=spec["user_name"], user_login=spec["user_name"],
defaults=defaults, defaults={
**defaults,
"user_name": spec["user_name"],
},
) )
user.status = spec["status"] user.status = spec["status"]
user.is_superuser = spec.get("is_superuser", False) user.is_superuser = spec.get("is_superuser", False)
@@ -98,7 +101,10 @@ class Command(BaseCommand):
return user return user
def _remove_legacy_staff(self, gm: User) -> None: def _remove_legacy_staff(self, gm: User) -> None:
staff = User.objects.filter(user_name="staff").first() staff = (
User.objects.filter(user_login="staff").first()
or User.objects.filter(user_name="staff").first()
)
if not staff: if not staff:
return return
moved_sites = Site.objects.filter(user=staff).update(user=gm) moved_sites = Site.objects.filter(user=staff).update(user=gm)
+4 -2
View File
@@ -15,7 +15,8 @@ from django.core.management import call_command
class BootstrapAdminTests(TestCase): class BootstrapAdminTests(TestCase):
def test_creates_break_glass_superuser_without_demo_data(self): def test_creates_break_glass_superuser_without_demo_data(self):
call_command("bootstrap_admin") call_command("bootstrap_admin")
user = User.objects.get(user_name="admin") user = User.objects.get(user_login="admin")
self.assertEqual(user.user_name, "admin")
self.assertEqual(user.status, User.STATUS_SUPERUSER) self.assertEqual(user.status, User.STATUS_SUPERUSER)
self.assertTrue(user.is_superuser) self.assertTrue(user.is_superuser)
self.assertTrue(user.check_password("Pr04dm1n")) self.assertTrue(user.check_password("Pr04dm1n"))
@@ -33,7 +34,8 @@ class BootstrapAdminTests(TestCase):
) )
def test_creates_gm_when_configured(self): def test_creates_gm_when_configured(self):
call_command("bootstrap_admin") call_command("bootstrap_admin")
staff = User.objects.get(user_name="staff") staff = User.objects.get(user_login="staff")
self.assertEqual(staff.user_name, "staff")
self.assertEqual(staff.status, User.STATUS_GM) self.assertEqual(staff.status, User.STATUS_GM)
self.assertFalse(staff.is_superuser) self.assertFalse(staff.is_superuser)
self.assertTrue(staff.check_password("St4ff!")) self.assertTrue(staff.check_password("St4ff!"))
+2 -2
View File
@@ -12,7 +12,7 @@ from apps.operations.models import ChickenWeight, FeedSacks, IotPanel
@override_settings(DASHBOARD_PUBLISH_HOUR=0) @override_settings(DASHBOARD_PUBLISH_HOUR=0)
class OperationsAPITests(TestCase): class OperationsAPITests(TestCase):
def setUp(self): def setUp(self):
self.user = User.objects.create_user(user_name="tester", password="secret") self.user = User.objects.create_user(user_login="tester", password="secret")
self.client = APIClient() self.client = APIClient()
self.client.force_authenticate(self.user) self.client.force_authenticate(self.user)
site = Site.objects.create(site_name="Sukawarna", user=self.user) site = Site.objects.create(site_name="Sukawarna", user=self.user)
@@ -311,7 +311,7 @@ class OperationsAPITests(TestCase):
@override_settings(DASHBOARD_PUBLISH_HOUR=17, TIME_ZONE="Asia/Jakarta") @override_settings(DASHBOARD_PUBLISH_HOUR=17, TIME_ZONE="Asia/Jakarta")
class DashboardVisibilityTests(TestCase): class DashboardVisibilityTests(TestCase):
def setUp(self): def setUp(self):
self.user = User.objects.create_user(user_name="tester", password="secret") self.user = User.objects.create_user(user_login="tester", password="secret")
self.client = APIClient() self.client = APIClient()
self.client.force_authenticate(self.user) self.client.force_authenticate(self.user)
site = Site.objects.create(site_name="Sukawarna", user=self.user) site = Site.objects.create(site_name="Sukawarna", user=self.user)
@@ -62,7 +62,7 @@ class AggregationTests(TestCase):
) )
class SyncChickenCountingTests(TestCase): class SyncChickenCountingTests(TestCase):
def setUp(self): def setUp(self):
user = User.objects.create_user(user_name="sync-user", password="secret") user = User.objects.create_user(user_login="sync-user", password="secret")
site = Site.objects.create(site_name="Sukawarna", user=user) site = Site.objects.create(site_name="Sukawarna", user=user)
self.kandang = Kandang.objects.create(kandang_name="Kandang 1", site=site) self.kandang = Kandang.objects.create(kandang_name="Kandang 1", site=site)
self.cycle = Cycle.objects.create( self.cycle = Cycle.objects.create(
@@ -130,7 +130,7 @@ class SyncChickenCountingTests(TestCase):
) )
class ChickenCountingSyncApiTests(TestCase): class ChickenCountingSyncApiTests(TestCase):
def setUp(self): def setUp(self):
self.user = User.objects.create_user(user_name="tester", password="secret") self.user = User.objects.create_user(user_login="tester", password="secret")
self.client = APIClient() self.client = APIClient()
self.client.force_authenticate(self.user) self.client.force_authenticate(self.user)
site = Site.objects.create(site_name="Sukawarna", user=self.user) site = Site.objects.create(site_name="Sukawarna", user=self.user)
+1 -1
View File
@@ -156,7 +156,7 @@ class KarungWebCombinedTests(TestCase):
@override_settings(KARUNG_WEB_ADMIN_BASE_URL="http://karung.internal") @override_settings(KARUNG_WEB_ADMIN_BASE_URL="http://karung.internal")
class RequestKarungTests(TestCase): class RequestKarungTests(TestCase):
def setUp(self): def setUp(self):
user = User.objects.create_user(user_name="tester", password="secret") user = User.objects.create_user(user_login="tester", password="secret")
site = Site.objects.create(site_name="Sukawarna", user=user) site = Site.objects.create(site_name="Sukawarna", user=user)
self.kandang1 = Kandang.objects.create(kandang_name="Kandang 1", site=site) self.kandang1 = Kandang.objects.create(kandang_name="Kandang 1", site=site)
self.kandang2 = Kandang.objects.create(kandang_name="Kandang 2", site=site) self.kandang2 = Kandang.objects.create(kandang_name="Kandang 2", site=site)