diff --git a/backend/backend/admin.py b/backend/backend/admin.py index fadff5e..8e453da 100644 --- a/backend/backend/admin.py +++ b/backend/backend/admin.py @@ -1,30 +1,52 @@ from django.contrib import admin -from django.contrib.auth.admin import UserAdmin +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin +from django.contrib.auth.models import Group +from unfold.admin import ModelAdmin -from .models import * +from .models import ( + Member, + Position, + Appointment, + Reference, + StudyProgram, + Section, + Team, + Application, + Role, +) +admin.site.unregister(Group) -# Register your models here. -class MemberAdmin(UserAdmin): + +@admin.register(Group) +class GroupAdmin(BaseGroupAdmin, ModelAdmin): + """Group admin with Unfold styling for managing roles/permissions.""" + list_filter_submit = True + + +class MemberAdmin(BaseUserAdmin, ModelAdmin): """ Custom admin interface for the Member model. Required as we are using ssn as the username instead of a username - and do not want a username field + and do not want a username field. """ model = Member - list_display = ("ssn", "email", "name", "is_staff", "is_active", "verified_email") + list_display = ("ssn", "email", "name", "status", "is_staff", "is_active", "verified_email") + list_filter = ("status", "is_staff", "is_active", "verified_email") search_fields = ("ssn", "email", "name") - ordering = ( - "ssn", - "email", - ) + ordering = ("ssn", "email") + list_filter_submit = True + filter_horizontal = ("groups", "user_permissions") + fieldsets = ( (None, {"fields": ("ssn", "email", "password")}), - ("Personal info", {"fields": ("name",)}), + ("Personal info", {"fields": ("name", "phone_number", "study_program", "registration_year")}), ( "Permissions", - {"fields": ("is_active", "is_staff", "is_superuser", "verified_email")}, + {"fields": ("is_active", "is_staff", "is_superuser", "verified_email", "status", "groups", + "user_permissions")}, ), ) add_fieldsets = ( @@ -34,7 +56,10 @@ class MemberAdmin(UserAdmin): "fields": ( "ssn", "email", - "password", + "password1", + "password2", + "name", + "phone_number", "is_active", "is_staff", "is_superuser", @@ -45,10 +70,72 @@ class MemberAdmin(UserAdmin): ) +@admin.register(Team) +class TeamAdmin(ModelAdmin): + list_display = ("name_en", "name_sv") + search_fields = ("name_en", "name_sv", "desc_en", "desc_sv") + list_filter_submit = True + + +@admin.register(Role) +class RoleAdmin(ModelAdmin): + list_display = ("title_en", "title_sv", "team", "role_type", "archived") + list_filter = ("team", "role_type", "archived") + search_fields = ("title_en", "title_sv", "description_en", "description_sv", "team__name_en", "team__name_sv") + list_filter_submit = True + + +@admin.register(Position) +class PositionAdmin(ModelAdmin): + list_display = ("role", "recruitment_start", "recruitment_end", "term_from", "term_end", "appointed") + list_filter = ("role__team", "recruitment_start", "term_from") + search_fields = ("role__title_en", "role__title_sv", "comment_eng", "comment_sv") + list_filter_submit = True + date_hierarchy = "recruitment_start" + + +@admin.register(Application) +class ApplicationAdmin(ModelAdmin): + list_display = ("position", "member", "status", "decision_date") + list_filter = ("status", "position__role__team") + search_fields = ( + "position__role__title_en", + "position__role__title_sv", + "member__name", + "member__email", + ) + list_filter_submit = True + + +@admin.register(Appointment) +class AppointmentAdmin(ModelAdmin): + list_display = ("member", "position", "status", "appointed_date", "appointed_by") + list_filter = ("status", "position__role__team") + search_fields = ("member__name", "member__email", "position__role__title_en", "position__role__title_sv") + list_filter_submit = True + date_hierarchy = "appointed_date" + + +@admin.register(Reference) +class ReferenceAdmin(ModelAdmin): + list_display = ("name", "application", "email", "phone_num", "title") + search_fields = ("name", "email", "application__member__name") + list_filter_submit = True + + +@admin.register(Section) +class SectionAdmin(ModelAdmin): + list_display = ("abbreviation", "section_en", "section_sv") + search_fields = ("abbreviation", "section_en", "section_sv") + list_filter_submit = True + + +@admin.register(StudyProgram) +class StudyProgramAdmin(ModelAdmin): + list_display = ("name_en", "name_sv", "section") + list_filter = ("section",) + search_fields = ("name_en", "name_sv", "section__abbreviation") + list_filter_submit = True + + admin.site.register(Member, MemberAdmin) -admin.site.register(Role) -admin.site.register(Team) -admin.site.register(Position) -admin.site.register(Application) -admin.site.register(Reference) -admin.site.register(Appointment) diff --git a/backend/backend/managers.py b/backend/backend/managers.py index d85c62b..81b08a4 100644 --- a/backend/backend/managers.py +++ b/backend/backend/managers.py @@ -10,34 +10,35 @@ class MemberManager(BaseUserManager): Methods ------- - create_user(email, password=None, **extra_fields) - Creates and returns a user with an email, password and other fields. - create_superuser(email, password=None, **extra_fields) - Creates and returns a superuser with an email, password and other fields. + create_user(ssn, email, password=None, **extra_fields) + Creates and returns a user with ssn, email, password and other fields. + create_superuser(ssn, email, password=None, **extra_fields) + Creates and returns a superuser with ssn, email, password and other fields. """ - def create_user(self, email, password=None, **extra_fields): + def create_user(self, ssn, email, password=None, **extra_fields): + if not ssn: + raise ValueError("The SSN field must be set") if not email: raise ValueError("The Email field must be set") - user = self.model(email=self.normalize_email(email), **extra_fields) - - user.set_password(password) - user.save(using=self._db) - return user - - def create_superuser(self, email, password=None, **extra_fields): user = self.model( + ssn=ssn, email=self.normalize_email(email), - is_staff=True, - is_superuser=True, **extra_fields, ) user.set_password(password) user.save(using=self._db) - return user + def create_superuser(self, ssn, email, password=None, **extra_fields): + extra_fields.setdefault("is_staff", True) + extra_fields.setdefault("is_superuser", True) + extra_fields.setdefault("is_active", True) + extra_fields.setdefault("verified_email", True) + + return self.create_user(ssn, email, password, **extra_fields) + class PositionManager(Manager): """Custom manager for Position model""" diff --git a/backend/backend/models.py b/backend/backend/models.py index d43e7e5..ce7f8ad 100644 --- a/backend/backend/models.py +++ b/backend/backend/models.py @@ -41,7 +41,7 @@ class Member(AbstractBaseUser, PermissionsMixin): USERNAME_FIELD = "ssn" EMAIL_FIELD = "email" - REQUIRED_FIELDS = [] # TODO: add more fields, maybe + REQUIRED_FIELDS = ["email", "name", "phone_number"] objects = MemberManager() @@ -146,20 +146,22 @@ def has_perm(self, perm, obj=None): if not self.verified_email: return False - # Lets superusers and staff do anything - if self.is_superuser or self.is_staff: + # Only superusers get all permissions + if self.is_superuser: return True + # Staff and regular users: check actual permissions return super().has_perm(perm, obj) def has_module_perms(self, app_label): if not self.verified_email: return False - # Lets superusers and staff do anything - if self.is_superuser or self.is_staff: + # Only superusers get all module permissions + if self.is_superuser: return True + # Staff and regular users: check actual permissions return super().has_module_perms(app_label) @staticmethod @@ -182,6 +184,9 @@ def find_user_by_ssn(ssn): return None + def __str__(self): + return f"{self.name} ({self.ssn})" + class Position(models.Model): """ @@ -225,6 +230,9 @@ class Position(models.Model): comment_eng = models.TextField(verbose_name=("Comment in English"), blank=True) comment_sv = models.TextField(verbose_name=("Comment in Swedish"), blank=True) + def __str__(self): + return f"{self.role} ({self.term_from} - {self.term_end})" + class Appointment(models.Model): """ @@ -360,6 +368,9 @@ class Reference(models.Model): blank=True, ) + def __str__(self): + return f"{self.name} - {self.application}" + class StudyProgram(models.Model): """ @@ -390,6 +401,9 @@ class StudyProgram(models.Model): help_text=_("Enter the name of the section in Swedish"), ) + def __str__(self): + return self.name_en + class Section(models.Model): """ @@ -421,6 +435,9 @@ class Section(models.Model): blank=False, ) + def __str__(self): + return f"{self.abbreviation} - {self.section_en}" + class Team(models.Model): """ @@ -477,6 +494,9 @@ class Team(models.Model): # FieldPanel('description_sv'), # ])] + def __str__(self): + return self.name_en + class Application(models.Model): """ @@ -556,6 +576,9 @@ class Application(models.Model): verbose_name=_("Decision date"), null=True, blank=True ) + def __str__(self): + return f"{self.member.name} - {self.position} ({self.status})" + class Role(models.Model): """ @@ -683,3 +706,6 @@ def role_type_to_level(role_type): # FieldPanel('role_type'), # FieldPanel('teams', widget=CheckboxSelectMultiple), # ])] + + def __str__(self): + return f"{self.title_en} ({self.team})" diff --git a/backend/config/settings.py b/backend/config/settings.py index ee264f5..f9ab4cb 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -39,6 +39,8 @@ # Application definition INSTALLED_APPS = [ + "unfold", + "unfold.contrib.filters", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", @@ -165,3 +167,13 @@ MEDIA_ROOT = "../media" + + +# Django Unfold Admin Configuration +UNFOLD = { + "SITE_TITLE": "UTN Apply Admin", + "SITE_HEADER": "UTN Apply", + "SITE_SYMBOL": "diversity_3", # Material icon + "SHOW_HISTORY": True, + "SHOW_VIEW_ON_SITE": True, +} diff --git a/backend/requirements.txt b/backend/requirements.txt index 074ca9b..59c6d5d 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -16,3 +16,6 @@ python-decouple==3.8 psycopg2 Pillow>=8.0.0 + +# Admin theme +django-unfold