Re-write users app tests in pytest style & perform minor refactoring (#1680)

This commit is contained in:
Nikita Shupeyko 2018-06-27 19:33:38 +03:00 committed by GitHub
parent 275c13292c
commit 8ad7adb11a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 258 additions and 332 deletions

View File

@ -1,18 +1,14 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="tests - all" type="DjangoTestsConfigurationType" factoryName="Django tests" singleton="true"> <configuration default="false" name="pytest: ." type="tests" factoryName="py.test" singleton="true">
<module name="{{ cookiecutter.project_slug }}" /> <module name="{{ cookiecutter.project_slug }}" />
<option name="INTERPRETER_OPTIONS" value="" /> <option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" /> <option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="DJANGO_SETTINGS_MODULE" value="config.settings.test" />
</envs>
<option name="SDK_HOME" value="" /> <option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" /> <option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" /> <option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" /> <option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" /> <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<PathMappingSettings> <PathMappingSettings>
<option name="pathMappings"> <option name="pathMappings">
<list> <list>
@ -20,11 +16,10 @@
</list> </list>
</option> </option>
</PathMappingSettings> </PathMappingSettings>
<option name="TARGET" value="." /> <option name="_new_keywords" value="&quot;&quot;" />
<option name="SETTINGS_FILE" value="" /> <option name="_new_additionalArguments" value="&quot;&quot;" />
<option name="CUSTOM_SETTINGS" value="false" /> <option name="_new_target" value="&quot;.&quot;" />
<option name="USE_OPTIONS" value="false" /> <option name="_new_targetType" value="&quot;PATH&quot;" />
<option name="OPTIONS" value="" />
<method /> <method />
</configuration> </configuration>
</component> </component>

View File

@ -1,18 +1,14 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="tests - module: users" type="DjangoTestsConfigurationType" factoryName="Django tests" singleton="true"> <configuration default="false" name="pytest: users" type="tests" factoryName="py.test" singleton="true">
<module name="{{ cookiecutter.project_slug }}" /> <module name="{{ cookiecutter.project_slug }}" />
<option name="INTERPRETER_OPTIONS" value="" /> <option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" /> <option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="DJANGO_SETTINGS_MODULE" value="config.settings.test" />
</envs>
<option name="SDK_HOME" value="" /> <option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" /> <option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" /> <option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" /> <option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" /> <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<PathMappingSettings> <PathMappingSettings>
<option name="pathMappings"> <option name="pathMappings">
<list> <list>
@ -20,11 +16,10 @@
</list> </list>
</option> </option>
</PathMappingSettings> </PathMappingSettings>
<option name="TARGET" value="{{ cookiecutter.project_slug }}.users" /> <option name="_new_keywords" value="&quot;&quot;" />
<option name="SETTINGS_FILE" value="" /> <option name="_new_additionalArguments" value="&quot;&quot;" />
<option name="CUSTOM_SETTINGS" value="false" /> <option name="_new_target" value="&quot;./{{ cookiecutter.project_slug }}/users/&quot;" />
<option name="USE_OPTIONS" value="false" /> <option name="_new_targetType" value="&quot;PATH&quot;" />
<option name="OPTIONS" value="" />
<method /> <method />
</configuration> </configuration>
</component> </component>

View File

@ -1,30 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="tests - class: TestUser" type="DjangoTestsConfigurationType" factoryName="Django tests" singleton="true">
<module name="{{ cookiecutter.project_slug }}" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="DJANGO_SETTINGS_MODULE" value="config.settings.test" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
<PathMappingSettings>
<option name="pathMappings">
<list>
<mapping local-root="$PROJECT_DIR$" remote-root="/app" />
</list>
</option>
</PathMappingSettings>
<option name="TARGET" value="{{ cookiecutter.project_slug }}.users.tests.test_models.TestUser" />
<option name="SETTINGS_FILE" value="" />
<option name="CUSTOM_SETTINGS" value="false" />
<option name="USE_OPTIONS" value="false" />
<option name="OPTIONS" value="" />
<method />
</configuration>
</component>

View File

@ -1,30 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="tests - file: test_models" type="DjangoTestsConfigurationType" factoryName="Django tests" singleton="true">
<module name="{{ cookiecutter.project_slug }}" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="DJANGO_SETTINGS_MODULE" value="config.settings.test" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
<PathMappingSettings>
<option name="pathMappings">
<list>
<mapping local-root="$PROJECT_DIR$" remote-root="/app" />
</list>
</option>
</PathMappingSettings>
<option name="TARGET" value="{{ cookiecutter.project_slug }}.users.tests.test_models" />
<option name="SETTINGS_FILE" value="" />
<option name="CUSTOM_SETTINGS" value="false" />
<option name="USE_OPTIONS" value="false" />
<option name="OPTIONS" value="" />
<method />
</configuration>
</component>

View File

@ -1,30 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="tests - specific: test_get_absolute_url" type="DjangoTestsConfigurationType" factoryName="Django tests" singleton="true">
<module name="{{ cookiecutter.project_slug }}" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="DJANGO_SETTINGS_MODULE" value="config.settings.test" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
<PathMappingSettings>
<option name="pathMappings">
<list>
<mapping local-root="$PROJECT_DIR$" remote-root="/app" />
</list>
</option>
</PathMappingSettings>
<option name="TARGET" value="{{ cookiecutter.project_slug }}.users.tests.test_models.TestUser.test_get_absolute_url" />
<option name="SETTINGS_FILE" value="" />
<option name="CUSTOM_SETTINGS" value="false" />
<option name="USE_OPTIONS" value="false" />
<option name="OPTIONS" value="" />
<method />
</configuration>
</component>

View File

@ -22,7 +22,6 @@ coverage==4.5.1 # https://github.com/nedbat/coveragepy
# Django # Django
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
factory-boy==2.11.1 # https://github.com/FactoryBoy/factory_boy factory-boy==2.11.1 # https://github.com/FactoryBoy/factory_boy
django-test-plus==1.1.0 # https://github.com/revsys/django-test-plus
django-debug-toolbar==1.9.1 # https://github.com/jazzband/django-debug-toolbar 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 django-extensions==2.0.7 # https://github.com/django-extensions/django-extensions

View File

@ -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()

View File

@ -1,15 +1,18 @@
from django.conf import settings from typing import Any
from allauth.account.adapter import DefaultAccountAdapter from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from django.conf import settings
from django.http import HttpRequest
class AccountAdapter(DefaultAccountAdapter): 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) return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
class SocialAccountAdapter(DefaultSocialAccountAdapter): 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) return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)

