commit 585445ffe1b35fc05255cc504afde4f115469caa
Author: Jonas Haag <jonas@lophus.org>
Date: Sat Dec 18 14:25:32 2010 +0100
Make password reset possible with username or e-mail, not only e-mail.
diff --git a/django/contrib/admin/templates/registration/password_reset_form.html b/django/contrib/admin/templates/registration/password_reset_form.html
index d3a1284..ec7502f 100644
a
|
b
|
|
9 | 9 | |
10 | 10 | <h1>{% trans "Password reset" %}</h1> |
11 | 11 | |
12 | | <p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll e-mail instructions for setting a new one." %}</p> |
| 12 | <p>{% trans "Forgotten your password? Enter your username or e-mail address below, and we'll e-mail instructions for setting a new one." %}</p> |
13 | 13 | |
14 | 14 | <form action="" method="post">{% csrf_token %} |
15 | 15 | {{ form.email.errors }} |
16 | | <p><label for="id_email">{% trans 'E-mail address:' %}</label> {{ form.email }} <input type="submit" value="{% trans 'Reset my password' %}" /></p> |
| 16 | <p><label for="id_email_or_username">{% trans 'E-mail address:' %}</label> {{ form.email }} <input type="submit" value="{% trans 'Reset my password' %}" /></p> |
17 | 17 | </form> |
18 | 18 | |
19 | 19 | {% endblock %} |
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index 7473279..658eee3 100644
a
|
b
|
|
| 1 | import re |
| 2 | from django.core.validators import validate_email |
1 | 3 | from django.contrib.auth.models import User |
2 | 4 | from django.contrib.auth import authenticate |
3 | 5 | from django.contrib.auth.tokens import default_token_generator |
… |
… |
from django import forms
|
7 | 9 | from django.utils.translation import ugettext_lazy as _ |
8 | 10 | from django.utils.http import int_to_base36 |
9 | 11 | |
| 12 | USERNAME_RE = re.compile(r'^[\w.@+-]+$') |
| 13 | |
| 14 | def guess_is_email(string): |
| 15 | if not USERNAME_RE.match(string): |
| 16 | # can't be a username |
| 17 | return True |
| 18 | try: |
| 19 | validate_email(string) |
| 20 | return True |
| 21 | except forms.ValidationError: |
| 22 | return False |
| 23 | |
10 | 24 | class UserCreationForm(forms.ModelForm): |
11 | 25 | """ |
12 | 26 | A form that creates a user, with no privileges, from the given username and password. |
13 | 27 | """ |
14 | | username = forms.RegexField(label=_("Username"), max_length=30, regex=r'^[\w.@+-]+$', |
| 28 | username = forms.RegexField(label=_("Username"), max_length=30, regex=USERNAME_RE, |
15 | 29 | help_text = _("Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only."), |
16 | 30 | error_messages = {'invalid': _("This value may contain only letters, numbers and @/./+/-/_ characters.")}) |
17 | 31 | password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput) |
… |
… |
class AuthenticationForm(forms.Form):
|
105 | 119 | return self.user_cache |
106 | 120 | |
107 | 121 | class PasswordResetForm(forms.Form): |
108 | | email = forms.EmailField(label=_("E-mail"), max_length=75) |
| 122 | email_or_username = forms.CharField(label=_("E-mail or username"), max_length=75) |
109 | 123 | |
110 | | def clean_email(self): |
| 124 | def clean_email_or_username(self): |
111 | 125 | """ |
112 | | Validates that a user exists with the given e-mail address. |
| 126 | Validates that a user exists with the given e-mail address or username. |
113 | 127 | """ |
114 | | email = self.cleaned_data["email"] |
115 | | self.users_cache = User.objects.filter(email__iexact=email) |
| 128 | identifier = self.cleaned_data["email_or_username"] |
| 129 | if guess_is_email(identifier): |
| 130 | self.users_cache = User.objects.filter(email__iexact=identifier) |
| 131 | else: |
| 132 | self.users_cache = User.objects.filter(username=identifier) |
116 | 133 | if len(self.users_cache) == 0: |
117 | | raise forms.ValidationError(_("That e-mail address doesn't have an associated user account. Are you sure you've registered?")) |
118 | | return email |
| 134 | raise forms.ValidationError(_("Unknown username or e-mail. Are you sure you've registered?")) |
| 135 | return identifier |
119 | 136 | |
120 | 137 | def save(self, domain_override=None, email_template_name='registration/password_reset_email.html', |
121 | 138 | use_https=False, token_generator=default_token_generator, from_email=None, request=None): |
diff --git a/django/contrib/auth/tests/forms.py b/django/contrib/auth/tests/forms.py
index 5aa49e0..09db43b 100644
a
|
b
|
class PasswordResetFormTest(TestCase):
|
219 | 219 | |
220 | 220 | fixtures = ['authtestdata.json'] |
221 | 221 | |
222 | | def test_invalid_email(self): |
223 | | data = {'email':'not valid'} |
224 | | form = PasswordResetForm(data) |
225 | | self.assertFalse(form.is_valid()) |
226 | | self.assertEqual(form['email'].errors, |
227 | | [u'Enter a valid e-mail address.']) |
228 | | |
229 | 222 | def test_nonexistant_email(self): |
230 | 223 | # Test nonexistant email address |
231 | | data = {'email':'foo@bar.com'} |
| 224 | data = {'email_or_username':'foo@bar.com'} |
232 | 225 | form = PasswordResetForm(data) |
233 | 226 | self.assertFalse(form.is_valid()) |
234 | 227 | self.assertEqual(form.errors, |
235 | | {'email': [u"That e-mail address doesn't have an associated user account. Are you sure you've registered?"]}) |
| 228 | {'email_or_username': [u"Unknown username or e-mail. Are you sure you've registered?"]}) |
236 | 229 | |
237 | 230 | def test_cleaned_data(self): |
238 | 231 | # Regression test |
239 | 232 | user = User.objects.create_user("jsmith3", "jsmith3@example.com", "test123") |
240 | | data = {'email':'jsmith3@example.com'} |
| 233 | data = {'email_or_username':'jsmith3@example.com'} |
241 | 234 | form = PasswordResetForm(data) |
242 | 235 | self.assertTrue(form.is_valid()) |
243 | | self.assertEqual(form.cleaned_data['email'], u'jsmith3@example.com') |
| 236 | self.assertEqual(form.cleaned_data['email_or_username'], u'jsmith3@example.com') |
244 | 237 | |
245 | 238 | |
246 | 239 | def test_bug_5605(self): |
diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py
index 014c819..ebe4283 100644
a
|
b
|
class AuthViewsTestCase(TestCase):
|
47 | 47 | class PasswordResetTest(AuthViewsTestCase): |
48 | 48 | |
49 | 49 | def test_email_not_found(self): |
50 | | "Error is raised if the provided email address isn't currently registered" |
| 50 | "Error is raised if the provided username/email address isn't currently registered" |
51 | 51 | response = self.client.get('/password_reset/') |
52 | 52 | self.assertEquals(response.status_code, 200) |
53 | | response = self.client.post('/password_reset/', {'email': 'not_a_real_email@email.com'}) |
54 | | self.assertContains(response, "That e-mail address doesn't have an associated user account") |
55 | | self.assertEquals(len(mail.outbox), 0) |
| 53 | for identifier in ['not_a_real_username', 'not_a_real_email@email.com']: |
| 54 | response = self.client.post('/password_reset/', {'email_or_username': identifier}) |
| 55 | self.assertContains(response, "Unknown username or e-mail.") |
| 56 | self.assertEquals(len(mail.outbox), 0) |
56 | 57 | |
57 | 58 | def test_email_found(self): |
58 | | "Email is sent if a valid email address is provided for password reset" |
59 | | response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'}) |
60 | | self.assertEquals(response.status_code, 302) |
61 | | self.assertEquals(len(mail.outbox), 1) |
62 | | self.assert_("http://" in mail.outbox[0].body) |
63 | | self.assertEquals(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email) |
| 59 | "Email is sent if a valid username/email address is provided for password reset" |
| 60 | for i, identifier in enumerate(['staff', 'staffmember@example.com'], 1): |
| 61 | response = self.client.post('/password_reset/', {'email_or_username' : identifier}) |
| 62 | self.assertEquals(response.status_code, 302) |
| 63 | self.assertEquals(len(mail.outbox), i) |
| 64 | self.assert_("http://" in mail.outbox[0].body) |
| 65 | self.assertEquals(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email) |
64 | 66 | |
65 | 67 | def test_email_found_custom_from(self): |
66 | | "Email is sent if a valid email address is provided for password reset when a custom from_email is provided." |
67 | | response = self.client.post('/password_reset_from_email/', {'email': 'staffmember@example.com'}) |
68 | | self.assertEquals(response.status_code, 302) |
69 | | self.assertEquals(len(mail.outbox), 1) |
70 | | self.assertEquals("staffmember@example.com", mail.outbox[0].from_email) |
| 68 | "Email is sent if a valid username/email address is provided for password reset when a custom from_email is provided." |
| 69 | for i, identifier in enumerate(['staff', 'staffmember@example.com'], 1): |
| 70 | response = self.client.post('/password_reset_from_email/', {'email_or_username': identifier}) |
| 71 | self.assertEquals(response.status_code, 302) |
| 72 | self.assertEquals(len(mail.outbox), i) |
| 73 | self.assertEquals("staffmember@example.com", mail.outbox[0].from_email) |
71 | 74 | |
72 | 75 | def _test_confirm_start(self): |
73 | 76 | # Start by creating the email |
74 | | response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'}) |
| 77 | response = self.client.post('/password_reset/', {'email_or_username': 'staffmember@example.com'}) |
75 | 78 | self.assertEquals(response.status_code, 302) |
76 | 79 | self.assertEquals(len(mail.outbox), 1) |
77 | 80 | return self._read_signup_email(mail.outbox[0]) |