From 22d2087b4fa0dfc29ebcfc75382369d3f9bc4b46 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Wed, 5 Apr 2023 12:26:38 +0100 Subject: [PATCH] Move custom manager into a separate module and add tests --- hooks/post_gen_project.py | 21 +++++++ .../users/managers.py | 34 ++++++++++++ .../users/models.py | 46 ++-------------- .../users/tests/test_managers.py | 55 +++++++++++++++++++ 4 files changed, 115 insertions(+), 41 deletions(-) create mode 100644 {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/managers.py create mode 100644 {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_managers.py diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index b28bb0166..22cda531d 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -45,6 +45,24 @@ def remove_gplv3_files(): os.remove(file_name) +def remove_custom_user_manager_files(): + os.remove( + os.path.join( + "{{cookiecutter.project_slug}}", + "users", + "managers.py", + ) + ) + os.remove( + os.path.join( + "{{cookiecutter.project_slug}}", + "users", + "tests", + "test_managers.py", + ) + ) + + def remove_pycharm_files(): idea_dir_path = ".idea" if os.path.exists(idea_dir_path): @@ -441,6 +459,9 @@ def main(): if "{{ cookiecutter.open_source_license}}" != "GPLv3": remove_gplv3_files() + if "{{ cookiecutter.username_type }}" == "username": + remove_custom_user_manager_files() + if "{{ cookiecutter.use_pycharm }}".lower() == "n": remove_pycharm_files() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/managers.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/managers.py new file mode 100644 index 000000000..017ab14e7 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/managers.py @@ -0,0 +1,34 @@ +from django.contrib.auth.hashers import make_password +from django.contrib.auth.models import UserManager as DjangoUserManager + + +class UserManager(DjangoUserManager): + """Custom manager for the User model.""" + + def _create_user(self, email: str, password: str | None, **extra_fields): + """ + Create and save a user with the given email and password. + """ + if not email: + raise ValueError("The given email must be set") + 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: str, password: str | None = 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: str, password: str | None = 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) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py index ca828a07d..1e4807510 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py @@ -1,44 +1,10 @@ -{%- if cookiecutter.username_type == "email" -%} -from django.contrib.auth.hashers import make_password -{% endif -%} from django.contrib.auth.models import AbstractUser -{%- if cookiecutter.username_type == "email" %} -from django.contrib.auth.models import UserManager as DjangoUserManager -{%- endif %} from django.db.models import CharField{% if cookiecutter.username_type == "email" %}, EmailField{% endif %} from django.urls import reverse from django.utils.translation import gettext_lazy as _ {%- if cookiecutter.username_type == "email" %} - -class UserManager(DjangoUserManager): - 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 given email must be set") - 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, 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, 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) +from {{ cookiecutter.project_slug }}.users.managers import UserManager {%- endif %} @@ -49,16 +15,14 @@ class User(AbstractUser): check forms.SignupForm and forms.SocialSignupForms accordingly. """ - #: First and last name do not cover name patterns around the globe + # First and last name do not cover name patterns around the globe name = CharField(_("Name of User"), blank=True, max_length=255) - {%- if cookiecutter.username_type == "email" %} - username = None # type: ignore - email = EmailField(_("email address"), unique=True) - {%- endif %} first_name = None # type: ignore last_name = None # type: ignore - {%- if cookiecutter.username_type == "email" %} + email = EmailField(_("email address"), unique=True) + username = None # type: ignore + USERNAME_FIELD = "email" REQUIRED_FIELDS = [] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_managers.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_managers.py new file mode 100644 index 000000000..f25af4ee2 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_managers.py @@ -0,0 +1,55 @@ +from io import StringIO + +import pytest +from django.core.management import call_command + +from {{ cookiecutter.project_slug }}.users.models import User + + +@pytest.mark.django_db +class TestUserManager: + def test_create_user(self): + user = User.objects.create_user( + email="john@example.com", + password="something-r@nd0m!", + ) + assert user.email == "john@example.com" + assert not user.is_staff + assert not user.is_superuser + assert user.check_password("something-r@nd0m!") + assert user.username is None + + def test_create_superuser(self): + user = User.objects.create_superuser( + email="admin@example.com", + password="something-r@nd0m!", + ) + assert user.email == "admin@example.com" + assert user.is_staff + assert user.is_superuser + assert user.username is None + + def test_create_superuser_username_ignored(self): + user = User.objects.create_superuser( + email="test@example.com", + password="something-r@nd0m!", + ) + assert user.username is None + + +@pytest.mark.django_db +def test_createsuperuser_command(): + """Ensure createsuperuser command works with our custom manager.""" + out = StringIO() + command_result = call_command( + "createsuperuser", + "--email", + "henry@example.com", + interactive=False, + stdout=out, + ) + + assert command_result is None + assert out.getvalue() == "Superuser created successfully.\n" + user = User.objects.get(email="henry@example.com") + assert not user.has_usable_password()