View File

@ -1,40 +1,17 @@
from django import forms
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin from django.contrib.auth import admin as auth_admin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm from django.contrib.auth import get_user_model
from .models import User
from django.utils.translation import ugettext_lazy as _
from {{ cookiecutter.project_slug }}.users.forms import UserChangeForm, UserCreationForm
class MyUserChangeForm(UserChangeForm): User = get_user_model()
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"])
@admin.register(User) @admin.register(User)
class MyUserAdmin(AuthUserAdmin): class UserAdmin(auth_admin.UserAdmin):
form = MyUserChangeForm
add_form = MyUserCreationForm form = UserChangeForm
fieldsets = (("User Profile", {"fields": ("name",)}),) + AuthUserAdmin.fieldsets add_form = UserCreationForm
list_display = ("username", "name", "is_superuser") fieldsets = (("User", {"fields": ("name",)}),) + auth_admin.UserAdmin.fieldsets
list_display = ["username", "name", "is_superuser"]
search_fields = ["name"] search_fields = ["name"]

View File

@ -2,14 +2,11 @@ from django.apps import AppConfig
class UsersAppConfig(AppConfig): class UsersAppConfig(AppConfig):
name = "{{cookiecutter.project_slug}}.users"
name = "{{ cookiecutter.project_slug }}.users"
verbose_name = "Users" verbose_name = "Users"
def ready(self): def ready(self):
"""Override this to put in:
Users system checks
Users signal registration
"""
try: try:
import users.signals # noqa F401 import users.signals # noqa F401
except ImportError: except ImportError:

View File

@ -0,0 +1,31 @@
from django.contrib.auth import get_user_model, forms
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
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"])

View File

@ -1,5 +1,5 @@
from django.contrib.auth.models import AbstractUser 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.urls import reverse
from django.utils.translation import ugettext_lazy as _ 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 # First Name and Last Name do not cover name patterns
# around the globe. # around the globe.
name = models.CharField(_("Name of User"), blank=True, max_length=255) name = CharField(_("Name of User"), blank=True, max_length=255)
def __str__(self):
return self.username
def get_absolute_url(self): def get_absolute_url(self):
return reverse("users:detail", kwargs={"username": self.username}) return reverse("users:detail", kwargs={"username": self.username})

