mirror of
https://github.com/cookiecutter/cookiecutter-django.git
synced 2024-11-10 19:57:09 +03:00
Re-write users app tests in pytest style & perform minor refactoring (#1680)
This commit is contained in:
parent
275c13292c
commit
8ad7adb11a
|
@ -1,18 +1,14 @@
|
|||
<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 }}" />
|
||||
<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" />
|
||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||
<PathMappingSettings>
|
||||
<option name="pathMappings">
|
||||
<list>
|
||||
|
@ -20,11 +16,10 @@
|
|||
</list>
|
||||
</option>
|
||||
</PathMappingSettings>
|
||||
<option name="TARGET" value="." />
|
||||
<option name="SETTINGS_FILE" value="" />
|
||||
<option name="CUSTOM_SETTINGS" value="false" />
|
||||
<option name="USE_OPTIONS" value="false" />
|
||||
<option name="OPTIONS" value="" />
|
||||
<option name="_new_keywords" value="""" />
|
||||
<option name="_new_additionalArguments" value="""" />
|
||||
<option name="_new_target" value=""."" />
|
||||
<option name="_new_targetType" value=""PATH"" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
</component>
|
|
@ -1,18 +1,14 @@
|
|||
<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 }}" />
|
||||
<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" />
|
||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||
<PathMappingSettings>
|
||||
<option name="pathMappings">
|
||||
<list>
|
||||
|
@ -20,11 +16,10 @@
|
|||
</list>
|
||||
</option>
|
||||
</PathMappingSettings>
|
||||
<option name="TARGET" value="{{ cookiecutter.project_slug }}.users" />
|
||||
<option name="SETTINGS_FILE" value="" />
|
||||
<option name="CUSTOM_SETTINGS" value="false" />
|
||||
<option name="USE_OPTIONS" value="false" />
|
||||
<option name="OPTIONS" value="" />
|
||||
<option name="_new_keywords" value="""" />
|
||||
<option name="_new_additionalArguments" value="""" />
|
||||
<option name="_new_target" value=""./{{ cookiecutter.project_slug }}/users/"" />
|
||||
<option name="_new_targetType" value=""PATH"" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
</component>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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.1.0 # 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
|
||||
|
|
|
@ -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()
|
|
@ -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)
|
||||
|
|
|
@ -1,40 +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.utils.translation import ugettext_lazy as _
|
||||
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"]
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"])
|
|
@ -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})
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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}/"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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}/"
|
||||
|
|
|
@ -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(
|
||||
"<str:username>/",
|
||||
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("<str:username>/", view=user_detail_view, name="detail"),
|
||||
]
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue
Block a user