change user_name still used as login and add display_name for dashboard view instead of username on login
This commit is contained in:
1 parent
7a562c18ec
commit
11b0ca26a5
33 files changed
+180
-145
No files matched your search
@@ -0,0 +1,34 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("accounts", "0012_user_login"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="user",
|
||||
old_name="user_name",
|
||||
new_name="display_name",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="user",
|
||||
old_name="user_login",
|
||||
new_name="user_name",
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="user",
|
||||
options={"ordering": ["display_name"]},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="display_name",
|
||||
field=models.CharField(max_length=30, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="user_name",
|
||||
field=models.CharField(max_length=30, unique=True),
|
||||
),
|
||||
]
|
||||
@@ -9,22 +9,22 @@ from django.utils import timezone
|
||||
|
||||
|
||||
class UserManager(BaseUserManager):
|
||||
def create_user(self, user_login: str, password: str | None = None, **extra_fields):
|
||||
if not user_login:
|
||||
raise ValueError("user_login is required")
|
||||
def create_user(self, user_name: str, password: str | None = None, **extra_fields):
|
||||
if not user_name:
|
||||
raise ValueError("user_name is required")
|
||||
extra_fields.setdefault("status", User.STATUS_GM)
|
||||
extra_fields.setdefault("is_superuser", False)
|
||||
extra_fields.setdefault("user_name", user_login)
|
||||
user = self.model(user_login=user_login, **extra_fields)
|
||||
extra_fields.setdefault("display_name", user_name)
|
||||
user = self.model(user_name=user_name, **extra_fields)
|
||||
user.set_password(password)
|
||||
user.save(using=self._db)
|
||||
return user
|
||||
|
||||
def create_superuser(self, user_login: str, password: str | None = None, **extra_fields):
|
||||
def create_superuser(self, user_name: str, password: str | None = None, **extra_fields):
|
||||
"""Break-glass: status=superuser (ops + user management). Hidden from user lists."""
|
||||
extra_fields["status"] = User.STATUS_SUPERUSER
|
||||
extra_fields["is_superuser"] = True
|
||||
return self.create_user(user_login, password, **extra_fields)
|
||||
return self.create_user(user_name, password, **extra_fields)
|
||||
|
||||
|
||||
class User(AbstractBaseUser, PermissionsMixin):
|
||||
@@ -76,8 +76,8 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||
)
|
||||
|
||||
user_id = models.BigAutoField(primary_key=True)
|
||||
user_login = models.CharField(max_length=30, unique=True)
|
||||
user_name = models.CharField(max_length=30, unique=True)
|
||||
display_name = models.CharField(max_length=30, unique=True)
|
||||
status = models.CharField(max_length=30, choices=STATUS_CHOICES, default=STATUS_GM)
|
||||
# BUH who manages this GM / GM Admin (unused for director / buh themselves).
|
||||
managed_by = models.ForeignKey(
|
||||
@@ -104,15 +104,15 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||
|
||||
objects = UserManager()
|
||||
|
||||
USERNAME_FIELD = "user_login"
|
||||
REQUIRED_FIELDS: list[str] = ["user_name"]
|
||||
USERNAME_FIELD = "user_name"
|
||||
REQUIRED_FIELDS: list[str] = ["display_name"]
|
||||
|
||||
class Meta:
|
||||
db_table = "user_access"
|
||||
ordering = ["user_name"]
|
||||
ordering = ["display_name"]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.user_name
|
||||
return self.display_name
|
||||
|
||||
@property
|
||||
def is_executive(self) -> bool:
|
||||
|
||||
@@ -19,19 +19,19 @@ class UserSerializer(PkAsIdMixin, serializers.ModelSerializer):
|
||||
required=False,
|
||||
)
|
||||
managed_by_name = serializers.CharField(
|
||||
source="managed_by.user_name", read_only=True, allow_null=True
|
||||
source="managed_by.display_name", read_only=True, allow_null=True
|
||||
)
|
||||
managed_by_pending = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
managed_by_pending_name = serializers.CharField(
|
||||
source="managed_by_pending.user_name", read_only=True, allow_null=True
|
||||
source="managed_by_pending.display_name", read_only=True, allow_null=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
"id",
|
||||
"user_login",
|
||||
"user_name",
|
||||
"display_name",
|
||||
"status",
|
||||
"is_superuser",
|
||||
"managed_by",
|
||||
@@ -64,19 +64,19 @@ class UserWriteSerializer(PkAsIdMixin, serializers.ModelSerializer):
|
||||
required=False,
|
||||
)
|
||||
managed_by_name = serializers.CharField(
|
||||
source="managed_by.user_name", read_only=True, allow_null=True
|
||||
source="managed_by.display_name", read_only=True, allow_null=True
|
||||
)
|
||||
managed_by_pending = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
managed_by_pending_name = serializers.CharField(
|
||||
source="managed_by_pending.user_name", read_only=True, allow_null=True
|
||||
source="managed_by_pending.display_name", read_only=True, allow_null=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
"id",
|
||||
"user_login",
|
||||
"user_name",
|
||||
"display_name",
|
||||
"status",
|
||||
"is_superuser",
|
||||
"password",
|
||||
@@ -136,9 +136,9 @@ class UserWriteSerializer(PkAsIdMixin, serializers.ModelSerializer):
|
||||
|
||||
def create(self, validated_data):
|
||||
password = validated_data.pop("password")
|
||||
user_login = validated_data.pop("user_login")
|
||||
user_name = validated_data.pop("user_name")
|
||||
return User.objects.create_user(
|
||||
user_login=user_login, password=password, **validated_data
|
||||
user_name=user_name, password=password, **validated_data
|
||||
)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
@@ -161,7 +161,7 @@ class SetPasswordSerializer(serializers.Serializer):
|
||||
|
||||
|
||||
class LoginSerializer(serializers.Serializer):
|
||||
user_login = serializers.CharField(max_length=30)
|
||||
user_name = serializers.CharField(max_length=30)
|
||||
password = serializers.CharField(write_only=True)
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from apps.accounts.models import User
|
||||
class AccountAdminModelTests(TestCase):
|
||||
def test_account_admin_flags_and_executive(self):
|
||||
user = User.objects.create_user(
|
||||
user_login="acct1",
|
||||
user_name="acct1",
|
||||
password="secret",
|
||||
status=User.STATUS_ACCOUNT_ADMIN,
|
||||
)
|
||||
@@ -18,7 +18,7 @@ class AccountAdminModelTests(TestCase):
|
||||
|
||||
def test_inactive_clears_flags(self):
|
||||
user = User.objects.create_user(
|
||||
user_login="acct2",
|
||||
user_name="acct2",
|
||||
password="secret",
|
||||
status=User.STATUS_ACCOUNT_ADMIN,
|
||||
)
|
||||
@@ -30,7 +30,7 @@ class AccountAdminModelTests(TestCase):
|
||||
|
||||
def test_director_admin_is_not_superuser(self):
|
||||
user = User.objects.create_user(
|
||||
user_login="diradmin1",
|
||||
user_name="diradmin1",
|
||||
password="secret",
|
||||
status=User.STATUS_DIRECTOR_ADMIN,
|
||||
)
|
||||
@@ -42,21 +42,21 @@ class AccountAdminApiTests(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.account_admin = User.objects.create_user(
|
||||
user_login="acctadmin",
|
||||
user_name="acctadmin",
|
||||
password="acct123",
|
||||
status=User.STATUS_ACCOUNT_ADMIN,
|
||||
)
|
||||
self.gm = User.objects.create_user(
|
||||
user_login="gm1", password="gm123", status=User.STATUS_GM
|
||||
user_name="gm1", password="gm123", status=User.STATUS_GM
|
||||
)
|
||||
self.buh = User.objects.create_user(
|
||||
user_login="buh1", password="buh123", status=User.STATUS_BUH
|
||||
user_name="buh1", password="buh123", status=User.STATUS_BUH
|
||||
)
|
||||
self.director = User.objects.create_user(
|
||||
user_login="dir1", password="dir123", status=User.STATUS_DIRECTOR
|
||||
user_name="dir1", password="dir123", status=User.STATUS_DIRECTOR
|
||||
)
|
||||
self.superuser = User.objects.create_superuser(
|
||||
user_login="admin1",
|
||||
user_name="admin1",
|
||||
password="admin123",
|
||||
)
|
||||
self.assertTrue(self.superuser.is_superuser)
|
||||
@@ -67,15 +67,15 @@ class AccountAdminApiTests(TestCase):
|
||||
response = self.client.post(
|
||||
"/api/v1/users/",
|
||||
{
|
||||
"user_login": "newgm",
|
||||
"user_name": "newgm",
|
||||
"display_name": "newgm",
|
||||
"status": User.STATUS_GM,
|
||||
"password": "temp-pass",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, 201, response.data)
|
||||
created = User.objects.get(user_name="newgm")
|
||||
created = User.objects.get(display_name="newgm")
|
||||
self.assertEqual(created.status, User.STATUS_GM)
|
||||
self.assertIsNotNone(
|
||||
authenticate(username="newgm", password="temp-pass")
|
||||
@@ -85,7 +85,7 @@ class AccountAdminApiTests(TestCase):
|
||||
self.client.force_authenticate(self.account_admin)
|
||||
response = self.client.post(
|
||||
"/api/v1/users/",
|
||||
{"user_login": "nopass", "user_name": "nopass", "status": User.STATUS_GM},
|
||||
{"user_name": "nopass", "display_name": "nopass", "status": User.STATUS_GM},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
@@ -96,8 +96,8 @@ class AccountAdminApiTests(TestCase):
|
||||
response = self.client.post(
|
||||
"/api/v1/users/",
|
||||
{
|
||||
"user_login": "blocked",
|
||||
"user_name": "blocked",
|
||||
"display_name": "blocked",
|
||||
"status": User.STATUS_GM,
|
||||
"password": "x",
|
||||
},
|
||||
@@ -109,7 +109,7 @@ class AccountAdminApiTests(TestCase):
|
||||
self.client.force_authenticate(self.account_admin)
|
||||
response = self.client.get("/api/v1/users/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
names = {row["user_name"] for row in response.data["results"]}
|
||||
names = {row["display_name"] for row in response.data["results"]}
|
||||
self.assertIn("gm1", names)
|
||||
self.assertIn("buh1", names)
|
||||
self.assertIn("dir1", names)
|
||||
@@ -148,15 +148,15 @@ class AccountAdminApiTests(TestCase):
|
||||
{"password": "hack"},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, 403, actor.user_name)
|
||||
self.assertEqual(response.status_code, 403, actor.display_name)
|
||||
|
||||
def test_superuser_break_glass_create(self):
|
||||
self.client.force_authenticate(self.superuser)
|
||||
response = self.client.post(
|
||||
"/api/v1/users/",
|
||||
{
|
||||
"user_login": "breakglass",
|
||||
"user_name": "breakglass",
|
||||
"display_name": "breakglass",
|
||||
"status": User.STATUS_BUH,
|
||||
"password": "bg-pass",
|
||||
},
|
||||
@@ -191,7 +191,7 @@ class AccountAdminApiTests(TestCase):
|
||||
|
||||
def test_peer_account_admin_can_deactivate_other_account_admin(self):
|
||||
peer = User.objects.create_user(
|
||||
user_login="acctadmin2",
|
||||
user_name="acctadmin2",
|
||||
password="acct123",
|
||||
status=User.STATUS_ACCOUNT_ADMIN,
|
||||
)
|
||||
@@ -207,11 +207,11 @@ class AccountAdminApiTests(TestCase):
|
||||
self.assertFalse(peer.is_active)
|
||||
|
||||
def test_superuser_hidden_from_user_list(self):
|
||||
User.objects.create_superuser(user_login="hidden_su", password="x")
|
||||
User.objects.create_superuser(user_name="hidden_su", password="x")
|
||||
self.client.force_authenticate(self.account_admin)
|
||||
response = self.client.get("/api/v1/users/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
names = {row["user_name"] for row in response.data["results"]}
|
||||
names = {row["display_name"] for row in response.data["results"]}
|
||||
self.assertNotIn("hidden_su", names)
|
||||
self.assertNotIn("admin1", names)
|
||||
|
||||
@@ -220,8 +220,8 @@ class AccountAdminApiTests(TestCase):
|
||||
response = self.client.post(
|
||||
"/api/v1/users/",
|
||||
{
|
||||
"user_login": "bad_su",
|
||||
"user_name": "bad_su",
|
||||
"display_name": "bad_su",
|
||||
"status": User.STATUS_SUPERUSER,
|
||||
"password": "x",
|
||||
},
|
||||
|
||||
@@ -8,13 +8,13 @@ class ManagedByRequestTests(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.buh = User.objects.create_user(
|
||||
user_login="buh1", password="x", status=User.STATUS_BUH
|
||||
user_name="buh1", password="x", status=User.STATUS_BUH
|
||||
)
|
||||
self.buh_other = User.objects.create_user(
|
||||
user_login="buh2", password="x", status=User.STATUS_BUH
|
||||
user_name="buh2", password="x", status=User.STATUS_BUH
|
||||
)
|
||||
self.gm = User.objects.create_user(
|
||||
user_login="gm1", password="x", status=User.STATUS_GM
|
||||
user_name="gm1", password="x", status=User.STATUS_GM
|
||||
)
|
||||
|
||||
def test_gm_requests_and_buh_approves(self):
|
||||
@@ -30,7 +30,7 @@ class ManagedByRequestTests(TestCase):
|
||||
self.client.force_authenticate(self.buh)
|
||||
pending = self.client.get("/api/v1/users/pending-managed/")
|
||||
self.assertEqual(pending.status_code, 200)
|
||||
self.assertEqual({row["user_name"] for row in pending.data}, {"gm1"})
|
||||
self.assertEqual({row["display_name"] for row in pending.data}, {"gm1"})
|
||||
|
||||
approve = self.client.post(f"/api/v1/users/{self.gm.pk}/approve-managed-by/", {})
|
||||
self.assertEqual(approve.status_code, 200)
|
||||
@@ -39,7 +39,7 @@ class ManagedByRequestTests(TestCase):
|
||||
self.assertIsNone(self.gm.managed_by_pending_id)
|
||||
|
||||
managed = self.client.get("/api/v1/users/")
|
||||
self.assertEqual({row["user_name"] for row in managed.data["results"]}, {"gm1"})
|
||||
self.assertEqual({row["display_name"] for row in managed.data["results"]}, {"gm1"})
|
||||
|
||||
def test_other_buh_cannot_approve(self):
|
||||
self.gm.request_managed_by(self.buh)
|
||||
@@ -60,17 +60,17 @@ class ManagedByRequestTests(TestCase):
|
||||
|
||||
def test_gm_lists_buhs_only(self):
|
||||
User.objects.create_user(
|
||||
user_login="buadmin1", password="x", status=User.STATUS_BU_ADMIN
|
||||
user_name="buadmin1", password="x", status=User.STATUS_BU_ADMIN
|
||||
)
|
||||
self.client.force_authenticate(self.gm)
|
||||
response = self.client.get("/api/v1/users/buhs/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
names = {row["user_name"] for row in response.data}
|
||||
names = {row["display_name"] for row in response.data}
|
||||
self.assertEqual(names, {"buh1", "buh2"})
|
||||
|
||||
def test_gm_admin_cannot_request_buh(self):
|
||||
gm_admin = User.objects.create_user(
|
||||
user_login="gma1", password="x", status=User.STATUS_GM_ADMIN
|
||||
user_name="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_login="buadmin1", password="x", status=User.STATUS_BU_ADMIN
|
||||
user_name="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_login="buadmin1", password="x", status=User.STATUS_BU_ADMIN
|
||||
user_name="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"])
|
||||
|
||||
@@ -10,19 +10,19 @@ class VisibilityScopeTests(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.director = User.objects.create_user(
|
||||
user_login="dir1", password="x", status=User.STATUS_DIRECTOR
|
||||
user_name="dir1", password="x", status=User.STATUS_DIRECTOR
|
||||
)
|
||||
self.buh = User.objects.create_user(
|
||||
user_login="buh1", password="x", status=User.STATUS_BUH
|
||||
user_name="buh1", password="x", status=User.STATUS_BUH
|
||||
)
|
||||
self.buh_other = User.objects.create_user(
|
||||
user_login="buh2", password="x", status=User.STATUS_BUH
|
||||
user_name="buh2", password="x", status=User.STATUS_BUH
|
||||
)
|
||||
self.gm = User.objects.create_user(
|
||||
user_login="gm1", password="x", status=User.STATUS_GM, managed_by=self.buh
|
||||
user_name="gm1", password="x", status=User.STATUS_GM, managed_by=self.buh
|
||||
)
|
||||
self.gm_other = User.objects.create_user(
|
||||
user_login="gm2",
|
||||
user_name="gm2",
|
||||
password="x",
|
||||
status=User.STATUS_GM,
|
||||
managed_by=self.buh_other,
|
||||
@@ -67,7 +67,7 @@ class VisibilityScopeTests(TestCase):
|
||||
self.client.force_authenticate(self.buh)
|
||||
response = self.client.get("/api/v1/users/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
names = {row["user_name"] for row in response.data["results"]}
|
||||
names = {row["display_name"] for row in response.data["results"]}
|
||||
self.assertEqual(names, {"gm1"})
|
||||
|
||||
def test_gm_sees_own_city_sites_only(self):
|
||||
|
||||
@@ -5,11 +5,11 @@ from apps.accounts.models import User
|
||||
|
||||
|
||||
class UserLoginFieldTests(TestCase):
|
||||
def test_authenticate_uses_user_login_not_user_name(self):
|
||||
def test_authenticate_uses_user_name_not_display_name(self):
|
||||
User.objects.create_user(
|
||||
user_login="gm.login",
|
||||
user_name="gm.login",
|
||||
password="gm123",
|
||||
user_name="gm",
|
||||
display_name="gm",
|
||||
status=User.STATUS_GM,
|
||||
)
|
||||
self.assertIsNotNone(
|
||||
@@ -17,18 +17,18 @@ class UserLoginFieldTests(TestCase):
|
||||
)
|
||||
self.assertIsNone(authenticate(username="gm", password="gm123"))
|
||||
|
||||
def test_create_user_defaults_user_name_to_login(self):
|
||||
user = User.objects.create_user(user_login="solo", password="x")
|
||||
self.assertEqual(user.user_login, "solo")
|
||||
def test_create_user_defaults_display_name_to_user_name(self):
|
||||
user = User.objects.create_user(user_name="solo", password="x")
|
||||
self.assertEqual(user.user_name, "solo")
|
||||
self.assertEqual(user.display_name, "solo")
|
||||
|
||||
|
||||
class LoginApiUserLoginTests(TestCase):
|
||||
def test_login_accepts_user_login(self):
|
||||
def test_login_accepts_user_name(self):
|
||||
User.objects.create_user(
|
||||
user_login="gm.login",
|
||||
user_name="gm.login",
|
||||
password="gm123",
|
||||
user_name="gm",
|
||||
display_name="gm",
|
||||
status=User.STATUS_GM,
|
||||
)
|
||||
client = Client()
|
||||
@@ -36,10 +36,10 @@ class LoginApiUserLoginTests(TestCase):
|
||||
token = client.cookies["csrftoken"].value
|
||||
response = client.post(
|
||||
"/api/v1/auth/login/",
|
||||
{"user_login": "gm.login", "password": "gm123"},
|
||||
{"user_name": "gm.login", "password": "gm123"},
|
||||
content_type="application/json",
|
||||
HTTP_X_CSRFTOKEN=token,
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json()["user_login"], "gm.login")
|
||||
self.assertEqual(response.json()["user_name"], "gm")
|
||||
self.assertEqual(response.json()["user_name"], "gm.login")
|
||||
self.assertEqual(response.json()["display_name"], "gm")
|
||||
@@ -57,7 +57,7 @@ class UserViewSet(viewsets.ModelViewSet):
|
||||
|
||||
def get_queryset(self):
|
||||
qs = User.objects.select_related("managed_by", "managed_by_pending").order_by(
|
||||
"user_name"
|
||||
"display_name"
|
||||
)
|
||||
return filter_users_queryset(qs, self.request.user)
|
||||
|
||||
@@ -103,7 +103,7 @@ class UserViewSet(viewsets.ModelViewSet):
|
||||
rows = User.objects.filter(
|
||||
status=User.STATUS_BUH,
|
||||
is_active=True,
|
||||
).order_by("user_name")
|
||||
).order_by("display_name")
|
||||
return Response(UserSerializer(rows, many=True).data)
|
||||
|
||||
@action(detail=False, methods=["get"], url_path="pending-managed")
|
||||
@@ -114,7 +114,7 @@ class UserViewSet(viewsets.ModelViewSet):
|
||||
rows = User.objects.filter(
|
||||
managed_by_pending=request.user,
|
||||
status=User.STATUS_GM,
|
||||
).select_related("managed_by", "managed_by_pending").order_by("user_name")
|
||||
).select_related("managed_by", "managed_by_pending").order_by("display_name")
|
||||
return Response(UserSerializer(rows, many=True).data)
|
||||
|
||||
@action(detail=False, methods=["post"], url_path="request-managed-by")
|
||||
@@ -235,7 +235,7 @@ class LoginView(APIView):
|
||||
serializer.is_valid(raise_exception=True)
|
||||
user = authenticate(
|
||||
request,
|
||||
username=serializer.validated_data["user_login"],
|
||||
username=serializer.validated_data["user_name"],
|
||||
password=serializer.validated_data["password"],
|
||||
)
|
||||
if user is None:
|
||||
|
||||
@@ -5,7 +5,7 @@ from apps.farms.models import Cycle, Flock, Kandang, Site
|
||||
|
||||
|
||||
class SiteSerializer(PkAsIdMixin, serializers.ModelSerializer):
|
||||
owner_name = serializers.CharField(source="user.user_name", read_only=True)
|
||||
owner_name = serializers.CharField(source="user.display_name", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Site
|
||||
|
||||
@@ -23,9 +23,9 @@ def _gm_visible_site(*, owner: User, site_name: str, code: str) -> Site:
|
||||
|
||||
class FarmAPITests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(user_login="tester", password="secret")
|
||||
self.user = User.objects.create_user(user_name="tester", password="secret")
|
||||
self.admin = User.objects.create_user(
|
||||
user_login="admin",
|
||||
user_name="admin",
|
||||
password="secret",
|
||||
status=User.STATUS_DIRECTOR_ADMIN,
|
||||
)
|
||||
@@ -124,7 +124,7 @@ class FarmAPITests(TestCase):
|
||||
self.assertEqual(cycle.status, Cycle.STATUS_ACTIVE)
|
||||
|
||||
def test_gm_can_approve_cycle_close(self):
|
||||
gm = User.objects.create_user(user_login="gm_approver", password="x", status=User.STATUS_GM)
|
||||
gm = User.objects.create_user(user_name="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)
|
||||
@@ -154,7 +154,7 @@ class FarmAPITests(TestCase):
|
||||
self.assertEqual(cycle.end_date, proposed)
|
||||
|
||||
def test_gm_can_reject_cycle_close(self):
|
||||
gm = User.objects.create_user(user_login="gm_rejector", password="x", status=User.STATUS_GM)
|
||||
gm = User.objects.create_user(user_name="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)
|
||||
@@ -181,7 +181,7 @@ class FarmAPITests(TestCase):
|
||||
|
||||
def test_gm_admin_cannot_approve_cycle_close(self):
|
||||
gm_admin = User.objects.create_user(
|
||||
user_login="gm_admin_user", password="x", status=User.STATUS_GM_ADMIN
|
||||
user_name="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"
|
||||
|
||||
@@ -6,14 +6,14 @@ from apps.farms.models import Kandang, Site
|
||||
|
||||
class KandangFeedInButtonUrlTests(TestCase):
|
||||
def setUp(self):
|
||||
self.owner = User.objects.create_user(user_login="owner", password="secret")
|
||||
self.owner = User.objects.create_user(user_name="owner", password="secret")
|
||||
self.admin = User.objects.create_user(
|
||||
user_login="admin",
|
||||
user_name="admin",
|
||||
password="admin123",
|
||||
status=User.STATUS_DIRECTOR_ADMIN,
|
||||
)
|
||||
self.staff = User.objects.create_user(
|
||||
user_login="staff",
|
||||
user_name="staff",
|
||||
password="staff123",
|
||||
status=User.STATUS_GM,
|
||||
)
|
||||
@@ -27,11 +27,11 @@ class KandangFeedInButtonUrlTests(TestCase):
|
||||
token = client.cookies["csrftoken"].value
|
||||
return {"HTTP_X_CSRFTOKEN": token}
|
||||
|
||||
def _login(self, client: Client, user_login: str, password: str) -> None:
|
||||
def _login(self, client: Client, user_name: str, password: str) -> None:
|
||||
client.get("/api/v1/auth/csrf/")
|
||||
response = client.post(
|
||||
"/api/v1/auth/login/",
|
||||
{"user_login": user_login, "password": password},
|
||||
{"user_name": user_name, "password": password},
|
||||
content_type="application/json",
|
||||
**self._csrf_headers(client),
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ from apps.accounts.models import ApiKey, User
|
||||
|
||||
def ensure_bootstrap_user(
|
||||
*,
|
||||
user_login: str,
|
||||
user_name: str,
|
||||
password: str,
|
||||
status: str,
|
||||
is_superuser: bool,
|
||||
@@ -15,9 +15,9 @@ def ensure_bootstrap_user(
|
||||
stdout,
|
||||
) -> User:
|
||||
user, created = User.objects.get_or_create(
|
||||
user_login=user_login,
|
||||
user_name=user_name,
|
||||
defaults={
|
||||
"user_name": user_login,
|
||||
"display_name": user_name,
|
||||
"status": status,
|
||||
"is_superuser": is_superuser,
|
||||
},
|
||||
@@ -25,7 +25,7 @@ def ensure_bootstrap_user(
|
||||
|
||||
if not created and user.status != status:
|
||||
stdout.write(
|
||||
f"Updating {user_login!r} status {user.status!r} → {status!r}"
|
||||
f"Updating {user_name!r} status {user.status!r} → {status!r}"
|
||||
)
|
||||
|
||||
should_set_password = created or not user.has_usable_password() or force_password
|
||||
@@ -35,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_login}")
|
||||
stdout.write(f"{action} {status} user {user_name}")
|
||||
else:
|
||||
user.status = status
|
||||
user.is_superuser = is_superuser
|
||||
user.save(update_fields=["status", "is_superuser", "updated_at"])
|
||||
stdout.write(f"User {user_login} already exists (password unchanged)")
|
||||
stdout.write(f"User {user_name} already exists (password unchanged)")
|
||||
|
||||
return user
|
||||
|
||||
@@ -71,7 +71,7 @@ class Command(BaseCommand):
|
||||
)
|
||||
|
||||
admin = ensure_bootstrap_user(
|
||||
user_login=admin_user_name,
|
||||
user_name=admin_user_name,
|
||||
password=admin_password,
|
||||
status=User.STATUS_SUPERUSER,
|
||||
is_superuser=True,
|
||||
@@ -91,7 +91,7 @@ class Command(BaseCommand):
|
||||
"BOOTSTRAP_STAFF_PASSWORD is required when BOOTSTRAP_STAFF_USER is set."
|
||||
)
|
||||
ensure_bootstrap_user(
|
||||
user_login=staff_user_name,
|
||||
user_name=staff_user_name,
|
||||
password=staff_password,
|
||||
status=User.STATUS_GM,
|
||||
is_superuser=False,
|
||||
|
||||
@@ -75,10 +75,10 @@ class Command(BaseCommand):
|
||||
defaults["managed_by"] = managed_by
|
||||
|
||||
user, created = User.objects.get_or_create(
|
||||
user_login=spec["user_name"],
|
||||
user_name=spec["user_name"],
|
||||
defaults={
|
||||
**defaults,
|
||||
"user_name": spec["user_name"],
|
||||
"display_name": spec["user_name"],
|
||||
},
|
||||
)
|
||||
user.status = spec["status"]
|
||||
@@ -93,7 +93,7 @@ class Command(BaseCommand):
|
||||
role = spec["status"]
|
||||
if user.is_superuser:
|
||||
role = f"{role}+superuser"
|
||||
link = f", managed_by={managed_by.user_name}" if managed_by else ""
|
||||
link = f", managed_by={managed_by.display_name}" if managed_by else ""
|
||||
action = "Created" if created else "Updated"
|
||||
self.stdout.write(
|
||||
f"{action} user {spec['user_name']} / {spec['password']} ({role}{link})"
|
||||
@@ -102,8 +102,8 @@ class Command(BaseCommand):
|
||||
|
||||
def _remove_legacy_staff(self, gm: User) -> None:
|
||||
staff = (
|
||||
User.objects.filter(user_login="staff").first()
|
||||
or User.objects.filter(user_name="staff").first()
|
||||
User.objects.filter(user_name="staff").first()
|
||||
or User.objects.filter(display_name="staff").first()
|
||||
)
|
||||
if not staff:
|
||||
return
|
||||
|
||||
@@ -186,7 +186,7 @@ class Command(BaseCommand):
|
||||
if site.user_id != user.pk:
|
||||
site.user = user
|
||||
site.save(update_fields=["user", "updated_at"])
|
||||
self.stdout.write(f"Sukawarna site owner: {user.user_name} ({user.status})")
|
||||
self.stdout.write(f"Sukawarna site owner: {user.display_name} ({user.status})")
|
||||
|
||||
kandang, _ = Kandang.objects.get_or_create(
|
||||
kandang_name="Kandang 1",
|
||||
|
||||
@@ -73,7 +73,7 @@ class Command(BaseCommand):
|
||||
if site.user_id != user.pk:
|
||||
site.user = user
|
||||
site.save(update_fields=["user", "updated_at"])
|
||||
self.stdout.write(f"Sukawarna site owner: {user.user_name} ({user.status})")
|
||||
self.stdout.write(f"Sukawarna site owner: {user.display_name} ({user.status})")
|
||||
|
||||
kandang, _ = Kandang.objects.get_or_create(
|
||||
kandang_name="Kandang 2",
|
||||
|
||||
@@ -15,8 +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_login="admin")
|
||||
self.assertEqual(user.user_name, "admin")
|
||||
user = User.objects.get(user_name="admin")
|
||||
self.assertEqual(user.display_name, "admin")
|
||||
self.assertEqual(user.status, User.STATUS_SUPERUSER)
|
||||
self.assertTrue(user.is_superuser)
|
||||
self.assertTrue(user.check_password("Pr04dm1n"))
|
||||
@@ -34,8 +34,8 @@ class BootstrapAdminTests(TestCase):
|
||||
)
|
||||
def test_creates_gm_when_configured(self):
|
||||
call_command("bootstrap_admin")
|
||||
staff = User.objects.get(user_login="staff")
|
||||
self.assertEqual(staff.user_name, "staff")
|
||||
staff = User.objects.get(user_name="staff")
|
||||
self.assertEqual(staff.display_name, "staff")
|
||||
self.assertEqual(staff.status, User.STATUS_GM)
|
||||
self.assertFalse(staff.is_superuser)
|
||||
self.assertTrue(staff.check_password("St4ff!"))
|
||||
|
||||
@@ -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_login="tester", password="secret")
|
||||
self.user = User.objects.create_user(user_name="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_login="tester", password="secret")
|
||||
self.user = User.objects.create_user(user_name="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_login="sync-user", password="secret")
|
||||
user = User.objects.create_user(user_name="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_login="tester", password="secret")
|
||||
self.user = User.objects.create_user(user_name="tester", password="secret")
|
||||
self.client = APIClient()
|
||||
self.client.force_authenticate(self.user)
|
||||
site = Site.objects.create(site_name="Sukawarna", user=self.user)
|
||||
|
||||
@@ -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_login="tester", password="secret")
|
||||
user = User.objects.create_user(user_name="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)
|
||||
|
||||
@@ -8,7 +8,7 @@ class CitySiteSerializer(serializers.ModelSerializer):
|
||||
# Never echo the outbound city API key back to the browser by default.
|
||||
has_api_key = serializers.SerializerMethodField()
|
||||
registered_by_name = serializers.CharField(
|
||||
source="registered_by.user_name", read_only=True, allow_null=True
|
||||
source="registered_by.display_name", read_only=True, allow_null=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -57,6 +57,7 @@ erDiagram
|
||||
USER_ACCESS {
|
||||
int user_id PK "NOT NULL"
|
||||
varchar user_name "30 NOT NULL"
|
||||
varchar display_name "30 NOT NULL"
|
||||
varchar password_hash "255 NOT NULL"
|
||||
varchar status "30 NOT NULL"
|
||||
timestamp created_at "NOT NULL"
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
CREATE TABLE IF NOT EXISTS user_access (
|
||||
user_id SERIAL PRIMARY KEY,
|
||||
user_name VARCHAR(30) NOT NULL UNIQUE,
|
||||
display_name VARCHAR(30) NOT NULL UNIQUE,
|
||||
password VARCHAR(128) NOT NULL,
|
||||
last_login TIMESTAMP WITH TIME ZONE,
|
||||
is_superuser BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
is_staff BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
@@ -28,7 +28,7 @@ const AccountAdminShell: React.FC = () => {
|
||||
</nav>
|
||||
<div className="border-t border-gray-200 p-4 space-y-2">
|
||||
<p className="text-xs text-gray-500 truncate">
|
||||
{user?.user_name} · {roleLabel}
|
||||
{user?.display_name} · {roleLabel}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -181,10 +181,10 @@ const Header: React.FC<HeaderProps> = ({
|
||||
className="flex items-center space-x-2 focus:outline-none"
|
||||
>
|
||||
<div className="w-10 h-10 rounded-full bg-red-100 text-red-700 flex items-center justify-center font-bold">
|
||||
{user.user_name.slice(0, 1).toUpperCase()}
|
||||
{user.display_name.slice(0, 1).toUpperCase()}
|
||||
</div>
|
||||
<div className="hidden lg:block text-left">
|
||||
<p className="text-sm font-semibold text-gray-800">{user.user_name}</p>
|
||||
<p className="text-sm font-semibold text-gray-800">{user.display_name}</p>
|
||||
<p className="text-xs text-gray-500">{ROLE_LABELS[user.status]}</p>
|
||||
</div>
|
||||
<i className="fa-solid fa-chevron-down text-xs text-gray-500" />
|
||||
@@ -192,7 +192,7 @@ const Header: React.FC<HeaderProps> = ({
|
||||
{open && (
|
||||
<div className="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 z-50">
|
||||
<div className="px-4 py-3 border-b">
|
||||
<p className="text-sm font-semibold text-gray-800">{user.user_name}</p>
|
||||
<p className="text-sm font-semibold text-gray-800">{user.display_name}</p>
|
||||
<p className="text-xs text-gray-500">{ROLE_LABELS[user.status]}</p>
|
||||
</div>
|
||||
<button
|
||||
|
||||
@@ -134,7 +134,7 @@ const BuhViewList: React.FC = () => {
|
||||
className="py-3 flex flex-wrap items-center justify-between gap-3"
|
||||
>
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-gray-800">{gm.user_name}</p>
|
||||
<p className="text-sm font-semibold text-gray-800">{gm.display_name}</p>
|
||||
<p className="text-xs text-gray-500">{ROLE_LABELS[gm.status] ?? gm.status}</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
@@ -179,7 +179,7 @@ const BuhViewList: React.FC = () => {
|
||||
return (
|
||||
<li key={gm.id} className="border border-gray-200 rounded-lg p-4">
|
||||
<div className="flex flex-wrap items-baseline justify-between gap-2 mb-2">
|
||||
<p className="text-sm font-semibold text-gray-800">{gm.user_name}</p>
|
||||
<p className="text-sm font-semibold text-gray-800">{gm.display_name}</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
{ROLE_LABELS[gm.status] ?? gm.status} · {sites.length} site
|
||||
</p>
|
||||
@@ -361,7 +361,7 @@ const GmViewList: React.FC = () => {
|
||||
<option value="">— pilih —</option>
|
||||
{buhs.map((buh) => (
|
||||
<option key={buh.id} value={buh.id}>
|
||||
{buh.user_name}
|
||||
{buh.display_name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
@@ -81,8 +81,8 @@ const UserManagementPage: React.FC = () => {
|
||||
setSaving(true);
|
||||
try {
|
||||
await api.users.create({
|
||||
user_login: userLogin.trim(),
|
||||
user_name: userName.trim(),
|
||||
user_name: userLogin.trim(),
|
||||
display_name: userName.trim(),
|
||||
status,
|
||||
password,
|
||||
});
|
||||
@@ -115,12 +115,12 @@ const UserManagementPage: React.FC = () => {
|
||||
};
|
||||
|
||||
const onDeactivate = (user: User) => {
|
||||
if (!window.confirm(`Nonaktifkan akun "${user.user_name}"?`)) return;
|
||||
if (!window.confirm(`Nonaktifkan akun "${user.display_name}"?`)) return;
|
||||
void wrap(async () => {
|
||||
setBusyId(user.id);
|
||||
try {
|
||||
await api.users.update(user.id, { status: 'inactive' });
|
||||
setNotice(`Akun "${user.user_name}" dinonaktifkan.`);
|
||||
setNotice(`Akun "${user.display_name}" dinonaktifkan.`);
|
||||
await reload();
|
||||
} finally {
|
||||
setBusyId(null);
|
||||
@@ -241,12 +241,12 @@ const UserManagementPage: React.FC = () => {
|
||||
<tr key={user.id} className="border-b border-gray-100 align-top">
|
||||
<td className="py-3 pr-4">
|
||||
<div className="font-medium text-gray-900">
|
||||
{user.user_name}
|
||||
{user.display_name}
|
||||
{isSelf ? (
|
||||
<span className="ml-2 text-xs font-normal text-gray-500">(Anda)</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">{user.user_login}</div>
|
||||
<div className="text-xs text-gray-500">{user.user_name}</div>
|
||||
</td>
|
||||
<td className="py-3 pr-4">
|
||||
{!isSelf && editId === user.id ? (
|
||||
|
||||
@@ -6,8 +6,8 @@ vi.mock('../../context/AuthContext.tsx', () => ({
|
||||
useAuth: () => ({
|
||||
user: {
|
||||
id: 9,
|
||||
user_login: 'account_admin',
|
||||
user_name: 'account_admin',
|
||||
display_name: 'account_admin',
|
||||
status: 'account_admin',
|
||||
is_superuser: false,
|
||||
created_at: '',
|
||||
|
||||
@@ -26,8 +26,8 @@ const {
|
||||
authState: {
|
||||
user: {
|
||||
id: 5,
|
||||
user_login: 'gm.user',
|
||||
user_name: 'gm.user',
|
||||
display_name: 'gm.user',
|
||||
status: 'gm' as string,
|
||||
managed_by: null as number | null,
|
||||
managed_by_name: null as string | null,
|
||||
@@ -85,8 +85,8 @@ describe('SettingsPage city site registry', () => {
|
||||
refreshUserMock.mockReset();
|
||||
authState.user = {
|
||||
id: 5,
|
||||
user_login: 'gm.user',
|
||||
user_name: 'gm.user',
|
||||
display_name: 'gm.user',
|
||||
status: 'gm',
|
||||
managed_by: null,
|
||||
managed_by_name: null,
|
||||
@@ -129,13 +129,13 @@ describe('SettingsPage city site registry', () => {
|
||||
updated_at: '',
|
||||
});
|
||||
buhsMock.mockResolvedValue([
|
||||
{ id: 2, user_login: 'buh', user_name: 'buh', status: 'buh', created_at: '', updated_at: '' },
|
||||
{ id: 2, user_name: 'buh', display_name: 'buh', status: 'buh', created_at: '', updated_at: '' },
|
||||
]);
|
||||
usersListMock.mockResolvedValue([
|
||||
{
|
||||
id: 5,
|
||||
user_login: 'gm',
|
||||
user_name: 'gm',
|
||||
display_name: 'gm',
|
||||
status: 'gm',
|
||||
managed_by: 2,
|
||||
created_at: '',
|
||||
@@ -191,8 +191,8 @@ describe('SettingsPage city site registry', () => {
|
||||
it('shows pending GM confirmation for BUH', async () => {
|
||||
authState.user = {
|
||||
id: 2,
|
||||
user_login: 'buh',
|
||||
user_name: 'buh',
|
||||
display_name: 'buh',
|
||||
status: 'buh',
|
||||
managed_by: null,
|
||||
managed_by_name: null,
|
||||
@@ -204,8 +204,8 @@ describe('SettingsPage city site registry', () => {
|
||||
pendingMock.mockResolvedValue([
|
||||
{
|
||||
id: 5,
|
||||
user_login: 'gm.user',
|
||||
user_name: 'gm.user',
|
||||
display_name: 'gm.user',
|
||||
status: 'gm',
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
|
||||
@@ -10,8 +10,8 @@ const { listMock, createMock, updateMock, setPasswordMock, authState } = vi.hois
|
||||
authState: {
|
||||
user: {
|
||||
id: 99,
|
||||
user_login: 'account_admin',
|
||||
user_name: 'account_admin',
|
||||
display_name: 'account_admin',
|
||||
status: 'account_admin' as string,
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
@@ -45,8 +45,8 @@ describe('UserManagementPage', () => {
|
||||
setPasswordMock.mockReset();
|
||||
authState.user = {
|
||||
id: 99,
|
||||
user_login: 'account_admin',
|
||||
user_name: 'account_admin',
|
||||
display_name: 'account_admin',
|
||||
status: 'account_admin',
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
@@ -54,8 +54,8 @@ describe('UserManagementPage', () => {
|
||||
listMock.mockResolvedValue([
|
||||
{
|
||||
id: 1,
|
||||
user_login: 'gm1.login',
|
||||
user_name: 'gm1',
|
||||
user_name: 'gm1.login',
|
||||
display_name: 'gm1',
|
||||
status: 'gm',
|
||||
managed_by_name: 'buh1',
|
||||
created_at: '',
|
||||
@@ -63,8 +63,8 @@ describe('UserManagementPage', () => {
|
||||
},
|
||||
{
|
||||
id: 99,
|
||||
user_login: 'account_admin',
|
||||
user_name: 'account_admin',
|
||||
display_name: 'account_admin',
|
||||
status: 'account_admin',
|
||||
managed_by_name: null,
|
||||
created_at: '',
|
||||
@@ -73,8 +73,8 @@ describe('UserManagementPage', () => {
|
||||
]);
|
||||
createMock.mockResolvedValue({
|
||||
id: 2,
|
||||
user_login: 'newgm',
|
||||
user_name: 'newgm',
|
||||
display_name: 'newgm',
|
||||
status: 'gm',
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
@@ -108,8 +108,8 @@ describe('UserManagementPage', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createMock).toHaveBeenCalledWith({
|
||||
user_login: 'newgm',
|
||||
user_name: 'New GM',
|
||||
user_name: 'newgm',
|
||||
display_name: 'New GM',
|
||||
status: 'gm',
|
||||
password: 'temp123',
|
||||
});
|
||||
|
||||
@@ -30,13 +30,13 @@ const LoginForm: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label htmlFor="user_login" className="block text-sm font-medium text-gray-700">
|
||||
<label htmlFor="user_name" className="block text-sm font-medium text-gray-700">
|
||||
Login
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
id="user_login"
|
||||
name="user_login"
|
||||
id="user_name"
|
||||
name="user_name"
|
||||
type="text"
|
||||
autoComplete="username"
|
||||
required
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('apiClient', () => {
|
||||
|
||||
it('sends CSRF token on POST', async () => {
|
||||
document.cookie = 'csrftoken=abc123; path=/';
|
||||
const fetchMock = vi.fn().mockResolvedValue(jsonResponse({ id: 1, user_name: 'admin' }));
|
||||
const fetchMock = vi.fn().mockResolvedValue(jsonResponse({ id: 1, user_name: 'admin', display_name: 'admin' }));
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
await api.auth.login('admin', 'admin123');
|
||||
|
||||
@@ -159,8 +159,8 @@ const del = (path: string) => request<void>(path, { method: 'DELETE' });
|
||||
export const api = {
|
||||
auth: {
|
||||
csrf: () => get<{ csrfToken: string }>('/auth/csrf/'),
|
||||
login: (user_login: string, password: string) =>
|
||||
post<User>('/auth/login/', { user_login, password }),
|
||||
login: (user_name: string, password: string) =>
|
||||
post<User>('/auth/login/', { user_name, password }),
|
||||
logout: () => post<void>('/auth/logout/'),
|
||||
me: () => get<User>('/auth/me/'),
|
||||
},
|
||||
|
||||
+2
-2
@@ -18,8 +18,8 @@ export type UserStatus =
|
||||
|
||||
export type User = {
|
||||
id: number;
|
||||
user_login: string;
|
||||
user_name: string;
|
||||
display_name: string;
|
||||
status: UserStatus;
|
||||
is_superuser?: boolean;
|
||||
managed_by?: number | null;
|
||||
@@ -32,8 +32,8 @@ export type User = {
|
||||
};
|
||||
|
||||
export type UserWrite = {
|
||||
user_login: string;
|
||||
user_name: string;
|
||||
display_name: string;
|
||||
status: UserStatus;
|
||||
password?: string;
|
||||
managed_by?: number | null;
|
||||
|
||||
Reference in new issue
Block a user