diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/tests___all.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/pytest___.xml
similarity index 52%
rename from {{cookiecutter.project_slug}}/.idea/runConfigurations/tests___all.xml
rename to {{cookiecutter.project_slug}}/.idea/runConfigurations/pytest___.xml
index be70ffcd6..08f76c454 100644
--- a/{{cookiecutter.project_slug}}/.idea/runConfigurations/tests___all.xml
+++ b/{{cookiecutter.project_slug}}/.idea/runConfigurations/pytest___.xml
@@ -1,18 +1,14 @@
-
+
-
-
-
-
-
+
-
-
-
-
-
+
+
+
+
-
+
\ No newline at end of file
diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/tests___module__users.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/pytest__users.xml
similarity index 50%
rename from {{cookiecutter.project_slug}}/.idea/runConfigurations/tests___module__users.xml
rename to {{cookiecutter.project_slug}}/.idea/runConfigurations/pytest__users.xml
index d838b5da6..574361fea 100644
--- a/{{cookiecutter.project_slug}}/.idea/runConfigurations/tests___module__users.xml
+++ b/{{cookiecutter.project_slug}}/.idea/runConfigurations/pytest__users.xml
@@ -1,18 +1,14 @@
-
+
-
-
-
-
-
+
-
-
-
-
-
+
+
+
+
-
+
\ No newline at end of file
diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/tests___class__TestUser.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/tests___class__TestUser.xml
deleted file mode 100644
index 204de9dd1..000000000
--- a/{{cookiecutter.project_slug}}/.idea/runConfigurations/tests___class__TestUser.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/tests___file__test_models.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/tests___file__test_models.xml
deleted file mode 100644
index ddb6d3d6d..000000000
--- a/{{cookiecutter.project_slug}}/.idea/runConfigurations/tests___file__test_models.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/tests___specific__test_get_absolute_url.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/tests___specific__test_get_absolute_url.xml
deleted file mode 100644
index c391058f2..000000000
--- a/{{cookiecutter.project_slug}}/.idea/runConfigurations/tests___specific__test_get_absolute_url.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/{{cookiecutter.project_slug}}/requirements/local.txt b/{{cookiecutter.project_slug}}/requirements/local.txt
index c67276107..83e8846c8 100644
--- a/{{cookiecutter.project_slug}}/requirements/local.txt
+++ b/{{cookiecutter.project_slug}}/requirements/local.txt
@@ -22,7 +22,6 @@ coverage==4.5.1 # https://github.com/nedbat/coveragepy
# Django
# ------------------------------------------------------------------------------
factory-boy==2.11.1 # https://github.com/FactoryBoy/factory_boy
-django-test-plus==1.0.22 # https://github.com/revsys/django-test-plus
django-debug-toolbar==1.9.1 # https://github.com/jazzband/django-debug-toolbar
django-extensions==2.0.7 # https://github.com/django-extensions/django-extensions
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py
new file mode 100644
index 000000000..aae11d269
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py
@@ -0,0 +1,20 @@
+import pytest
+from django.conf import settings
+from django.test import RequestFactory
+
+from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory
+
+
+@pytest.fixture(autouse=True)
+def media_storage(settings, tmpdir):
+ settings.MEDIA_ROOT = tmpdir.strpath
+
+
+@pytest.fixture
+def user() -> settings.AUTH_USER_MODEL:
+ return UserFactory()
+
+
+@pytest.fixture
+def request_factory() -> RequestFactory:
+ return RequestFactory()
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py
index 5b63593b2..9361d6eca 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py
@@ -1,15 +1,18 @@
-from django.conf import settings
+from typing import Any
+
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
+from django.conf import settings
+from django.http import HttpRequest
class AccountAdapter(DefaultAccountAdapter):
- def is_open_for_signup(self, request):
+ def is_open_for_signup(self, request: HttpRequest):
return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
class SocialAccountAdapter(DefaultSocialAccountAdapter):
- def is_open_for_signup(self, request, sociallogin):
+ def is_open_for_signup(self, request: HttpRequest, sociallogin: Any):
return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py
index 8da8f86a5..cc6efed5f 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py
@@ -1,39 +1,17 @@
-from django import forms
from django.contrib import admin
-from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
-from django.contrib.auth.forms import UserChangeForm, UserCreationForm
-from .models import User
+from django.contrib.auth import admin as auth_admin
+from django.contrib.auth import get_user_model
+from {{ cookiecutter.project_slug }}.users.forms import UserChangeForm, UserCreationForm
-class MyUserChangeForm(UserChangeForm):
-
- class Meta(UserChangeForm.Meta):
- model = User
-
-
-class MyUserCreationForm(UserCreationForm):
-
- error_message = UserCreationForm.error_messages.update(
- {"duplicate_username": "This username has already been taken."}
- )
-
- class Meta(UserCreationForm.Meta):
- model = User
-
- def clean_username(self):
- username = self.cleaned_data["username"]
- try:
- User.objects.get(username=username)
- except User.DoesNotExist:
- return username
-
- raise forms.ValidationError(self.error_messages["duplicate_username"])
+User = get_user_model()
@admin.register(User)
-class MyUserAdmin(AuthUserAdmin):
- form = MyUserChangeForm
- add_form = MyUserCreationForm
- fieldsets = (("User Profile", {"fields": ("name",)}),) + AuthUserAdmin.fieldsets
- list_display = ("username", "name", "is_superuser")
+class UserAdmin(auth_admin.UserAdmin):
+
+ form = UserChangeForm
+ add_form = UserCreationForm
+ fieldsets = (("User", {"fields": ("name",)}),) + auth_admin.UserAdmin.fieldsets
+ list_display = ["username", "name", "is_superuser"]
search_fields = ["name"]
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py
index 32fab76d7..854665fd3 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py
@@ -2,14 +2,11 @@ from django.apps import AppConfig
class UsersAppConfig(AppConfig):
- name = "{{cookiecutter.project_slug}}.users"
+
+ name = "{{ cookiecutter.project_slug }}.users"
verbose_name = "Users"
def ready(self):
- """Override this to put in:
- Users system checks
- Users signal registration
- """
try:
import users.signals # noqa F401
except ImportError:
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py
new file mode 100644
index 000000000..542e23efc
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py
@@ -0,0 +1,30 @@
+from django.contrib.auth import get_user_model, forms
+from django.core.exceptions import ValidationError
+
+User = get_user_model()
+
+
+class UserChangeForm(forms.UserChangeForm):
+
+ class Meta(forms.UserChangeForm.Meta):
+ model = User
+
+
+class UserCreationForm(forms.UserCreationForm):
+
+ error_message = forms.UserCreationForm.error_messages.update(
+ {"duplicate_username": "This username has already been taken."}
+ )
+
+ class Meta(forms.UserCreationForm.Meta):
+ model = User
+
+ def clean_username(self):
+ username = self.cleaned_data["username"]
+
+ try:
+ User.objects.get(username=username)
+ except User.DoesNotExist:
+ return username
+
+ raise ValidationError(self.error_messages["duplicate_username"])
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py
index 304758713..8f07b15a1 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py
@@ -1,5 +1,5 @@
from django.contrib.auth.models import AbstractUser
-from django.db import models
+from django.db.models import CharField
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
@@ -8,10 +8,7 @@ class User(AbstractUser):
# First Name and Last Name do not cover name patterns
# around the globe.
- name = models.CharField(_("Name of User"), blank=True, max_length=255)
-
- def __str__(self):
- return self.username
+ name = CharField(_("Name of User"), blank=True, max_length=255)
def get_absolute_url(self):
return reverse("users:detail", kwargs={"username": self.username})
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py
index 8a871b64d..009905768 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py
@@ -1,11 +1,29 @@
-import factory
+from typing import Any, Sequence
+
+from django.contrib.auth import get_user_model
+from factory import DjangoModelFactory, Faker, post_generation
-class UserFactory(factory.django.DjangoModelFactory):
- username = factory.Sequence(lambda n: f"user-{n}")
- email = factory.Sequence(lambda n: f"user-{n}@example.com")
- password = factory.PostGenerationMethodCall("set_password", "password")
+class UserFactory(DjangoModelFactory):
+
+ username = Faker("user_name")
+ email = Faker("email")
+ name = Faker("name")
+
+ @post_generation
+ def password(self, create: bool, extracted: Sequence[Any], **kwargs):
+ password = Faker(
+ "password",
+ length=42,
+ special_chars=True,
+ digits=True,
+ upper_case=True,
+ lower_case=True,
+ ).generate(
+ extra_kwargs={}
+ )
+ self.set_password(password)
class Meta:
- model = "users.User"
- django_get_or_create = ("username",)
+ model = get_user_model()
+ django_get_or_create = ["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
deleted file mode 100644
index a3307103f..000000000
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from test_plus.test import TestCase
-
-from ..admin import MyUserCreationForm
-
-
-class TestMyUserCreationForm(TestCase):
-
- def setUp(self):
- self.user = self.make_user("notalamode", "notalamodespassword")
-
- def test_clean_username_success(self):
- # Instantiate the form with a new username
- form = MyUserCreationForm(
- {
- "username": "alamode",
- "password1": "7jefB#f@Cc7YJB]2v",
- "password2": "7jefB#f@Cc7YJB]2v",
- }
- )
- # Run is_valid() to trigger the validation
- valid = form.is_valid()
- self.assertTrue(valid)
-
- # Run the actual clean_username method
- username = form.clean_username()
- self.assertEqual("alamode", username)
-
- def test_clean_username_false(self):
- # Instantiate the form with the same username as self.user
- form = MyUserCreationForm(
- {
- "username": self.user.username,
- "password1": "notalamodespassword",
- "password2": "notalamodespassword",
- }
- )
- # Run is_valid() to trigger the validation, which is going to fail
- # because the username is already taken
- valid = form.is_valid()
- self.assertFalse(valid)
-
- # The form.errors dict should contain a single error called 'username'
- self.assertTrue(len(form.errors) == 1)
- self.assertTrue("username" in form.errors)
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
new file mode 100644
index 000000000..e80661648
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py
@@ -0,0 +1,41 @@
+import pytest
+
+from {{ cookiecutter.project_slug }}.users.forms import UserCreationForm
+from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory
+
+pytestmark = pytest.mark.django_db
+
+
+class TestUserCreationForm:
+
+ def test_clean_username(self):
+ # A user with proto_user params does not exist yet.
+ proto_user = UserFactory.build()
+
+ form = UserCreationForm(
+ {
+ "username": proto_user.username,
+ "password1": proto_user._password,
+ "password2": proto_user._password,
+ }
+ )
+
+ assert form.is_valid()
+ assert form.clean_username() == proto_user.username
+
+ # Creating a user.
+ form.save()
+
+ # The user with proto_user params already exists,
+ # hence cannot be created.
+ form = UserCreationForm(
+ {
+ "username": proto_user.username,
+ "password1": proto_user._password,
+ "password2": proto_user._password,
+ }
+ )
+
+ assert not form.is_valid()
+ assert len(form.errors) == 1
+ assert "username" in form.errors
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 13121a011..548636323 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
@@ -1,16 +1,8 @@
-from test_plus.test import TestCase
+import pytest
+from django.conf import settings
+
+pytestmark = pytest.mark.django_db
-class TestUser(TestCase):
-
- def setUp(self):
- self.user = self.make_user()
-
- def test__str__(self):
- self.assertEqual(
- self.user.__str__(),
- "testuser", # This is the default username for self.make_user()
- )
-
- def test_get_absolute_url(self):
- self.assertEqual(self.user.get_absolute_url(), "/users/testuser/")
+def test_user_get_absolute_url(user: settings.AUTH_USER_MODEL):
+ 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 6b072436d..20bd3dbad 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
@@ -1,44 +1,28 @@
+import pytest
+from django.conf import settings
from django.urls import reverse, resolve
-from test_plus.test import TestCase
+pytestmark = pytest.mark.django_db
-class TestUserURLs(TestCase):
- """Test URL patterns for users app."""
+def test_detail(user: settings.AUTH_USER_MODEL):
+ assert (
+ reverse("users:detail", kwargs={"username": user.username})
+ == f"/users/{user.username}/"
+ )
+ assert resolve(f"/users/{user.username}/").view_name == "users:detail"
- def setUp(self):
- self.user = self.make_user()
- def test_list_reverse(self):
- """users:list should reverse to /users/."""
- self.assertEqual(reverse("users:list"), "/users/")
+def test_list():
+ assert reverse("users:list") == "/users/"
+ assert resolve("/users/").view_name == "users:list"
- def test_list_resolve(self):
- """/users/ should resolve to users:list."""
- self.assertEqual(resolve("/users/").view_name, "users:list")
- def test_redirect_reverse(self):
- """users:redirect should reverse to /users/~redirect/."""
- self.assertEqual(reverse("users:redirect"), "/users/~redirect/")
+def test_update():
+ assert reverse("users:update") == "/users/~update/"
+ assert resolve("/users/~update/").view_name == "users:update"
- def test_redirect_resolve(self):
- """/users/~redirect/ should resolve to users:redirect."""
- self.assertEqual(resolve("/users/~redirect/").view_name, "users:redirect")
- def test_detail_reverse(self):
- """users:detail should reverse to /users/testuser/."""
- self.assertEqual(
- reverse("users:detail", kwargs={"username": "testuser"}), "/users/testuser/"
- )
-
- def test_detail_resolve(self):
- """/users/testuser/ should resolve to users:detail."""
- self.assertEqual(resolve("/users/testuser/").view_name, "users:detail")
-
- def test_update_reverse(self):
- """users:update should reverse to /users/~update/."""
- self.assertEqual(reverse("users:update"), "/users/~update/")
-
- def test_update_resolve(self):
- """/users/~update/ should resolve to users:update."""
- self.assertEqual(resolve("/users/~update/").view_name, "users:update")
+def test_redirect():
+ assert reverse("users:redirect") == "/users/~redirect/"
+ assert resolve("/users/~redirect/").view_name == "users:redirect"
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 07cbda669..0992e4626 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
@@ -1,52 +1,53 @@
+import pytest
+from django.conf import settings
from django.test import RequestFactory
-from test_plus.test import TestCase
+from {{ cookiecutter.project_slug }}.users.views import UserRedirectView, UserUpdateView
-from ..views import UserRedirectView, UserUpdateView
+pytestmark = pytest.mark.django_db
-class BaseUserTestCase(TestCase):
+class TestUserUpdateView:
+ """
+ TODO:
+ extracting view initialization code as class-scoped fixture
+ would be great if only pytest-django supported non-function-scoped
+ fixture db access -- this is a work-in-progress for now:
+ https://github.com/pytest-dev/pytest-django/pull/258
+ """
- def setUp(self):
- self.user = self.make_user()
- self.factory = RequestFactory()
+ def test_get_success_url(
+ self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory
+ ):
+ view = UserUpdateView()
+ request = request_factory.get("/fake-url/")
+ request.user = user
-
-class TestUserRedirectView(BaseUserTestCase):
-
- def test_get_redirect_url(self):
- # Instantiate the view directly. Never do this outside a test!
- view = UserRedirectView()
- # Generate a fake request
- request = self.factory.get("/fake-url")
- # Attach the user to the request
- request.user = self.user
- # Attach the request to the view
view.request = request
- # Expect: '/users/testuser/', as that is the default username for
- # self.make_user()
- self.assertEqual(view.get_redirect_url(), "/users/testuser/")
+
+ assert view.get_success_url() == f"/users/{user.username}/"
+
+ def test_get_object(
+ self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory
+ ):
+ view = UserUpdateView()
+ request = request_factory.get("/fake-url/")
+ request.user = user
+
+ view.request = request
+
+ assert view.get_object() == user
-class TestUserUpdateView(BaseUserTestCase):
+class TestUserRedirectView:
- def setUp(self):
- # call BaseUserTestCase.setUp()
- super(TestUserUpdateView, self).setUp()
- # Instantiate the view directly. Never do this outside a test!
- self.view = UserUpdateView()
- # Generate a fake request
- request = self.factory.get("/fake-url")
- # Attach the user to the request
- request.user = self.user
- # Attach the request to the view
- self.view.request = request
+ def test_get_redirect_url(
+ self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory
+ ):
+ view = UserRedirectView()
+ request = request_factory.get("/fake-url")
+ request.user = user
- def test_get_success_url(self):
- # Expect: '/users/testuser/', as that is the default username for
- # self.make_user()
- self.assertEqual(self.view.get_success_url(), "/users/testuser/")
+ view.request = request
- def test_get_object(self):
- # Expect: self.user, as that is the request's user object
- self.assertEqual(self.view.get_object(), self.user)
+ assert view.get_redirect_url() == f"/users/{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 fef93ad58..2502a0c0c 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py
@@ -1,15 +1,16 @@
from django.urls import path
-from . import views
+from {{ cookiecutter.project_slug }}.users.views import (
+ user_list_view,
+ user_redirect_view,
+ user_update_view,
+ user_detail_view,
+)
app_name = "users"
urlpatterns = [
- path("", view=views.UserListView.as_view(), name="list"),
- path("~redirect/", view=views.UserRedirectView.as_view(), name="redirect"),
- path("~update/", view=views.UserUpdateView.as_view(), name="update"),
- path(
- "/",
- view=views.UserDetailView.as_view(),
- name="detail",
- ),
+ path("", view=user_list_view, name="list"),
+ path("~redirect/", view=user_redirect_view, name="redirect"),
+ path("~update/", view=user_update_view, name="update"),
+ path("/", view=user_detail_view, name="detail"),
]
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py
index a9038b71e..35e26e94f 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py
@@ -1,43 +1,52 @@
+from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse
from django.views.generic import DetailView, ListView, RedirectView, UpdateView
-from .models import User
+User = get_user_model()
class UserDetailView(LoginRequiredMixin, DetailView):
+
model = User
- # These next two lines tell the view to index lookups by username
slug_field = "username"
slug_url_kwarg = "username"
+user_detail_view = UserDetailView.as_view()
+
+
+class UserListView(LoginRequiredMixin, ListView):
+
+ model = User
+ slug_field = "username"
+ slug_url_kwarg = "username"
+
+
+user_list_view = UserListView.as_view()
+
+
+class UserUpdateView(LoginRequiredMixin, UpdateView):
+
+ model = User
+ fields = ["name"]
+
+ def get_success_url(self):
+ return reverse("users:detail", kwargs={"username": self.request.user.username})
+
+ def get_object(self):
+ return User.objects.get(username=self.request.user.username)
+
+
+user_update_view = UserUpdateView.as_view()
+
+
class UserRedirectView(LoginRequiredMixin, RedirectView):
+
permanent = False
def get_redirect_url(self):
return reverse("users:detail", kwargs={"username": self.request.user.username})
-class UserUpdateView(LoginRequiredMixin, UpdateView):
-
- fields = ["name"]
-
- # we already imported User in the view code above, remember?
- model = User
-
- # send the user back to their own page after a successful update
-
- def get_success_url(self):
- return reverse("users:detail", kwargs={"username": self.request.user.username})
-
- def get_object(self):
- # Only get the User record for the user making the request
- return User.objects.get(username=self.request.user.username)
-
-
-class UserListView(LoginRequiredMixin, ListView):
- model = User
- # These next two lines tell the view to index lookups by username
- slug_field = "username"
- slug_url_kwarg = "username"
+user_redirect_view = UserRedirectView.as_view()