View File

@ -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): class UserFactory(DjangoModelFactory):
username = factory.Sequence(lambda n: f"user-{n}")
email = factory.Sequence(lambda n: f"user-{n}@example.com") username = Faker("user_name")
password = factory.PostGenerationMethodCall("set_password", "password") 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: class Meta:
model = "users.User" model = get_user_model()
django_get_or_create = ("username",) django_get_or_create = ["username"]

View File

@ -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)

View File

@ -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

View File

@ -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 test_user_get_absolute_url(user: settings.AUTH_USER_MODEL):
assert user.get_absolute_url() == f"/users/{user.username}/"
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/")

View File

@ -1,44 +1,28 @@
import pytest
from django.conf import settings
from django.urls import reverse, resolve from django.urls import reverse, resolve
from test_plus.test import TestCase pytestmark = pytest.mark.django_db
class TestUserURLs(TestCase): def test_detail(user: settings.AUTH_USER_MODEL):
"""Test URL patterns for users app.""" 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): def test_list():
"""users:list should reverse to /users/.""" assert reverse("users:list") == "/users/"
self.assertEqual(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): def test_update():
"""users:redirect should reverse to /users/~redirect/.""" assert reverse("users:update") == "/users/~update/"
self.assertEqual(reverse("users:redirect"), "/users/~redirect/") 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): def test_redirect():
"""users:detail should reverse to /users/testuser/.""" assert reverse("users:redirect") == "/users/~redirect/"
self.assertEqual( assert resolve("/users/~redirect/").view_name == "users:redirect"
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")

View File

@ -1,52 +1,53 @@
import pytest
from django.conf import settings
from django.test import RequestFactory 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): def test_get_success_url(
self.user = self.make_user() self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory
self.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 view.request = request
# Expect: '/users/testuser/', as that is the default username for
# self.make_user() assert view.get_success_url() == f"/users/{user.username}/"
self.assertEqual(view.get_redirect_url(), "/users/testuser/")
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): def test_get_redirect_url(
# call BaseUserTestCase.setUp() self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory
super(TestUserUpdateView, self).setUp() ):
# Instantiate the view directly. Never do this outside a test! view = UserRedirectView()
self.view = UserUpdateView() request = request_factory.get("/fake-url")
# Generate a fake request request.user = user
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_success_url(self): view.request = request
# Expect: '/users/testuser/', as that is the default username for
# self.make_user()
self.assertEqual(self.view.get_success_url(), "/users/testuser/")
def test_get_object(self): assert view.get_redirect_url() == f"/users/{user.username}/"
# Expect: self.user, as that is the request's user object
self.assertEqual(self.view.get_object(), self.user)

View File

@ -1,15 +1,16 @@
from django.urls import path 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" app_name = "users"
urlpatterns = [ urlpatterns = [
path("", view=views.UserListView.as_view(), name="list"), path("", view=user_list_view, name="list"),
path("~redirect/", view=views.UserRedirectView.as_view(), name="redirect"), path("~redirect/", view=user_redirect_view, name="redirect"),
path("~update/", view=views.UserUpdateView.as_view(), name="update"), path("~update/", view=user_update_view, name="update"),
path( path("<str:username>/", view=user_detail_view, name="detail"),
"<str:username>/",
view=views.UserDetailView.as_view(),
name="detail",
),
] ]

View File

@ -1,43 +1,52 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse from django.urls import reverse
from django.views.generic import DetailView, ListView, RedirectView, UpdateView from django.views.generic import DetailView, ListView, RedirectView, UpdateView
from .models import User User = get_user_model()
class UserDetailView(LoginRequiredMixin, DetailView): class UserDetailView(LoginRequiredMixin, DetailView):
model = User model = User
# These next two lines tell the view to index lookups by username
slug_field = "username" slug_field = "username"
slug_url_kwarg = "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): class UserRedirectView(LoginRequiredMixin, RedirectView):
permanent = False permanent = False
def get_redirect_url(self): def get_redirect_url(self):
return reverse("users:detail", kwargs={"username": self.request.user.username}) return reverse("users:detail", kwargs={"username": self.request.user.username})
class UserUpdateView(LoginRequiredMixin, UpdateView): user_redirect_view = UserRedirectView.as_view()
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"