From 8d42c5c36489de32d5c1f5a4a43386e5f4bbcaf3 Mon Sep 17 00:00:00 2001 From: Daniel Ursache Dogariu Date: Sat, 30 Mar 2024 13:00:54 +0200 Subject: [PATCH 1/3] Use a custom User model --- backend/seismic_site/settings.py | 4 + backend/users/__init__.py | 0 backend/users/admin.py | 3 + backend/users/apps.py | 6 ++ backend/users/migrations/0001_initial.py | 106 +++++++++++++++++++++++ backend/users/migrations/__init__.py | 0 backend/users/models.py | 71 +++++++++++++++ backend/users/tests.py | 3 + backend/users/views.py | 3 + 9 files changed, 196 insertions(+) create mode 100644 backend/users/__init__.py create mode 100644 backend/users/admin.py create mode 100644 backend/users/apps.py create mode 100644 backend/users/migrations/0001_initial.py create mode 100644 backend/users/migrations/__init__.py create mode 100644 backend/users/models.py create mode 100644 backend/users/tests.py create mode 100644 backend/users/views.py diff --git a/backend/seismic_site/settings.py b/backend/seismic_site/settings.py index 86cb41cb..c5f0db84 100644 --- a/backend/seismic_site/settings.py +++ b/backend/seismic_site/settings.py @@ -146,6 +146,7 @@ "django_q", "whitenoise.runserver_nostatic", # project apps + "users", "utils", "buildings", "static_custom", @@ -224,6 +225,9 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" +AUTH_USER_MODEL = "users.User" + + # Password validation # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators diff --git a/backend/users/__init__.py b/backend/users/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/users/admin.py b/backend/users/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/backend/users/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/backend/users/apps.py b/backend/users/apps.py new file mode 100644 index 00000000..88f7b179 --- /dev/null +++ b/backend/users/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UsersConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "users" diff --git a/backend/users/migrations/0001_initial.py b/backend/users/migrations/0001_initial.py new file mode 100644 index 00000000..ccc04642 --- /dev/null +++ b/backend/users/migrations/0001_initial.py @@ -0,0 +1,106 @@ +# Generated by Django 4.2.11 on 2024-03-30 11:00 + +from django.db import migrations, models +import django.db.models.functions.text +import django.utils.timezone +import users.models +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ] + + operations = [ + migrations.CreateModel( + name="User", + fields=[ + ("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")), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + unique=True, + verbose_name="ID", + ), + ), + ( + "username", + models.CharField( + blank=True, + editable=False, + help_text="Field reserved for future use", + max_length=150, + null=True, + unique=True, + verbose_name="username", + ), + ), + ("email", models.EmailField(max_length=254, unique=True, verbose_name="email address")), + ( + "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", + ), + ), + ], + managers=[ + ("objects", users.models.CustomUserManager()), + ], + ), + migrations.AddConstraint( + model_name="user", + constraint=models.UniqueConstraint(django.db.models.functions.text.Lower("email"), name="email_unique"), + ), + ] diff --git a/backend/users/migrations/__init__.py b/backend/users/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/users/models.py b/backend/users/models.py new file mode 100644 index 00000000..88bc9f86 --- /dev/null +++ b/backend/users/models.py @@ -0,0 +1,71 @@ +import uuid + +from django.contrib.auth.hashers import make_password +from django.contrib.auth.models import AbstractUser, UserManager +from django.db import models +from django.db.models.functions import Lower +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ + + +class CustomUserManager(UserManager): + def _create_user(self, email, password, **extra_fields): + """ + Create and save a user with the given email and password. + """ + email = self.normalize_email(email) + user = self.model(email=email, **extra_fields) + user.password = make_password(password) + user.save(using=self._db) + return user + + def create_user(self, email=None, password=None, **extra_fields): + extra_fields.setdefault("is_staff", False) + extra_fields.setdefault("is_superuser", False) + return self._create_user(email, password, **extra_fields) + + def create_superuser(self, email=None, password=None, **extra_fields): + extra_fields.setdefault("is_staff", True) + extra_fields.setdefault("is_superuser", 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) + + +class User(AbstractUser): + """ + The default Django user model, but change it to use the email address + instead of the username + """ + + id = models.UUIDField(verbose_name="ID", primary_key=True, unique=True, default=uuid.uuid4, editable=False) + + # We ignore the "username" field because the authentication + # will be done by email + password + username = models.CharField( + verbose_name=_("username"), + max_length=150, + unique=True, + help_text=_("Field reserved for future use"), + validators=[], + blank=True, + null=True, + editable=False, + ) + + email = models.EmailField(verbose_name=_("email address"), blank=False, null=False, unique=True) + + objects = CustomUserManager() + + USERNAME_FIELD = "email" + REQUIRED_FIELDS = [] + + class Meta: + constraints = [ + models.UniqueConstraint(Lower("email"), name="email_unique"), + ] + diff --git a/backend/users/tests.py b/backend/users/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/backend/users/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/users/views.py b/backend/users/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/backend/users/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 735869bb86e21dad72c1aa51ede1326463043193 Mon Sep 17 00:00:00 2001 From: Daniel Ursache Dogariu Date: Sat, 30 Mar 2024 13:01:32 +0200 Subject: [PATCH 2/3] Drop unused settings --- backend/seismic_site/settings.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/backend/seismic_site/settings.py b/backend/seismic_site/settings.py index c5f0db84..49c878e7 100644 --- a/backend/seismic_site/settings.py +++ b/backend/seismic_site/settings.py @@ -309,12 +309,8 @@ STATIC_ROOT = os.path.abspath(os.path.join(BASE_DIR, "static")) MEDIA_ROOT = os.path.abspath(os.path.join(BASE_DIR, "media")) -DEV_DEPENDECIES_LOCATION = "bower_components" +STATICFILES_DIRS = [] -STATICFILES_DIRS = [ - os.path.abspath(os.path.join(DEV_DEPENDECIES_LOCATION)), - os.path.abspath(os.path.join("static_extras")), -] default_storage_options = {} From 302e01eb0a2fa30e87d02feb2d0f8c3ee380b369 Mon Sep 17 00:00:00 2001 From: Daniel Ursache Dogariu Date: Sat, 30 Mar 2024 14:30:15 +0200 Subject: [PATCH 3/3] Update the seeder scripts --- backend/tests/conftest.py | 2 +- backend/utils/management/commands/_private/seed_user.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 56e74f59..aab51634 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -15,4 +15,4 @@ def test_user(db, django_user_model, basic_user_data): @pytest.fixture def basic_user_data(): - return {"username": "testuser", "password": "testpassword"} + return {"email": "testuser", "password": "testpassword"} diff --git a/backend/utils/management/commands/_private/seed_user.py b/backend/utils/management/commands/_private/seed_user.py index 82c7ff96..ba805952 100644 --- a/backend/utils/management/commands/_private/seed_user.py +++ b/backend/utils/management/commands/_private/seed_user.py @@ -9,9 +9,9 @@ class CommonCreateUserCommand(BaseCommand): def add_arguments(self, parser): parser.add_argument( - "--username", + "--email", type=str, - help="Username of the superuser (default: email)", + help="Email of the superuser", required=False, ) parser.add_argument( @@ -34,7 +34,6 @@ def _create_user( password: str, is_superuser: bool, is_staff: bool, - username: str = None, first_name: str = "Admin", last_name: str = "Admin", ): @@ -49,7 +48,6 @@ def _create_user( user = user_model( email=admin_email, - username=username or admin_email, first_name=first_name, last_name=last_name, is_active=True,