Fixed #29449 -- Reverted "Fixed #28757 -- Allowed using contrib.auth forms without installing contrib.auth."

This reverts commit 3333d935d2 due to
a crash if USERNAME_FIELD isn't a CharField.
This commit is contained in:
Tim Graham 2018-07-02 18:10:36 -04:00
parent eac9ab7ebb
commit f3fa86a89b
4 changed files with 60 additions and 132 deletions

View File

@ -7,6 +7,7 @@ from django.contrib.auth import (
from django.contrib.auth.hashers import ( from django.contrib.auth.hashers import (
UNUSABLE_PASSWORD_PREFIX, identify_hasher, UNUSABLE_PASSWORD_PREFIX, identify_hasher,
) )
from django.contrib.auth.models import User
from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.shortcuts import get_current_site from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
@ -82,9 +83,9 @@ class UserCreationForm(forms.ModelForm):
) )
class Meta: class Meta:
model = UserModel model = User
fields = (UserModel.USERNAME_FIELD,) fields = ("username",)
field_classes = {UserModel.USERNAME_FIELD: UsernameField} field_classes = {'username': UsernameField}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -131,9 +132,9 @@ class UserChangeForm(forms.ModelForm):
) )
class Meta: class Meta:
model = UserModel model = User
fields = '__all__' fields = '__all__'
field_classes = {UserModel.USERNAME_FIELD: UsernameField} field_classes = {'username': UsernameField}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

