summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorshanghui <shangdahao@gmail.com>2017-11-15 20:27:53 +0800
committerTim Graham <timograham@gmail.com>2018-01-05 14:47:37 -0500
commit3333d935d2914cd80cf31f4803821ad5c0e2a51d (patch)
treeee875fb8615f3e01607345f753cbce1cf4a051a0
parent44c5b239e0f3ec81f8428c30ce784b4ad7e3adec (diff)
Fixed #28757 -- Allowed using contrib.auth forms without installing contrib.auth.
Also fixed #28608 -- Allowed UserCreationForm and UserChangeForm to work with custom user models. Thanks Sagar Chalise and RĂ´mulo Collopy for reports, and Tim Graham and Tim Martin for reviews.
-rw-r--r--django/contrib/auth/forms.py11
-rw-r--r--docs/releases/2.1.txt4
-rw-r--r--docs/topics/auth/customizing.txt32
-rw-r--r--docs/topics/auth/default.txt9
-rw-r--r--tests/auth_tests/test_forms.py140
5 files changed, 136 insertions, 60 deletions
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index e857f95cb2..dfceccb2ec 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -7,7 +7,6 @@ from django.contrib.auth import (
from django.contrib.auth.hashers import (
UNUSABLE_PASSWORD_PREFIX, identify_hasher,
)
-from django.contrib.auth.models import User
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import EmailMultiAlternatives
@@ -83,9 +82,9 @@ class UserCreationForm(forms.ModelForm):
)
class Meta:
- model = User
- fields = ("username",)
- field_classes = {'username': UsernameField}
+ model = UserModel
+ fields = (UserModel.USERNAME_FIELD,)
+ field_classes = {UserModel.USERNAME_FIELD: UsernameField}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -132,9 +131,9 @@ class UserChangeForm(forms.ModelForm):
)
class Meta:
- model = User
+ model = UserModel
fields = '__all__'
- field_classes = {'username': UsernameField}
+ field_classes = {UserModel.USERNAME_FIELD: UsernameField}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
diff --git a/docs/releases/2.1.txt b/docs/releases/2.1.txt
index 3c9fad8ca5..f349d1ed9a 100644
--- a/docs/releases/2.1.txt
+++ b/docs/releases/2.1.txt
@@ -48,6 +48,10 @@ Minor features
* :djadmin:`createsuperuser` now gives a prompt to allow bypassing the
:setting:`AUTH_PASSWORD_VALIDATORS` checks.
+* :class:`~django.contrib.auth.forms.UserCreationForm` and
+ :class:`~django.contrib.auth.forms.UserChangeForm` no longer need to be
+ rewritten for a custom user model.
+
:mod:`django.contrib.contenttypes`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt
index ce518b41be..97fac352ac 100644
--- a/docs/topics/auth/customizing.txt
+++ b/docs/topics/auth/customizing.txt
@@ -820,11 +820,20 @@ are working with.
The following forms are compatible with any subclass of
:class:`~django.contrib.auth.models.AbstractBaseUser`:
-* :class:`~django.contrib.auth.forms.AuthenticationForm`: Uses the username
- field specified by :attr:`~models.CustomUser.USERNAME_FIELD`.
+* :class:`~django.contrib.auth.forms.AuthenticationForm`
* :class:`~django.contrib.auth.forms.SetPasswordForm`
* :class:`~django.contrib.auth.forms.PasswordChangeForm`
* :class:`~django.contrib.auth.forms.AdminPasswordChangeForm`
+* :class:`~django.contrib.auth.forms.UserCreationForm`
+* :class:`~django.contrib.auth.forms.UserChangeForm`
+
+The forms that handle a username use the username field specified by
+:attr:`~models.CustomUser.USERNAME_FIELD`.
+
+.. versionchanged:: 2.1
+
+ In older versions, ``UserCreationForm`` and ``UserChangeForm`` need to be
+ rewritten to work with custom user models.
The following forms make assumptions about the user model and can be used as-is
if those assumptions are met:
@@ -835,25 +844,6 @@ if those assumptions are met:
default) that can be used to identify the user and a boolean field named
``is_active`` to prevent password resets for inactive users.
-Finally, the following forms are tied to
-:class:`~django.contrib.auth.models.User` and need to be rewritten or extended
-to work with a custom user model:
-
-* :class:`~django.contrib.auth.forms.UserCreationForm`
-* :class:`~django.contrib.auth.forms.UserChangeForm`
-
-If your custom user model is a simple subclass of ``AbstractUser``, then you
-can extend these forms in this manner::
-
- from django.contrib.auth.forms import UserCreationForm
- from myapp.models import CustomUser
-
- class CustomUserCreationForm(UserCreationForm):
-
- class Meta(UserCreationForm.Meta):
- model = CustomUser
- fields = UserCreationForm.Meta.fields + ('custom_field',)
-
Custom users and :mod:`django.contrib.admin`
--------------------------------------------
diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt
index a4ba59b02f..3e8a63c25f 100644
--- a/docs/topics/auth/default.txt
+++ b/docs/topics/auth/default.txt
@@ -1486,9 +1486,12 @@ provides several built-in forms located in :mod:`django.contrib.auth.forms`:
A :class:`~django.forms.ModelForm` for creating a new user.
- It has three fields: ``username`` (from the user model), ``password1``,
- and ``password2``. It verifies that ``password1`` and ``password2`` match,
- validates the password using
+ It has three fields: one named after the
+ :attr:`~django.contrib.auth.models.CustomUser.USERNAME_FIELD` from the
+ user model, and ``password1`` and ``password2``.
+
+ It verifies that ``password1`` and ``password2`` match, validates the
+ password using
:func:`~django.contrib.auth.password_validation.validate_password`, and
sets the user's password using
:meth:`~django.contrib.auth.models.User.set_password()`.
diff --git a/tests/auth_tests/test_forms.py b/tests/auth_tests/test_forms.py
index 5d2074e33a..497d385fce 100644
--- a/tests/auth_tests/test_forms.py
+++ b/tests/auth_tests/test_forms.py
@@ -1,7 +1,9 @@
import datetime
import re
+from importlib import reload
from unittest import mock
+import django
from django import forms
from django.contrib.auth.forms import (
AdminPasswordChangeForm, AuthenticationForm, PasswordChangeForm,
@@ -11,7 +13,7 @@ from django.contrib.auth.forms import (
from django.contrib.auth.models import User
from django.contrib.auth.signals import user_login_failed
from django.contrib.sites.models import Site
-from django.core import mail
+from django.core import mail, signals
from django.core.mail import EmailMultiAlternatives
from django.forms.fields import CharField, Field, IntegerField
from django.test import SimpleTestCase, TestCase, override_settings
@@ -27,6 +29,24 @@ from .models.with_integer_username import IntegerUsernameUser
from .settings import AUTH_TEMPLATES
+def reload_auth_forms(sender, setting, value, enter, **kwargs):
+ if setting == 'AUTH_USER_MODEL':
+ reload(django.contrib.auth.forms)
+
+
+class ReloadFormsMixin:
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ signals.setting_changed.connect(reload_auth_forms)
+
+ @classmethod
+ def tearDownClass(cls):
+ signals.setting_changed.disconnect(reload_auth_forms)
+ super().tearDownClass()
+
+
class TestDataMixin:
@classmethod
@@ -37,9 +57,10 @@ class TestDataMixin:
cls.u4 = User.objects.create(username='empty_password', password='')
cls.u5 = User.objects.create(username='unmanageable_password', password='$')
cls.u6 = User.objects.create(username='unknown_password', password='foo$bar')
+ cls.u7 = ExtensionUser.objects.create(username='extension_client', date_of_birth='1998-02-24')
-class UserCreationFormTest(TestDataMixin, TestCase):
+class UserCreationFormTest(ReloadFormsMixin, TestDataMixin, TestCase):
def test_user_already_exists(self):
data = {
@@ -175,19 +196,25 @@ class UserCreationFormTest(TestDataMixin, TestCase):
)
def test_custom_form(self):
- class CustomUserCreationForm(UserCreationForm):
- class Meta(UserCreationForm.Meta):
- model = ExtensionUser
- fields = UserCreationForm.Meta.fields + ('date_of_birth',)
+ with override_settings(AUTH_USER_MODEL='auth_tests.ExtensionUser'):
+ from django.contrib.auth.forms import UserCreationForm
+ self.assertEqual(UserCreationForm.Meta.model, ExtensionUser)
- data = {
- 'username': 'testclient',
- 'password1': 'testclient',
- 'password2': 'testclient',
- 'date_of_birth': '1988-02-24',
- }
- form = CustomUserCreationForm(data)
- self.assertTrue(form.is_valid())
+ class CustomUserCreationForm(UserCreationForm):
+ class Meta(UserCreationForm.Meta):
+ fields = UserCreationForm.Meta.fields + ('date_of_birth',)
+
+ data = {
+ 'username': 'testclient',
+ 'password1': 'testclient',
+ 'password2': 'testclient',
+ 'date_of_birth': '1988-02-24',
+ }
+ form = CustomUserCreationForm(data)
+ self.assertTrue(form.is_valid())
+ # reload_auth_forms() reloads the form.
+ from django.contrib.auth.forms import UserCreationForm
+ self.assertEqual(UserCreationForm.Meta.model, User)
def test_custom_form_with_different_username_field(self):
class CustomUserCreationForm(UserCreationForm):
@@ -261,6 +288,30 @@ class UserCreationFormTest(TestDataMixin, TestCase):
['The password is too similar to the first name.'],
)
+ def test_with_custom_user_model(self):
+ with override_settings(AUTH_USER_MODEL='auth_tests.ExtensionUser'):
+ data = {
+ 'username': 'test_username',
+ 'password1': 'test_password',
+ 'password2': 'test_password',
+ }
+ from django.contrib.auth.forms import UserCreationForm
+ self.assertEqual(UserCreationForm.Meta.model, ExtensionUser)
+ form = UserCreationForm(data)
+ self.assertTrue(form.is_valid())
+
+ def test_customer_user_model_with_different_username_field(self):
+ with override_settings(AUTH_USER_MODEL='auth_tests.CustomUser'):
+ from django.contrib.auth.forms import UserCreationForm
+ self.assertEqual(UserCreationForm.Meta.model, CustomUser)
+ data = {
+ 'email': 'testchange@test.com',
+ 'password1': 'test_password',
+ 'password2': 'test_password',
+ }
+ form = UserCreationForm(data)
+ self.assertTrue(form.is_valid())
+
class AuthenticationFormTest(TestDataMixin, TestCase):
@@ -605,7 +656,7 @@ class PasswordChangeFormTest(TestDataMixin, TestCase):
self.assertEqual(form.cleaned_data['new_password2'], data['new_password2'])
-class UserChangeFormTest(TestDataMixin, TestCase):
+class UserChangeFormTest(ReloadFormsMixin, TestDataMixin, TestCase):
def test_username_validity(self):
user = User.objects.get(username='testclient')
@@ -679,22 +730,51 @@ class UserChangeFormTest(TestDataMixin, TestCase):
self.assertEqual(form.initial['password'], form['password'].value())
def test_custom_form(self):
- class CustomUserChangeForm(UserChangeForm):
- class Meta(UserChangeForm.Meta):
- model = ExtensionUser
- fields = ('username', 'password', 'date_of_birth',)
+ with override_settings(AUTH_USER_MODEL='auth_tests.ExtensionUser'):
+ from django.contrib.auth.forms import UserChangeForm
+ self.assertEqual(UserChangeForm.Meta.model, ExtensionUser)
- user = User.objects.get(username='testclient')
- data = {
- 'username': 'testclient',
- 'password': 'testclient',
- 'date_of_birth': '1998-02-24',
- }
- form = CustomUserChangeForm(data, instance=user)
- self.assertTrue(form.is_valid())
- form.save()
- self.assertEqual(form.cleaned_data['username'], 'testclient')
- self.assertEqual(form.cleaned_data['date_of_birth'], datetime.date(1998, 2, 24))
+ class CustomUserChangeForm(UserChangeForm):
+ class Meta(UserChangeForm.Meta):
+ fields = ('username', 'password', 'date_of_birth')
+
+ data = {
+ 'username': 'testclient',
+ 'password': 'testclient',
+ 'date_of_birth': '1998-02-24',
+ }
+ form = CustomUserChangeForm(data, instance=self.u7)
+ self.assertTrue(form.is_valid())
+ form.save()
+ self.assertEqual(form.cleaned_data['username'], 'testclient')
+ self.assertEqual(form.cleaned_data['date_of_birth'], datetime.date(1998, 2, 24))
+ # reload_auth_forms() reloads the form.
+ from django.contrib.auth.forms import UserChangeForm
+ self.assertEqual(UserChangeForm.Meta.model, User)
+
+ def test_with_custom_user_model(self):
+ with override_settings(AUTH_USER_MODEL='auth_tests.ExtensionUser'):
+ from django.contrib.auth.forms import UserChangeForm
+ self.assertEqual(UserChangeForm.Meta.model, ExtensionUser)
+ data = {
+ 'username': 'testclient',
+ 'date_joined': '1998-02-24',
+ 'date_of_birth': '1998-02-24',
+ }
+ form = UserChangeForm(data, instance=self.u7)
+ self.assertTrue(form.is_valid())
+
+ def test_customer_user_model_with_different_username_field(self):
+ with override_settings(AUTH_USER_MODEL='auth_tests.CustomUser'):
+ from django.contrib.auth.forms import UserChangeForm
+ self.assertEqual(UserChangeForm.Meta.model, CustomUser)
+ user = CustomUser.custom_objects.create(email='test@test.com', date_of_birth='1998-02-24')
+ data = {
+ 'email': 'testchange@test.com',
+ 'date_of_birth': '1998-02-24',
+ }
+ form = UserChangeForm(data, instance=user)
+ self.assertTrue(form.is_valid())
@override_settings(TEMPLATES=AUTH_TEMPLATES)