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:
|
||||
|
||||
Reference in new issue
Block a user