diff --git a/main/settings.py b/main/settings.py index 6db134f..587e9da 100644 --- a/main/settings.py +++ b/main/settings.py @@ -64,6 +64,7 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", + "user", ] MIDDLEWARE = [ @@ -112,6 +113,7 @@ }, } +AUTH_USER_MODEL = "user.User" # Password validation # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators diff --git a/user/__init__.py b/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user/admin.py b/user/admin.py new file mode 100755 index 0000000..d08019c --- /dev/null +++ b/user/admin.py @@ -0,0 +1,57 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin +from django.utils.translation import gettext_lazy as _ + +from .models import User + + +@admin.register(User) +class UserAdmin(DjangoUserAdmin): + list_display = ("email", "first_name", "last_name", "is_staff") + list_filter = ("is_staff", "is_superuser", "is_active", "groups") + search_fields = ("first_name", "last_name", "email") + ordering = ("email",) + + fieldsets = ( + ( + None, + { + "fields": ( + "email", + "password", + ) + }, + ), + ( + _("Personal info"), + { + "fields": ( + "first_name", + "last_name", + "department", + ) + }, + ), + ( + _("Permissions"), + { + "fields": ( + "is_active", + "is_staff", + "is_superuser", + "groups", + "user_permissions", + ), + }, + ), + (_("Important dates"), {"fields": ("last_login", "date_joined")}), + ) + add_fieldsets = ( + ( + None, + { + "classes": ("wide",), + "fields": ("email", "password1", "password2"), + }, + ), + ) diff --git a/user/apps.py b/user/apps.py new file mode 100755 index 0000000..578292c --- /dev/null +++ b/user/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "user" diff --git a/user/managers.py b/user/managers.py new file mode 100644 index 0000000..451a23f --- /dev/null +++ b/user/managers.py @@ -0,0 +1,35 @@ +from django.contrib.auth.base_user import BaseUserManager +from django.utils.translation import gettext_lazy as _ + + +class CustomUserManager(BaseUserManager): + """ + Custom user model manager where email is the unique identifiers + for authentication instead of usernames. + """ + + def create_user(self, email, password, **extra_fields): + """ + Create and save a user with the given email and password. + """ + if not email: + raise ValueError(_("The Email must be set")) + email = self.normalize_email(email) + user = self.model(email=email, **extra_fields) + user.set_password(password) + user.save() + return user + + def create_superuser(self, email, password, **extra_fields): + """ + Create and save a SuperUser with the given email and password. + """ + extra_fields.setdefault("is_staff", True) + extra_fields.setdefault("is_superuser", True) + extra_fields.setdefault("is_active", True) + + if extra_fields.get("is_staff") is not True: + raise ValueError(_("Superuser must have is_staff=True.")) + if extra_fields.get("is_superuser") is not True: + raise ValueError(_("Superuser must have is_superuser=True.")) + return self.create_user(email, password, **extra_fields) diff --git a/user/migrations/0001_initial.py b/user/migrations/0001_initial.py new file mode 100644 index 0000000..30e9b22 --- /dev/null +++ b/user/migrations/0001_initial.py @@ -0,0 +1,84 @@ +# Generated by Django 5.1 on 2024-09-11 05:48 + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ] + + operations = [ + migrations.CreateModel( + name="User", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("password", models.CharField(max_length=128, verbose_name="password")), + ("last_login", models.DateTimeField(blank=True, null=True, verbose_name="last login")), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ("first_name", models.CharField(blank=True, max_length=150, verbose_name="first name")), + ("last_name", models.CharField(blank=True, max_length=150, verbose_name="last name")), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ("date_joined", models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined")), + ("email", models.EmailField(max_length=254, unique=True)), + ( + "display_name", + models.CharField(blank=True, max_length=255, verbose_name="system generated user display name"), + ), + ("user_type", models.PositiveSmallIntegerField(choices=[(1000, "HR")], null=True)), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.permission", + verbose_name="user permissions", + ), + ), + ], + options={ + "verbose_name": "user", + "verbose_name_plural": "users", + "abstract": False, + }, + ), + ] diff --git a/user/migrations/0002_alter_user_user_type.py b/user/migrations/0002_alter_user_user_type.py new file mode 100755 index 0000000..65a599e --- /dev/null +++ b/user/migrations/0002_alter_user_user_type.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1 on 2024-09-12 05:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("user", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="user_type", + field=models.PositiveSmallIntegerField(choices=[(1, "HR")], null=True), + ), + ] diff --git a/user/migrations/0003_rename_user_type_user_department.py b/user/migrations/0003_rename_user_type_user_department.py new file mode 100644 index 0000000..0cde0f8 --- /dev/null +++ b/user/migrations/0003_rename_user_type_user_department.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1 on 2024-09-13 09:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("user", "0002_alter_user_user_type"), + ] + + operations = [ + migrations.RenameField( + model_name="user", + old_name="user_type", + new_name="department", + ), + ] diff --git a/user/migrations/__init__.py b/user/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user/models.py b/user/models.py new file mode 100755 index 0000000..99f2085 --- /dev/null +++ b/user/models.py @@ -0,0 +1,33 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from .managers import CustomUserManager + + +# Create your models here. +class User(AbstractUser): + class Department(models.IntegerChoices): + HR = 1, _("HR") + + EMAIL_FIELD = USERNAME_FIELD = "email" + REQUIRED_FIELDS = [] + + username = None + email = models.EmailField(unique=True) + display_name = models.CharField( + verbose_name=_("system generated user display name"), + blank=True, + max_length=255, + ) + department = models.PositiveSmallIntegerField(choices=Department.choices, null=True) + + objects: CustomUserManager = CustomUserManager() + + def save(self, *args, **kwargs): + # Make sure email are store in lowercase + self.email = self.email.lower() + if self.pk is None: + super().save(*args, **kwargs) + self.display_name = self.get_full_name() or f"User#{self.pk}" + return super().save(*args, **kwargs) diff --git a/user/tests.py b/user/tests.py new file mode 100644 index 0000000..a39b155 --- /dev/null +++ b/user/tests.py @@ -0,0 +1 @@ +# Create your tests here. diff --git a/user/views.py b/user/views.py new file mode 100644 index 0000000..60f00ef --- /dev/null +++ b/user/views.py @@ -0,0 +1 @@ +# Create your views here.