diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py index cbf69aaaa..10fe4e709 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py @@ -14,7 +14,7 @@ class UserAdmin(auth_admin.UserAdmin): form = UserAdminChangeForm add_form = UserAdminCreationForm fieldsets = ( - {% if cookiecutter.username_type == "email" -%} + {%- if cookiecutter.username_type == "email" -%} (None, {"fields": ("email", "password")}), (_("Personal info"), {"fields": ("name",)}), {%- else %} @@ -37,3 +37,15 @@ class UserAdmin(auth_admin.UserAdmin): ) list_display = ["{{cookiecutter.username_type}}", "name", "is_superuser"] search_fields = ["name"] + {% if cookiecutter.username_type == "email" -%} + ordering = ["id"] + add_fieldsets = ( + ( + None, + { + "classes": ("wide",), + "fields": ("email", "password1", "password2"), + }, + ), + ) + {%- endif %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py index eb0c95c62..1c30bb8ff 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py @@ -11,7 +11,7 @@ class UserSerializer(serializers.ModelSerializer): fields = ["name", "url"] extra_kwargs = { - "url": {"view_name": "api:user-detail", "lookup_field": "id"} + "url": {"view_name": "api:user-detail", "lookup_field": "pk"} } {%- else %} fields = ["username", "name", "url"] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py index b2f3625ef..170566558 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py @@ -14,7 +14,7 @@ class UserViewSet(RetrieveModelMixin, ListModelMixin, UpdateModelMixin, GenericV serializer_class = UserSerializer queryset = User.objects.all() {% if cookiecutter.username_type == "email" -%} - lookup_field = "id" + lookup_field = "pk" {%- else %} lookup_field = "username" {%- endif %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py index ab5abf784..d9a7446bf 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py @@ -2,6 +2,7 @@ from allauth.account.forms import SignupForm from allauth.socialaccount.forms import SignupForm as SocialSignupForm from django.contrib.auth import forms as admin_forms from django.contrib.auth import get_user_model +from django.forms import EmailField User = get_user_model() @@ -9,6 +10,7 @@ User = get_user_model() class UserAdminChangeForm(admin_forms.UserChangeForm): class Meta(admin_forms.UserChangeForm.Meta): model = User + field_classes = {"email": EmailField} class UserAdminCreationForm(admin_forms.UserCreationForm): @@ -19,6 +21,8 @@ class UserAdminCreationForm(admin_forms.UserCreationForm): class Meta(admin_forms.UserCreationForm.Meta): model = User + fields = ("email",) + field_classes = {"email": EmailField} class UserSignupForm(SignupForm): diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py index f7d721036..84930d9ae 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py @@ -42,6 +42,7 @@ class Migration(migrations.Migration): verbose_name="superuser status", ), ), + {%- if cookiecutter.username_type == "email" -%} ( "username", models.CharField( @@ -63,6 +64,14 @@ class Migration(migrations.Migration): blank=True, max_length=254, verbose_name="email address" ), ), + {%- else %} + ( + "email", + models.EmailField( + unique=True, max_length=254, verbose_name="email address" + ), + ), + {%- endif %} ( "is_staff", models.BooleanField( diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py index 9d86e7a9b..32cc19ef7 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py @@ -1,14 +1,14 @@ -{% if cookiecutter.username_type == "email" -%} +{%- if cookiecutter.username_type == "email" -%} from django.contrib.auth.hashers import make_password from django.contrib.auth.models import AbstractUser, UserManager as DjangoUserManager {%- else %} from django.contrib.auth.models import AbstractUser {%- endif %} -from django.db.models import CharField +from django.db.models import CharField, EmailField 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): """ @@ -38,7 +38,7 @@ class UserManager(DjangoUserManager): return self._create_user(email, password, **extra_fields) - +{%- endif %} class User(AbstractUser): """ @@ -51,12 +51,16 @@ class User(AbstractUser): 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" -%} + {%- if cookiecutter.username_type == "email" -%} USERNAME_FIELD = "email" + REQUIRED_FIELDS = [] + + objects = UserManager() {%- endif %} def get_absolute_url(self): @@ -66,7 +70,7 @@ class User(AbstractUser): str: URL for user detail. """ - {% if cookiecutter.username_type == "email" -%} + {%- if cookiecutter.username_type == "email" -%} return reverse("users:detail", kwargs={"id": self.id}) {%- else %} return reverse("users:detail", kwargs={"username": self.username}) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py index 763a293e7..29b6f32a0 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py @@ -22,8 +22,8 @@ class TestUserAdmin: response = admin_client.post( url, data={ - {% if cookiecutter.username_type == "email" -%} - "email": "test@test.com", + {%- if cookiecutter.username_type == "email" -%} + "email": "new-admin@example.com", {%- else %} "username": "test", {%- endif %} @@ -32,14 +32,18 @@ class TestUserAdmin: }, ) assert response.status_code == 302 - {% if cookiecutter.username_type == "email" -%} - assert User.objects.filter(email="test@test.com").exists() + {%- if cookiecutter.username_type == "email" -%} + assert User.objects.filter(email="new-admin@example.com").exists() {%- else %} assert User.objects.filter(username="test").exists() {%- endif %} def test_view_user(self, admin_client): + {%- if cookiecutter.username_type == "email" -%} + user = User.objects.get(email="admin@example.com") + {%- else %} user = User.objects.get(username="admin") + {%- endif %} url = reverse("admin:users_user_change", kwargs={"object_id": user.pk}) response = admin_client.get(url) assert response.status_code == 200 diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py index ba03cccc9..65c4d6987 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py @@ -25,7 +25,6 @@ class TestUserViewSet: assert response.data == { {% if cookiecutter.username_type == "email" -%} - "email": user.email, "url": f"http://testserver/api/users/{user.pk}/", {%- else %} "username": user.username, diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py index 473dfd7a5..1bc326bc8 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py @@ -24,7 +24,7 @@ class TestUserAdminCreationForm: # hence cannot be created. form = UserAdminCreationForm( { - {% if cookiecutter.username_type == "email" -%} + {%- if cookiecutter.username_type == "email" -%} "email": user.email, {%- else %} "username": user.username, @@ -36,7 +36,7 @@ class TestUserAdminCreationForm: assert not form.is_valid() assert len(form.errors) == 1 - {% if cookiecutter.username_type == "email" -%} + {%- if cookiecutter.username_type == "email" -%} assert "email" in form.errors assert form.errors["email"][0] == _("This email has already been taken.") {%- else %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py index 87bd5fd5b..a99006f0a 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py @@ -2,7 +2,7 @@ from {{ cookiecutter.project_slug }}.users.models import User def test_user_get_absolute_url(user: User): - {% if cookiecutter.username_type == "email" -%} + {%- if cookiecutter.username_type == "email" -%} assert user.get_absolute_url() == f"/users/{user.pk}/" {%- else %} assert user.get_absolute_url() == f"/users/{user.username}/" diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py index bca40dcc1..6d2249fb0 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py @@ -4,7 +4,7 @@ from {{ cookiecutter.project_slug }}.users.models import User def test_detail(user: User): - {% if cookiecutter.username_type == "email" -%} + {%- if cookiecutter.username_type == "email" -%} assert ( reverse("users:detail", kwargs={"pk": user.pk}) == f"/users/{user.pk}/" diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py index b3563d03b..cdf0312e2 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py @@ -39,7 +39,7 @@ class TestUserUpdateView: view.request = request - {% if cookiecutter.username_type == "email" -%} + {%- if cookiecutter.username_type == "email" -%} assert view.get_success_url() == f"/users/{user.pk}/" {%- else %} assert view.get_success_url() == f"/users/{user.username}/" @@ -83,7 +83,7 @@ class TestUserRedirectView: view.request = request - {% if cookiecutter.username_type == "email" -%} + {%- if cookiecutter.username_type == "email" -%} assert view.get_redirect_url() == f"/users/{user.pk}/" {%- else %} assert view.get_redirect_url() == f"/users/{user.username}/" @@ -95,7 +95,7 @@ class TestUserDetailView: request = rf.get("/fake-url/") request.user = UserFactory() - {% if cookiecutter.username_type == "email" -%} + {%- if cookiecutter.username_type == "email" -%} response = user_detail_view(request, pk=user.pk) {%- else %} response = user_detail_view(request, username=user.username) @@ -107,7 +107,7 @@ class TestUserDetailView: request = rf.get("/fake-url/") request.user = AnonymousUser() - {% if cookiecutter.username_type == "email" -%} + {%- if cookiecutter.username_type == "email" -%} response = user_detail_view(request, pk=user.pk) {%- else %} response = user_detail_view(request, username=user.username) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py index 3556dd95a..86a5cf5e6 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py @@ -10,8 +10,8 @@ app_name = "users" urlpatterns = [ path("~redirect/", view=user_redirect_view, name="redirect"), path("~update/", view=user_update_view, name="update"), - {% if cookiecutter.username_type == "email" -%} - path("/", view=user_detail_view, name="detail"), + {%- if cookiecutter.username_type == "email" -%} + path("/", view=user_detail_view, name="detail"), {%- else %} path("/", view=user_detail_view, name="detail"), {%- endif %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py index 59a8373c0..bb19f9f4e 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py @@ -11,7 +11,7 @@ User = get_user_model() class UserDetailView(LoginRequiredMixin, DetailView): model = User - {% if cookiecutter.username_type == "email" -%} + {%- if cookiecutter.username_type == "email" -%} slug_field = "id" slug_url_kwarg = "id" {%- else %} @@ -47,7 +47,7 @@ class UserRedirectView(LoginRequiredMixin, RedirectView): permanent = False def get_redirect_url(self): - {% if cookiecutter.username_type == "email" -%} + {%- if cookiecutter.username_type == "email" -%} return reverse("users:detail", kwargs={"pk": self.request.user.pk}) {%- else %} return reverse("users:detail", kwargs={"username": self.request.user.username})