mirror of
https://github.com/django/django.git
synced 2025-05-29 18:26:29 +00:00
Fixed #25617 -- Added case-insensitive unique username validation in UserCreationForm.
Co-Authored-By: Neven Mundar <nmundar@gmail.com>
This commit is contained in:
parent
1833eb3f3e
commit
298d02a77a
@ -81,7 +81,7 @@ class UsernameField(forms.CharField):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class UserCreationForm(forms.ModelForm):
|
class BaseUserCreationForm(forms.ModelForm):
|
||||||
"""
|
"""
|
||||||
A form that creates a user, with no privileges, from the given username and
|
A form that creates a user, with no privileges, from the given username and
|
||||||
password.
|
password.
|
||||||
@ -146,6 +146,21 @@ class UserCreationForm(forms.ModelForm):
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreationForm(BaseUserCreationForm):
|
||||||
|
error_messages = {
|
||||||
|
**BaseUserCreationForm.error_messages,
|
||||||
|
"unique": _("A user with that username already exists."),
|
||||||
|
}
|
||||||
|
|
||||||
|
def clean_username(self):
|
||||||
|
"""Reject usernames that differ only in case."""
|
||||||
|
username = self.cleaned_data.get("username")
|
||||||
|
if username and User.objects.filter(username__iexact=username).exists():
|
||||||
|
raise forms.ValidationError(self.error_messages["unique"], code="unique")
|
||||||
|
else:
|
||||||
|
return username
|
||||||
|
|
||||||
|
|
||||||
class UserChangeForm(forms.ModelForm):
|
class UserChangeForm(forms.ModelForm):
|
||||||
password = ReadOnlyPasswordHashField(
|
password = ReadOnlyPasswordHashField(
|
||||||
label=_("Password"),
|
label=_("Password"),
|
||||||
|
@ -127,6 +127,9 @@ Minor features
|
|||||||
* :class:`~django.contrib.auth.forms.UserCreationForm` now saves many-to-many
|
* :class:`~django.contrib.auth.forms.UserCreationForm` now saves many-to-many
|
||||||
form fields for a custom user model.
|
form fields for a custom user model.
|
||||||
|
|
||||||
|
* The new :class:`~django.contrib.auth.forms.BaseUserCreationForm` is now the
|
||||||
|
recommended base class for customizing the user creation form.
|
||||||
|
|
||||||
:mod:`django.contrib.contenttypes`
|
:mod:`django.contrib.contenttypes`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -484,6 +487,10 @@ Miscellaneous
|
|||||||
* The minimum supported version of ``asgiref`` is increased from 3.5.2 to
|
* The minimum supported version of ``asgiref`` is increased from 3.5.2 to
|
||||||
3.6.0.
|
3.6.0.
|
||||||
|
|
||||||
|
* :class:`~django.contrib.auth.forms.UserCreationForm` now rejects usernames
|
||||||
|
that differ only in case. If you need the previous behavior, use
|
||||||
|
:class:`~django.contrib.auth.forms.BaseUserCreationForm` instead.
|
||||||
|
|
||||||
.. _deprecated-features-4.2:
|
.. _deprecated-features-4.2:
|
||||||
|
|
||||||
Features deprecated in 4.2
|
Features deprecated in 4.2
|
||||||
|
@ -1654,9 +1654,12 @@ provides several built-in forms located in :mod:`django.contrib.auth.forms`:
|
|||||||
A form used in the admin interface to change a user's information and
|
A form used in the admin interface to change a user's information and
|
||||||
permissions.
|
permissions.
|
||||||
|
|
||||||
.. class:: UserCreationForm
|
.. class:: BaseUserCreationForm
|
||||||
|
|
||||||
A :class:`~django.forms.ModelForm` for creating a new user.
|
.. versionadded:: 4.2
|
||||||
|
|
||||||
|
A :class:`~django.forms.ModelForm` for creating a new user. This is the
|
||||||
|
recommended base class if you need to customize the user creation form.
|
||||||
|
|
||||||
It has three fields: ``username`` (from the user model), ``password1``,
|
It has three fields: ``username`` (from the user model), ``password1``,
|
||||||
and ``password2``. It verifies that ``password1`` and ``password2`` match,
|
and ``password2``. It verifies that ``password1`` and ``password2`` match,
|
||||||
@ -1665,11 +1668,19 @@ provides several built-in forms located in :mod:`django.contrib.auth.forms`:
|
|||||||
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()`.
|
||||||
|
|
||||||
|
.. class:: UserCreationForm
|
||||||
|
|
||||||
|
Inherits from :class:`BaseUserCreationForm`. To help prevent confusion with
|
||||||
|
similar usernames, the form doesn't allow usernames that differ only in
|
||||||
|
case.
|
||||||
|
|
||||||
.. versionchanged:: 4.2
|
.. versionchanged:: 4.2
|
||||||
|
|
||||||
In older versions, :class:`UserCreationForm` didn't save many-to-many
|
In older versions, :class:`UserCreationForm` didn't save many-to-many
|
||||||
form fields for a custom user model.
|
form fields for a custom user model.
|
||||||
|
|
||||||
|
In older versions, usernames that differ only in case are allowed.
|
||||||
|
|
||||||
.. currentmodule:: django.contrib.auth
|
.. currentmodule:: django.contrib.auth
|
||||||
|
|
||||||
Authentication data in templates
|
Authentication data in templates
|
||||||
|
@ -6,6 +6,7 @@ from unittest import mock
|
|||||||
from django.contrib.auth.forms import (
|
from django.contrib.auth.forms import (
|
||||||
AdminPasswordChangeForm,
|
AdminPasswordChangeForm,
|
||||||
AuthenticationForm,
|
AuthenticationForm,
|
||||||
|
BaseUserCreationForm,
|
||||||
PasswordChangeForm,
|
PasswordChangeForm,
|
||||||
PasswordResetForm,
|
PasswordResetForm,
|
||||||
ReadOnlyPasswordHashField,
|
ReadOnlyPasswordHashField,
|
||||||
@ -54,14 +55,14 @@ class TestDataMixin:
|
|||||||
cls.u6 = User.objects.create(username="unknown_password", password="foo$bar")
|
cls.u6 = User.objects.create(username="unknown_password", password="foo$bar")
|
||||||
|
|
||||||
|
|
||||||
class UserCreationFormTest(TestDataMixin, TestCase):
|
class BaseUserCreationFormTest(TestDataMixin, TestCase):
|
||||||
def test_user_already_exists(self):
|
def test_user_already_exists(self):
|
||||||
data = {
|
data = {
|
||||||
"username": "testclient",
|
"username": "testclient",
|
||||||
"password1": "test123",
|
"password1": "test123",
|
||||||
"password2": "test123",
|
"password2": "test123",
|
||||||
}
|
}
|
||||||
form = UserCreationForm(data)
|
form = BaseUserCreationForm(data)
|
||||||
self.assertFalse(form.is_valid())
|
self.assertFalse(form.is_valid())
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
form["username"].errors,
|
form["username"].errors,
|
||||||
@ -74,7 +75,7 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
"password1": "test123",
|
"password1": "test123",
|
||||||
"password2": "test123",
|
"password2": "test123",
|
||||||
}
|
}
|
||||||
form = UserCreationForm(data)
|
form = BaseUserCreationForm(data)
|
||||||
self.assertFalse(form.is_valid())
|
self.assertFalse(form.is_valid())
|
||||||
validator = next(
|
validator = next(
|
||||||
v
|
v
|
||||||
@ -90,7 +91,7 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
"password1": "test123",
|
"password1": "test123",
|
||||||
"password2": "test",
|
"password2": "test",
|
||||||
}
|
}
|
||||||
form = UserCreationForm(data)
|
form = BaseUserCreationForm(data)
|
||||||
self.assertFalse(form.is_valid())
|
self.assertFalse(form.is_valid())
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
form["password2"].errors, [str(form.error_messages["password_mismatch"])]
|
form["password2"].errors, [str(form.error_messages["password_mismatch"])]
|
||||||
@ -99,7 +100,7 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
def test_both_passwords(self):
|
def test_both_passwords(self):
|
||||||
# One (or both) passwords weren't given
|
# One (or both) passwords weren't given
|
||||||
data = {"username": "jsmith"}
|
data = {"username": "jsmith"}
|
||||||
form = UserCreationForm(data)
|
form = BaseUserCreationForm(data)
|
||||||
required_error = [str(Field.default_error_messages["required"])]
|
required_error = [str(Field.default_error_messages["required"])]
|
||||||
self.assertFalse(form.is_valid())
|
self.assertFalse(form.is_valid())
|
||||||
self.assertEqual(form["password1"].errors, required_error)
|
self.assertEqual(form["password1"].errors, required_error)
|
||||||
@ -119,7 +120,7 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
"password1": "test123",
|
"password1": "test123",
|
||||||
"password2": "test123",
|
"password2": "test123",
|
||||||
}
|
}
|
||||||
form = UserCreationForm(data)
|
form = BaseUserCreationForm(data)
|
||||||
self.assertTrue(form.is_valid())
|
self.assertTrue(form.is_valid())
|
||||||
form.save(commit=False)
|
form.save(commit=False)
|
||||||
self.assertEqual(password_changed.call_count, 0)
|
self.assertEqual(password_changed.call_count, 0)
|
||||||
@ -133,7 +134,7 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
"password1": "test123",
|
"password1": "test123",
|
||||||
"password2": "test123",
|
"password2": "test123",
|
||||||
}
|
}
|
||||||
form = UserCreationForm(data)
|
form = BaseUserCreationForm(data)
|
||||||
self.assertTrue(form.is_valid())
|
self.assertTrue(form.is_valid())
|
||||||
u = form.save()
|
u = form.save()
|
||||||
self.assertEqual(u.username, "宝")
|
self.assertEqual(u.username, "宝")
|
||||||
@ -147,7 +148,7 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
"password1": "pwd2",
|
"password1": "pwd2",
|
||||||
"password2": "pwd2",
|
"password2": "pwd2",
|
||||||
}
|
}
|
||||||
form = UserCreationForm(data)
|
form = BaseUserCreationForm(data)
|
||||||
self.assertTrue(form.is_valid())
|
self.assertTrue(form.is_valid())
|
||||||
user = form.save()
|
user = form.save()
|
||||||
self.assertNotEqual(user.username, ohm_username)
|
self.assertNotEqual(user.username, ohm_username)
|
||||||
@ -168,7 +169,7 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
"password1": "pwd2",
|
"password1": "pwd2",
|
||||||
"password2": "pwd2",
|
"password2": "pwd2",
|
||||||
}
|
}
|
||||||
form = UserCreationForm(data)
|
form = BaseUserCreationForm(data)
|
||||||
self.assertFalse(form.is_valid())
|
self.assertFalse(form.is_valid())
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
form.errors["username"], ["A user with that username already exists."]
|
form.errors["username"], ["A user with that username already exists."]
|
||||||
@ -198,7 +199,7 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
"password1": "testclient",
|
"password1": "testclient",
|
||||||
"password2": "testclient",
|
"password2": "testclient",
|
||||||
}
|
}
|
||||||
form = UserCreationForm(data)
|
form = BaseUserCreationForm(data)
|
||||||
self.assertFalse(form.is_valid())
|
self.assertFalse(form.is_valid())
|
||||||
self.assertEqual(len(form["password2"].errors), 2)
|
self.assertEqual(len(form["password2"].errors), 2)
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
@ -210,8 +211,8 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_custom_form(self):
|
def test_custom_form(self):
|
||||||
class CustomUserCreationForm(UserCreationForm):
|
class CustomUserCreationForm(BaseUserCreationForm):
|
||||||
class Meta(UserCreationForm.Meta):
|
class Meta(BaseUserCreationForm.Meta):
|
||||||
model = ExtensionUser
|
model = ExtensionUser
|
||||||
fields = UserCreationForm.Meta.fields + ("date_of_birth",)
|
fields = UserCreationForm.Meta.fields + ("date_of_birth",)
|
||||||
|
|
||||||
@ -225,8 +226,8 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
self.assertTrue(form.is_valid())
|
self.assertTrue(form.is_valid())
|
||||||
|
|
||||||
def test_custom_form_with_different_username_field(self):
|
def test_custom_form_with_different_username_field(self):
|
||||||
class CustomUserCreationForm(UserCreationForm):
|
class CustomUserCreationForm(BaseUserCreationForm):
|
||||||
class Meta(UserCreationForm.Meta):
|
class Meta(BaseUserCreationForm.Meta):
|
||||||
model = CustomUser
|
model = CustomUser
|
||||||
fields = ("email", "date_of_birth")
|
fields = ("email", "date_of_birth")
|
||||||
|
|
||||||
@ -240,8 +241,8 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
self.assertTrue(form.is_valid())
|
self.assertTrue(form.is_valid())
|
||||||
|
|
||||||
def test_custom_form_hidden_username_field(self):
|
def test_custom_form_hidden_username_field(self):
|
||||||
class CustomUserCreationForm(UserCreationForm):
|
class CustomUserCreationForm(BaseUserCreationForm):
|
||||||
class Meta(UserCreationForm.Meta):
|
class Meta(BaseUserCreationForm.Meta):
|
||||||
model = CustomUserWithoutIsActiveField
|
model = CustomUserWithoutIsActiveField
|
||||||
fields = ("email",) # without USERNAME_FIELD
|
fields = ("email",) # without USERNAME_FIELD
|
||||||
|
|
||||||
@ -254,8 +255,8 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
self.assertTrue(form.is_valid())
|
self.assertTrue(form.is_valid())
|
||||||
|
|
||||||
def test_custom_form_saves_many_to_many_field(self):
|
def test_custom_form_saves_many_to_many_field(self):
|
||||||
class CustomUserCreationForm(UserCreationForm):
|
class CustomUserCreationForm(BaseUserCreationForm):
|
||||||
class Meta(UserCreationForm.Meta):
|
class Meta(BaseUserCreationForm.Meta):
|
||||||
model = CustomUserWithM2M
|
model = CustomUserWithM2M
|
||||||
fields = UserCreationForm.Meta.fields + ("orgs",)
|
fields = UserCreationForm.Meta.fields + ("orgs",)
|
||||||
|
|
||||||
@ -278,7 +279,7 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
"password1": " testpassword ",
|
"password1": " testpassword ",
|
||||||
"password2": " testpassword ",
|
"password2": " testpassword ",
|
||||||
}
|
}
|
||||||
form = UserCreationForm(data)
|
form = BaseUserCreationForm(data)
|
||||||
self.assertTrue(form.is_valid())
|
self.assertTrue(form.is_valid())
|
||||||
self.assertEqual(form.cleaned_data["password1"], data["password1"])
|
self.assertEqual(form.cleaned_data["password1"], data["password1"])
|
||||||
self.assertEqual(form.cleaned_data["password2"], data["password2"])
|
self.assertEqual(form.cleaned_data["password2"], data["password2"])
|
||||||
@ -294,7 +295,7 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_password_help_text(self):
|
def test_password_help_text(self):
|
||||||
form = UserCreationForm()
|
form = BaseUserCreationForm()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
form.fields["password1"].help_text,
|
form.fields["password1"].help_text,
|
||||||
"<ul><li>"
|
"<ul><li>"
|
||||||
@ -313,10 +314,12 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_user_create_form_validates_password_with_all_data(self):
|
def test_user_create_form_validates_password_with_all_data(self):
|
||||||
"""UserCreationForm password validation uses all of the form's data."""
|
"""
|
||||||
|
BaseUserCreationForm password validation uses all of the form's data.
|
||||||
|
"""
|
||||||
|
|
||||||
class CustomUserCreationForm(UserCreationForm):
|
class CustomUserCreationForm(BaseUserCreationForm):
|
||||||
class Meta(UserCreationForm.Meta):
|
class Meta(BaseUserCreationForm.Meta):
|
||||||
model = User
|
model = User
|
||||||
fields = ("username", "email", "first_name", "last_name")
|
fields = ("username", "email", "first_name", "last_name")
|
||||||
|
|
||||||
@ -336,13 +339,13 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_username_field_autocapitalize_none(self):
|
def test_username_field_autocapitalize_none(self):
|
||||||
form = UserCreationForm()
|
form = BaseUserCreationForm()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
form.fields["username"].widget.attrs.get("autocapitalize"), "none"
|
form.fields["username"].widget.attrs.get("autocapitalize"), "none"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_html_autocomplete_attributes(self):
|
def test_html_autocomplete_attributes(self):
|
||||||
form = UserCreationForm()
|
form = BaseUserCreationForm()
|
||||||
tests = (
|
tests = (
|
||||||
("username", "username"),
|
("username", "username"),
|
||||||
("password1", "new-password"),
|
("password1", "new-password"),
|
||||||
@ -355,6 +358,21 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreationFormTest(TestDataMixin, TestCase):
|
||||||
|
def test_case_insensitive_username(self):
|
||||||
|
data = {
|
||||||
|
"username": "TeStClIeNt",
|
||||||
|
"password1": "test123",
|
||||||
|
"password2": "test123",
|
||||||
|
}
|
||||||
|
form = UserCreationForm(data)
|
||||||
|
self.assertFalse(form.is_valid())
|
||||||
|
self.assertEqual(
|
||||||
|
form["username"].errors,
|
||||||
|
["A user with that username already exists."],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# 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.
|
||||||
@override_settings(
|
@override_settings(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user