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):
self.client = APIClient()
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(
user_name="buh2", password="x", status=User.STATUS_BUH
user_login="buh2", password="x", status=User.STATUS_BUH
)
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):
@@ -60,7 +60,7 @@ class ManagedByRequestTests(TestCase):
def test_gm_lists_buhs_only(self):
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)
response = self.client.get("/api/v1/users/buhs/")
@@ -70,7 +70,7 @@ class ManagedByRequestTests(TestCase):
def test_gm_admin_cannot_request_buh(self):
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)
response = self.client.post(
@@ -80,7 +80,7 @@ class ManagedByRequestTests(TestCase):
def test_gm_cannot_request_bu_admin(self):
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)
response = self.client.post(
@@ -90,7 +90,7 @@ class ManagedByRequestTests(TestCase):
def test_bu_admin_cannot_approve(self):
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.save(update_fields=["managed_by_pending", "updated_at"])
+5 -5
View File
@@ -10,19 +10,19 @@ class VisibilityScopeTests(TestCase):
def setUp(self):
self.client = APIClient()
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(
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(
user_name="buh2", password="x", status=User.STATUS_BUH
user_login="buh2", password="x", status=User.STATUS_BUH
)
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(
user_name="gm2",
user_login="gm2",
password="x",
status=User.STATUS_GM,
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.farms.models import Cycle, Flock, Kandang, Site
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):
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(
user_name="admin",
user_login="admin",
password="secret",
status=User.STATUS_DIRECTOR_ADMIN,
)
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)
def test_create_flock_under_kandang_requires_director_admin(self):
@@ -73,12 +87,9 @@ class FarmAPITests(TestCase):
doc_in_count=9000,
status=Cycle.STATUS_ACTIVE,
)
new_end = start + timedelta(days=39)
response = self.client.patch(
f"/api/v1/cycles/{cycle.pk}/",
{
"end_date": new_end.isoformat(),
"total_days": 40,
"doc_in_count": 9500,
"chick_in_weight": 42,
},
@@ -86,8 +97,8 @@ class FarmAPITests(TestCase):
)
self.assertEqual(response.status_code, 200, response.data)
cycle.refresh_from_db()
self.assertEqual(cycle.end_date, new_end)
self.assertEqual(cycle.total_days, 40)
# end_date is not writable via PATCH (close flow uses approve-close).
self.assertEqual(cycle.end_date, start + timedelta(days=34))
self.assertEqual(cycle.doc_in_count, 9500)
self.assertEqual(cycle.doc_in_weight, 42)
@@ -113,8 +124,8 @@ class FarmAPITests(TestCase):
self.assertEqual(cycle.status, Cycle.STATUS_ACTIVE)
def test_gm_can_approve_cycle_close(self):
gm = User.objects.create_user(user_name="gm_approver", password="x", status=User.STATUS_GM)
site = Site.objects.create(site_name="GM Site", user=gm)
gm = User.objects.create_user(user_login="gm_approver", password="x", status=User.STATUS_GM)
site = _gm_visible_site(owner=gm, site_name="GM Site", code="gm-approve")
kandang = Kandang.objects.create(kandang_name="Kandang GM", site=site)
self.client.force_authenticate(gm)
start = date(2026, 8, 1)
@@ -143,8 +154,8 @@ class FarmAPITests(TestCase):
self.assertEqual(cycle.end_date, proposed)
def test_gm_can_reject_cycle_close(self):
gm = User.objects.create_user(user_name="gm_rejector", password="x", status=User.STATUS_GM)
site = Site.objects.create(site_name="GM Reject Site", user=gm)
gm = User.objects.create_user(user_login="gm_rejector", password="x", status=User.STATUS_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)
self.client.force_authenticate(gm)
start = date(2026, 8, 1)
@@ -170,9 +181,11 @@ class FarmAPITests(TestCase):
def test_gm_admin_cannot_approve_cycle_close(self):
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)
self.client.force_authenticate(gm_admin)
start = date(2026, 8, 1)
@@ -7,7 +7,7 @@ from apps.accounts.models import ApiKey, User
def ensure_bootstrap_user(
*,
user_name: str,
user_login: str,
password: str,
status: str,
is_superuser: bool,
@@ -15,8 +15,9 @@ def ensure_bootstrap_user(
stdout,
) -> User:
user, created = User.objects.get_or_create(
user_name=user_name,
user_login=user_login,
defaults={
"user_name": user_login,
"status": status,
"is_superuser": is_superuser,
},
@@ -24,7 +25,7 @@ def ensure_bootstrap_user(
if not created and user.status != status:
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
@@ -34,12 +35,12 @@ def ensure_bootstrap_user(
user.is_superuser = is_superuser
user.save()
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:
user.status = status
user.is_superuser = is_superuser
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
@@ -70,7 +71,7 @@ class Command(BaseCommand):
)
admin = ensure_bootstrap_user(
user_name=admin_user_name,
user_login=admin_user_name,
password=admin_password,
status=User.STATUS_SUPERUSER,
is_superuser=True,
@@ -90,7 +91,7 @@ class Command(BaseCommand):
"BOOTSTRAP_STAFF_PASSWORD is required when BOOTSTRAP_STAFF_USER is set."
)
ensure_bootstrap_user(
user_name=staff_user_name,
user_login=staff_user_name,
password=staff_password,
status=User.STATUS_GM,
is_superuser=False,
@@ -75,8 +75,11 @@ class Command(BaseCommand):
defaults["managed_by"] = managed_by
user, created = User.objects.get_or_create(
user_name=spec["user_name"],
defaults=defaults,
user_login=spec["user_name"],
defaults={
**defaults,
"user_name": spec["user_name"],
},
)
user.status = spec["status"]
user.is_superuser = spec.get("is_superuser", False)
@@ -98,7 +101,10 @@ class Command(BaseCommand):
return user
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:
return
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):
def test_creates_break_glass_superuser_without_demo_data(self):
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.assertTrue(user.is_superuser)
self.assertTrue(user.check_password("Pr04dm1n"))
@@ -33,7 +34,8 @@ class BootstrapAdminTests(TestCase):
)
def test_creates_gm_when_configured(self):
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.assertFalse(staff.is_superuser)
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)
class OperationsAPITests(TestCase):
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.force_authenticate(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")
class DashboardVisibilityTests(TestCase):
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.force_authenticate(self.user)
site = Site.objects.create(site_name="Sukawarna", user=self.user)
@@ -62,7 +62,7 @@ class AggregationTests(TestCase):
)
class SyncChickenCountingTests(TestCase):
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)
self.kandang = Kandang.objects.create(kandang_name="Kandang 1", site=site)
self.cycle = Cycle.objects.create(
@@ -130,7 +130,7 @@ class SyncChickenCountingTests(TestCase):
)
class ChickenCountingSyncApiTests(TestCase):
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.force_authenticate(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")
class RequestKarungTests(TestCase):
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)
self.kandang1 = Kandang.objects.create(kandang_name="Kandang 1", site=site)
self.kandang2 = Kandang.objects.create(kandang_name="Kandang 2", site=site)