mirror of https://github.com/django/django.git
Fixed #29379 -- Added autocomplete attribute to contrib.auth.forms fields.
Thank you to Nick Pope for review. Co-authored-by: CHI Cheng <cloudream@gmail.com>
This commit is contained in:
parent
c498f088c5
commit
dcb8f00d06
|
@ -78,12 +78,12 @@ class UserCreationForm(forms.ModelForm):
|
||||||
password1 = forms.CharField(
|
password1 = forms.CharField(
|
||||||
label=_("Password"),
|
label=_("Password"),
|
||||||
strip=False,
|
strip=False,
|
||||||
widget=forms.PasswordInput,
|
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
|
||||||
help_text=password_validation.password_validators_help_text_html(),
|
help_text=password_validation.password_validators_help_text_html(),
|
||||||
)
|
)
|
||||||
password2 = forms.CharField(
|
password2 = forms.CharField(
|
||||||
label=_("Password confirmation"),
|
label=_("Password confirmation"),
|
||||||
widget=forms.PasswordInput,
|
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
|
||||||
strip=False,
|
strip=False,
|
||||||
help_text=_("Enter the same password as before, for verification."),
|
help_text=_("Enter the same password as before, for verification."),
|
||||||
)
|
)
|
||||||
|
@ -96,7 +96,10 @@ class UserCreationForm(forms.ModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self._meta.model.USERNAME_FIELD in self.fields:
|
if self._meta.model.USERNAME_FIELD in self.fields:
|
||||||
self.fields[self._meta.model.USERNAME_FIELD].widget.attrs.update({'autofocus': True})
|
self.fields[self._meta.model.USERNAME_FIELD].widget.attrs.update({
|
||||||
|
'autocomplete': 'username',
|
||||||
|
'autofocus': True,
|
||||||
|
})
|
||||||
|
|
||||||
def clean_password2(self):
|
def clean_password2(self):
|
||||||
password1 = self.cleaned_data.get("password1")
|
password1 = self.cleaned_data.get("password1")
|
||||||
|
@ -163,11 +166,11 @@ class AuthenticationForm(forms.Form):
|
||||||
Base class for authenticating users. Extend this to get a form that accepts
|
Base class for authenticating users. Extend this to get a form that accepts
|
||||||
username/password logins.
|
username/password logins.
|
||||||
"""
|
"""
|
||||||
username = UsernameField(widget=forms.TextInput(attrs={'autofocus': True}))
|
username = UsernameField(widget=forms.TextInput(attrs={'autocomplete': 'username', 'autofocus': True}))
|
||||||
password = forms.CharField(
|
password = forms.CharField(
|
||||||
label=_("Password"),
|
label=_("Password"),
|
||||||
strip=False,
|
strip=False,
|
||||||
widget=forms.PasswordInput,
|
widget=forms.PasswordInput(attrs={'autocomplete': 'current-password'}),
|
||||||
)
|
)
|
||||||
|
|
||||||
error_messages = {
|
error_messages = {
|
||||||
|
@ -235,7 +238,11 @@ class AuthenticationForm(forms.Form):
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetForm(forms.Form):
|
class PasswordResetForm(forms.Form):
|
||||||
email = forms.EmailField(label=_("Email"), max_length=254)
|
email = forms.EmailField(
|
||||||
|
label=_("Email"),
|
||||||
|
max_length=254,
|
||||||
|
widget=forms.EmailInput(attrs={'autocomplete': 'email'})
|
||||||
|
)
|
||||||
|
|
||||||
def send_mail(self, subject_template_name, email_template_name,
|
def send_mail(self, subject_template_name, email_template_name,
|
||||||
context, from_email, to_email, html_email_template_name=None):
|
context, from_email, to_email, html_email_template_name=None):
|
||||||
|
@ -311,14 +318,14 @@ class SetPasswordForm(forms.Form):
|
||||||
}
|
}
|
||||||
new_password1 = forms.CharField(
|
new_password1 = forms.CharField(
|
||||||
label=_("New password"),
|
label=_("New password"),
|
||||||
widget=forms.PasswordInput,
|
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
|
||||||
strip=False,
|
strip=False,
|
||||||
help_text=password_validation.password_validators_help_text_html(),
|
help_text=password_validation.password_validators_help_text_html(),
|
||||||
)
|
)
|
||||||
new_password2 = forms.CharField(
|
new_password2 = forms.CharField(
|
||||||
label=_("New password confirmation"),
|
label=_("New password confirmation"),
|
||||||
strip=False,
|
strip=False,
|
||||||
widget=forms.PasswordInput,
|
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, user, *args, **kwargs):
|
def __init__(self, user, *args, **kwargs):
|
||||||
|
@ -357,7 +364,7 @@ class PasswordChangeForm(SetPasswordForm):
|
||||||
old_password = forms.CharField(
|
old_password = forms.CharField(
|
||||||
label=_("Old password"),
|
label=_("Old password"),
|
||||||
strip=False,
|
strip=False,
|
||||||
widget=forms.PasswordInput(attrs={'autofocus': True}),
|
widget=forms.PasswordInput(attrs={'autocomplete': 'current-password', 'autofocus': True}),
|
||||||
)
|
)
|
||||||
|
|
||||||
field_order = ['old_password', 'new_password1', 'new_password2']
|
field_order = ['old_password', 'new_password1', 'new_password2']
|
||||||
|
@ -385,13 +392,13 @@ class AdminPasswordChangeForm(forms.Form):
|
||||||
required_css_class = 'required'
|
required_css_class = 'required'
|
||||||
password1 = forms.CharField(
|
password1 = forms.CharField(
|
||||||
label=_("Password"),
|
label=_("Password"),
|
||||||
widget=forms.PasswordInput(attrs={'autofocus': True}),
|
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password', 'autofocus': True}),
|
||||||
strip=False,
|
strip=False,
|
||||||
help_text=password_validation.password_validators_help_text_html(),
|
help_text=password_validation.password_validators_help_text_html(),
|
||||||
)
|
)
|
||||||
password2 = forms.CharField(
|
password2 = forms.CharField(
|
||||||
label=_("Password (again)"),
|
label=_("Password (again)"),
|
||||||
widget=forms.PasswordInput,
|
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
|
||||||
strip=False,
|
strip=False,
|
||||||
help_text=_("Enter the same password as before, for verification."),
|
help_text=_("Enter the same password as before, for verification."),
|
||||||
)
|
)
|
||||||
|
|
|
@ -76,6 +76,10 @@ Minor features
|
||||||
to mirror the existing
|
to mirror the existing
|
||||||
:meth:`~django.contrib.auth.models.User.get_group_permissions()` method.
|
:meth:`~django.contrib.auth.models.User.get_group_permissions()` method.
|
||||||
|
|
||||||
|
* Added HTML ``autocomplete`` attribute to widgets of username, email, and
|
||||||
|
password fields in :mod:`django.contrib.auth.forms` for better interaction
|
||||||
|
with browser password managers.
|
||||||
|
|
||||||
:mod:`django.contrib.contenttypes`
|
:mod:`django.contrib.contenttypes`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -265,6 +265,17 @@ class UserCreationFormTest(TestDataMixin, TestCase):
|
||||||
form = UserCreationForm()
|
form = UserCreationForm()
|
||||||
self.assertEqual(form.fields['username'].widget.attrs.get('autocapitalize'), 'none')
|
self.assertEqual(form.fields['username'].widget.attrs.get('autocapitalize'), 'none')
|
||||||
|
|
||||||
|
def test_html_autocomplete_attributes(self):
|
||||||
|
form = UserCreationForm()
|
||||||
|
tests = (
|
||||||
|
('username', 'username'),
|
||||||
|
('password1', 'new-password'),
|
||||||
|
('password2', 'new-password'),
|
||||||
|
)
|
||||||
|
for field_name, autocomplete in tests:
|
||||||
|
with self.subTest(field_name=field_name, autocomplete=autocomplete):
|
||||||
|
self.assertEqual(form.fields[field_name].widget.attrs['autocomplete'], autocomplete)
|
||||||
|
|
||||||
|
|
||||||
# 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.
|
||||||
|
@ -492,6 +503,16 @@ class AuthenticationFormTest(TestDataMixin, TestCase):
|
||||||
self.assertEqual(error.code, 'invalid_login')
|
self.assertEqual(error.code, 'invalid_login')
|
||||||
self.assertEqual(error.params, {'username': 'username'})
|
self.assertEqual(error.params, {'username': 'username'})
|
||||||
|
|
||||||
|
def test_html_autocomplete_attributes(self):
|
||||||
|
form = AuthenticationForm()
|
||||||
|
tests = (
|
||||||
|
('username', 'username'),
|
||||||
|
('password', 'current-password'),
|
||||||
|
)
|
||||||
|
for field_name, autocomplete in tests:
|
||||||
|
with self.subTest(field_name=field_name, autocomplete=autocomplete):
|
||||||
|
self.assertEqual(form.fields[field_name].widget.attrs['autocomplete'], autocomplete)
|
||||||
|
|
||||||
|
|
||||||
class SetPasswordFormTest(TestDataMixin, TestCase):
|
class SetPasswordFormTest(TestDataMixin, TestCase):
|
||||||
|
|
||||||
|
@ -572,6 +593,16 @@ class SetPasswordFormTest(TestDataMixin, TestCase):
|
||||||
for french_text in french_help_texts:
|
for french_text in french_help_texts:
|
||||||
self.assertIn(french_text, html)
|
self.assertIn(french_text, html)
|
||||||
|
|
||||||
|
def test_html_autocomplete_attributes(self):
|
||||||
|
form = SetPasswordForm(self.u1)
|
||||||
|
tests = (
|
||||||
|
('new_password1', 'new-password'),
|
||||||
|
('new_password2', 'new-password'),
|
||||||
|
)
|
||||||
|
for field_name, autocomplete in tests:
|
||||||
|
with self.subTest(field_name=field_name, autocomplete=autocomplete):
|
||||||
|
self.assertEqual(form.fields[field_name].widget.attrs['autocomplete'], autocomplete)
|
||||||
|
|
||||||
|
|
||||||
class PasswordChangeFormTest(TestDataMixin, TestCase):
|
class PasswordChangeFormTest(TestDataMixin, TestCase):
|
||||||
|
|
||||||
|
@ -633,6 +664,11 @@ class PasswordChangeFormTest(TestDataMixin, TestCase):
|
||||||
self.assertEqual(form.cleaned_data['new_password1'], data['new_password1'])
|
self.assertEqual(form.cleaned_data['new_password1'], data['new_password1'])
|
||||||
self.assertEqual(form.cleaned_data['new_password2'], data['new_password2'])
|
self.assertEqual(form.cleaned_data['new_password2'], data['new_password2'])
|
||||||
|
|
||||||
|
def test_html_autocomplete_attributes(self):
|
||||||
|
user = User.objects.get(username='testclient')
|
||||||
|
form = PasswordChangeForm(user)
|
||||||
|
self.assertEqual(form.fields['old_password'].widget.attrs['autocomplete'], 'current-password')
|
||||||
|
|
||||||
|
|
||||||
class UserChangeFormTest(TestDataMixin, TestCase):
|
class UserChangeFormTest(TestDataMixin, TestCase):
|
||||||
|
|
||||||
|
@ -916,6 +952,10 @@ class PasswordResetFormTest(TestDataMixin, TestCase):
|
||||||
self.assertEqual(len(mail.outbox), 1)
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
self.assertEqual(mail.outbox[0].to, [email])
|
self.assertEqual(mail.outbox[0].to, [email])
|
||||||
|
|
||||||
|
def test_html_autocomplete_attributes(self):
|
||||||
|
form = PasswordResetForm()
|
||||||
|
self.assertEqual(form.fields['email'].widget.attrs['autocomplete'], 'email')
|
||||||
|
|
||||||
|
|
||||||
class ReadOnlyPasswordHashTest(SimpleTestCase):
|
class ReadOnlyPasswordHashTest(SimpleTestCase):
|
||||||
|
|
||||||
|
@ -997,3 +1037,14 @@ class AdminPasswordChangeFormTest(TestDataMixin, TestCase):
|
||||||
form2 = AdminPasswordChangeForm(user, {'password1': 'test', 'password2': ''})
|
form2 = AdminPasswordChangeForm(user, {'password1': 'test', 'password2': ''})
|
||||||
self.assertEqual(form2.errors['password2'], required_error)
|
self.assertEqual(form2.errors['password2'], required_error)
|
||||||
self.assertNotIn('password1', form2.errors)
|
self.assertNotIn('password1', form2.errors)
|
||||||
|
|
||||||
|
def test_html_autocomplete_attributes(self):
|
||||||
|
user = User.objects.get(username='testclient')
|
||||||
|
form = AdminPasswordChangeForm(user)
|
||||||
|
tests = (
|
||||||
|
('password1', 'new-password'),
|
||||||
|
('password2', 'new-password'),
|
||||||
|
)
|
||||||
|
for field_name, autocomplete in tests:
|
||||||
|
with self.subTest(field_name=field_name, autocomplete=autocomplete):
|
||||||
|
self.assertEqual(form.fields[field_name].widget.attrs['autocomplete'], autocomplete)
|
||||||
|
|
Loading…
Reference in New Issue