@ -814,20 +814,11 @@ are working with.
The following forms are compatible with any subclass of The following forms are compatible with any subclass of
:class:`~django.contrib.auth.models.AbstractBaseUser`: :class:`~django.contrib.auth.models.AbstractBaseUser`:
* :class:`~django.contrib.auth.forms.AuthenticationForm` * :class:`~django.contrib.auth.forms.AuthenticationForm`: Uses the username
field specified by :attr:`~models.CustomUser.USERNAME_FIELD`.
* :class:`~django.contrib.auth.forms.SetPasswordForm` * :class:`~django.contrib.auth.forms.SetPasswordForm`
* :class:`~django.contrib.auth.forms.PasswordChangeForm` * :class:`~django.contrib.auth.forms.PasswordChangeForm`
* :class:`~django.contrib.auth.forms.AdminPasswordChangeForm` * :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 The following forms make assumptions about the user model and can be used as-is
if those assumptions are met: if those assumptions are met:
@ -838,6 +829,25 @@ if those assumptions are met:
default) that can be used to identify the user and a boolean field named default) that can be used to identify the user and a boolean field named
``is_active`` to prevent password resets for inactive users. ``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` Custom users and :mod:`django.contrib.admin`
-------------------------------------------- --------------------------------------------

View File

@ -1508,12 +1508,9 @@ provides several built-in forms located in :mod:`django.contrib.auth.forms`:
A :class:`~django.forms.ModelForm` for creating a new user. A :class:`~django.forms.ModelForm` for creating a new user.
It has three fields: one named after the It has three fields: ``username`` (from the user model), ``password1``,
:attr:`~django.contrib.auth.models.CustomUser.USERNAME_FIELD` from the and ``password2``. It verifies that ``password1`` and ``password2`` match,
user model, and ``password1`` and ``password2``. validates the password using
It verifies that ``password1`` and ``password2`` match, validates the
password using
:func:`~django.contrib.auth.password_validation.validate_password`, and :func:`~django.contrib.auth.password_validation.validate_password`, and
sets the user's password using sets the user's password using
:meth:`~django.contrib.auth.models.User.set_password()`. :meth:`~django.contrib.auth.models.User.set_password()`.

View File

@ -1,9 +1,7 @@
import datetime import datetime
import re import re
from importlib import reload
from unittest import mock from unittest import mock
import django
from django import forms from django import forms
from django.contrib.auth.forms import ( from django.contrib.auth.forms import (
AdminPasswordChangeForm, AuthenticationForm, PasswordChangeForm, AdminPasswordChangeForm, AuthenticationForm, PasswordChangeForm,
@ -13,7 +11,7 @@ from django.contrib.auth.forms import (
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.signals import user_login_failed from django.contrib.auth.signals import user_login_failed
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core import mail, signals from django.core import mail
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
from django.forms.fields import CharField, Field, IntegerField from django.forms.fields import CharField, Field, IntegerField
from django.test import SimpleTestCase, TestCase, override_settings from django.test import SimpleTestCase, TestCase, override_settings
@ -29,24 +27,6 @@ from .models.with_integer_username import IntegerUsernameUser
from .settings import AUTH_TEMPLATES 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: class TestDataMixin:
@classmethod @classmethod
@ -57,10 +37,9 @@ class TestDataMixin:
cls.u4 = User.objects.create(username='empty_password', password='') cls.u4 = User.objects.create(username='empty_password', password='')
cls.u5 = User.objects.create(username='unmanageable_password', password='$') cls.u5 = User.objects.create(username='unmanageable_password', password='$')
cls.u6 = User.objects.create(username='unknown_password', password='foo$bar') 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(ReloadFormsMixin, TestDataMixin, TestCase): class UserCreationFormTest(TestDataMixin, TestCase):
def test_user_already_exists(self): def test_user_already_exists(self):
data = { data = {
@ -196,25 +175,19 @@ class UserCreationFormTest(ReloadFormsMixin, TestDataMixin, TestCase):
) )
def test_custom_form(self): def test_custom_form(self):
with override_settings(AUTH_USER_MODEL='auth_tests.ExtensionUser'): class CustomUserCreationForm(UserCreationForm):
from django.contrib.auth.forms import UserCreationForm class Meta(UserCreationForm.Meta):
self.assertEqual(UserCreationForm.Meta.model, ExtensionUser) model = ExtensionUser
fields = UserCreationForm.Meta.fields + ('date_of_birth',)
class CustomUserCreationForm(UserCreationForm): data = {
class Meta(UserCreationForm.Meta): 'username': 'testclient',
fields = UserCreationForm.Meta.fields + ('date_of_birth',) 'password1': 'testclient',
'password2': 'testclient',
data = { 'date_of_birth': '1988-02-24',
'username': 'testclient', }
'password1': 'testclient', form = CustomUserCreationForm(data)
'password2': 'testclient', self.assertTrue(form.is_valid())
'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): def test_custom_form_with_different_username_field(self):
class CustomUserCreationForm(UserCreationForm): class CustomUserCreationForm(UserCreationForm):
@ -288,30 +261,6 @@ class UserCreationFormTest(ReloadFormsMixin, TestDataMixin, TestCase):
['The password is too similar to the first name.'], ['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())
# To verify that the login form rejects inactive users, use an authentication # To verify that the login form rejects inactive users, use an authentication
# backend that allows them. # backend that allows them.
@ -677,7 +626,7 @@ class PasswordChangeFormTest(TestDataMixin, TestCase):
self.assertEqual(form.cleaned_data['new_password2'], data['new_password2']) self.assertEqual(form.cleaned_data['new_password2'], data['new_password2'])
class UserChangeFormTest(ReloadFormsMixin, TestDataMixin, TestCase): class UserChangeFormTest(TestDataMixin, TestCase):
def test_username_validity(self): def test_username_validity(self):
user = User.objects.get(username='testclient') user = User.objects.get(username='testclient')
@ -751,51 +700,22 @@ class UserChangeFormTest(ReloadFormsMixin, TestDataMixin, TestCase):
self.assertEqual(form.initial['password'], form['password'].value()) self.assertEqual(form.initial['password'], form['password'].value())
def test_custom_form(self): def test_custom_form(self):
with override_settings(AUTH_USER_MODEL='auth_tests.ExtensionUser'): class CustomUserChangeForm(UserChangeForm):
from django.contrib.auth.forms import UserChangeForm class Meta(UserChangeForm.Meta):
self.assertEqual(UserChangeForm.Meta.model, ExtensionUser) model = ExtensionUser
fields = ('username', 'password', 'date_of_birth',)
class CustomUserChangeForm(UserChangeForm): user = User.objects.get(username='testclient')
class Meta(UserChangeForm.Meta): data = {
fields = ('username', 'password', 'date_of_birth') 'username': 'testclient',
'password': 'testclient',
data = { 'date_of_birth': '1998-02-24',
'username': 'testclient', }
'password': 'testclient', form = CustomUserChangeForm(data, instance=user)
'date_of_birth': '1998-02-24', self.assertTrue(form.is_valid())
} form.save()
form = CustomUserChangeForm(data, instance=self.u7) self.assertEqual(form.cleaned_data['username'], 'testclient')
self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['date_of_birth'], datetime.date(1998, 2, 24))
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())
def test_password_excluded(self): def test_password_excluded(self):
class UserChangeFormWithoutPassword(UserChangeForm): class UserChangeFormWithoutPassword(UserChangeForm):