From b360f1a820c18a982bdc634d6da6298392cc50db Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Tue, 5 May 2026 14:23:03 +0100 Subject: [PATCH 1/4] feat(roles): rename role name to display_name and add timestamps --- ...003_rename_role_name_and_add_timestamps.py | 47 +++++++++++++++++++ apps/roles/models.py | 17 ++++--- tests/test_models.py | 4 +- 3 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 apps/roles/migrations/0003_rename_role_name_and_add_timestamps.py diff --git a/apps/roles/migrations/0003_rename_role_name_and_add_timestamps.py b/apps/roles/migrations/0003_rename_role_name_and_add_timestamps.py new file mode 100644 index 0000000..4241077 --- /dev/null +++ b/apps/roles/migrations/0003_rename_role_name_and_add_timestamps.py @@ -0,0 +1,47 @@ +# Generated by Django 5.2.13 on 2026-05-05 + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("roles", "0002_initial"), + ] + + operations = [ + migrations.RenameField( + model_name="role", + old_name="name", + new_name="display_name", + ), + migrations.AlterModelOptions( + name="role", + options={"ordering": ("display_name",)}, + ), + migrations.AlterField( + model_name="role", + name="display_name", + field=models.CharField(max_length=120, unique=True), + ), + migrations.AlterField( + model_name="role", + name="slug", + field=models.SlugField(unique=True), + ), + migrations.AddField( + model_name="role", + name="created_at", + field=models.DateTimeField( + auto_now_add=True, + default=django.utils.timezone.now, + ), + preserve_default=False, + ), + migrations.AddField( + model_name="role", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/apps/roles/models.py b/apps/roles/models.py index d4a03ff..81df275 100644 --- a/apps/roles/models.py +++ b/apps/roles/models.py @@ -1,4 +1,4 @@ -"""Role models for StudyBuddy users.""" +"""Role models for StudyBuddy access control.""" from __future__ import annotations @@ -7,11 +7,13 @@ class Role(models.Model): - """Named role that can be assigned to users.""" + """Named role used to support role-aware behaviour in the product.""" - name = models.CharField(max_length=80, unique=True) - slug = models.SlugField(max_length=100, unique=True) + slug = models.SlugField(unique=True) + display_name = models.CharField(max_length=120, unique=True) description = models.TextField(blank=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) users = models.ManyToManyField( settings.AUTH_USER_MODEL, blank=True, @@ -19,7 +21,10 @@ class Role(models.Model): ) class Meta: - ordering = ["name"] + """Model metadata for roles.""" + + ordering = ("display_name",) def __str__(self) -> str: - return self.name + """Return the human-readable role name.""" + return self.display_name diff --git a/tests/test_models.py b/tests/test_models.py index a86bde4..4f4fbea 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -108,7 +108,7 @@ def test_custom_user_display_name_falls_back_to_email() -> None: @pytest.mark.django_db def test_role_string_uses_name() -> None: """Roles display with their human-readable name.""" - role = Role.objects.create(name="Student", slug="student") + role = Role.objects.create(display_name="Student", slug="student") assert str(role) == "Student" @@ -121,7 +121,7 @@ def test_role_can_be_assigned_to_user() -> None: email="grace@example.com", password="password123", ) - role = Role.objects.create(name="Tutor", slug="tutor") + role = Role.objects.create(display_name="Tutor", slug="tutor") role.users.add(user) From 15459d9cea0a2b9c52ffea9a24bcad7d7b0952a9 Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Tue, 5 May 2026 14:25:15 +0100 Subject: [PATCH 2/4] feat(roles): add admin configuration for role management --- apps/roles/admin.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 apps/roles/admin.py diff --git a/apps/roles/admin.py b/apps/roles/admin.py new file mode 100644 index 0000000..5d41dd4 --- /dev/null +++ b/apps/roles/admin.py @@ -0,0 +1,17 @@ +"""Admin configuration for role management.""" + +from __future__ import annotations + +from django.contrib import admin + +from apps.roles.models import Role + + +@admin.register(Role) +class RoleAdmin(admin.ModelAdmin): + """Admin interface for StudyBuddy roles.""" + + list_display = ("display_name", "slug", "created_at", "updated_at") + search_fields = ("display_name", "slug", "description") + prepopulated_fields = {"slug": ("display_name",)} + readonly_fields = ("created_at", "updated_at") \ No newline at end of file From 9786ec1afc10d85acf2f075e92dc90bade9854da Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Tue, 5 May 2026 14:29:20 +0100 Subject: [PATCH 3/4] chore(roles): preserve role ownership of user assignments --- apps/roles/apps.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/roles/apps.py b/apps/roles/apps.py index d28ad2f..a9e96d0 100644 --- a/apps/roles/apps.py +++ b/apps/roles/apps.py @@ -1,4 +1,4 @@ -"""Role application configuration.""" +"""Application configuration for the roles app.""" from __future__ import annotations @@ -10,3 +10,4 @@ class RolesConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" name = "apps.roles" + label = "roles" From 8aed2c868e3be7b4e0d9da069f193fea5e2138d2 Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Tue, 5 May 2026 14:42:57 +0100 Subject: [PATCH 4/4] style(roles): format role admin --- apps/roles/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/roles/admin.py b/apps/roles/admin.py index 5d41dd4..3cc28bf 100644 --- a/apps/roles/admin.py +++ b/apps/roles/admin.py @@ -14,4 +14,4 @@ class RoleAdmin(admin.ModelAdmin): list_display = ("display_name", "slug", "created_at", "updated_at") search_fields = ("display_name", "slug", "description") prepopulated_fields = {"slug": ("display_name",)} - readonly_fields = ("created_at", "updated_at") \ No newline at end of file + readonly_fields = ("created_at", "updated_at")