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:
Hasan Ramezani 2019-03-10 11:03:42 +01:00 committed by Carlton Gibson
parent c498f088c5
commit dcb8f00d06
3 changed files with 73 additions and 11 deletions

View File

@ -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."),
) )

View File

@ -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`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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)