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:
parent
eac9ab7ebb
commit
f3fa86a89b
|
@ -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)
|
||||||
|
|
|
@ -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`
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -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()`.
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue