diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index aa33640..908b4e5 100644
a
|
b
|
|
1 | | from django.contrib.auth.models import User |
| 1 | from django.contrib.auth.models import User, UNUSABLE_PASSWORD |
2 | 2 | from django.contrib.auth import authenticate |
3 | 3 | from django.contrib.auth.tokens import default_token_generator |
4 | 4 | from django.contrib.sites.models import get_current_site |
… |
… |
from django.template import Context, loader
|
6 | 6 | from django import forms |
7 | 7 | from django.utils.translation import ugettext_lazy as _ |
8 | 8 | from django.utils.http import int_to_base36 |
| 9 | from django.utils.itercompat import any |
9 | 10 | |
10 | 11 | class UserCreationForm(forms.ModelForm): |
11 | 12 | """ |
… |
… |
class PasswordResetForm(forms.Form):
|
112 | 113 | """ |
113 | 114 | email = self.cleaned_data["email"] |
114 | 115 | self.users_cache = User.objects.filter(email__iexact=email) |
115 | | if len(self.users_cache) == 0: |
| 116 | if not len(self.users_cache): |
116 | 117 | raise forms.ValidationError(_("That e-mail address doesn't have an associated user account. Are you sure you've registered?")) |
| 118 | if any((user.password == UNUSABLE_PASSWORD) for user in self.users_cache): |
| 119 | raise forms.ValidationError(_("The user account associated with this email address cannot reset it's password.")) |
117 | 120 | return email |
118 | 121 | |
119 | 122 | def save(self, domain_override=None, email_template_name='registration/password_reset_email.html', |
diff --git a/django/contrib/auth/tests/forms.py b/django/contrib/auth/tests/forms.py
index 5aa49e0..7fd4846 100644
a
|
b
|
class PasswordResetFormTest(TestCase):
|
250 | 250 | self.assertEqual(user.email, 'tesT@example.com') |
251 | 251 | user = User.objects.create_user('forms_test3', 'tesT', 'test') |
252 | 252 | self.assertEqual(user.email, 'tesT') |
| 253 | |
| 254 | def test_unusable_password(self): |
| 255 | user = User.objects.create_user('testuser', 'test@example.com', 'test') |
| 256 | data = {"email": "test@example.com"} |
| 257 | form = PasswordResetForm(data) |
| 258 | self.assertTrue(form.is_valid()) |
| 259 | user.set_unusable_password() |
| 260 | user.save() |
| 261 | form = PasswordResetForm(data) |
| 262 | self.assertFalse(form.is_valid()) |
| 263 | self.assertEqual(form["email"].errors, |
| 264 | [u"The user account associated with this email address cannot reset it's password."]) |
diff --git a/django/utils/itercompat.py b/django/utils/itercompat.py
index ab27c3e..4e2a4d8 100644
a
|
b
|
these implementations if necessary.
|
6 | 6 | |
7 | 7 | import itertools |
8 | 8 | |
| 9 | __all__ = [ |
| 10 | 'all', |
| 11 | 'any', |
| 12 | 'is_iterable', |
| 13 | 'product', |
| 14 | ] |
| 15 | |
9 | 16 | # Fallback for Python 2.4, Python 2.5 |
10 | | def product(*args, **kwds): |
| 17 | def _product(*args, **kwds): |
11 | 18 | """ |
12 | 19 | Taken from http://docs.python.org/library/itertools.html#itertools.product |
13 | 20 | """ |
… |
… |
def product(*args, **kwds):
|
19 | 26 | result = [x+[y] for x in result for y in pool] |
20 | 27 | for prod in result: |
21 | 28 | yield tuple(prod) |
22 | | |
23 | | if hasattr(itertools, 'product'): |
24 | | product = itertools.product |
| 29 | product = getattr(itertools, 'product', _product) |
25 | 30 | |
26 | 31 | def is_iterable(x): |
27 | 32 | "A implementation independent way of checking for iterables" |
… |
… |
def is_iterable(x):
|
32 | 37 | else: |
33 | 38 | return True |
34 | 39 | |
35 | | def all(iterable): |
| 40 | def _all(iterable): |
| 41 | """ |
| 42 | Taken from http://docs.python.org/library/functions.html#all |
| 43 | """ |
36 | 44 | for item in iterable: |
37 | 45 | if not item: |
38 | 46 | return False |
39 | 47 | return True |
| 48 | all = getattr(__builtins__, "all", _all) |
| 49 | |
| 50 | def _any(iterable): |
| 51 | """ |
| 52 | Taken from http://docs.python.org/library/functions.html#any |
| 53 | """ |
| 54 | for element in iterable: |
| 55 | if element: |
| 56 | return True |
| 57 | return False |
| 58 | any = getattr(__builtins__, "any", _any) |
diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt
index f45c61a..365778f 100644
a
|
b
|
includes a few other useful built-in views located in
|
945 | 945 | |
946 | 946 | * ``form``: The form for resetting the user's password. |
947 | 947 | |
| 948 | .. note:: |
| 949 | |
| 950 | Users flagged with an unusable password (see |
| 951 | :meth:`~django.contrib.auth.models.User.set_unusable_password()` |
| 952 | will not be able to request a password reset. This is done to |
| 953 | prevent a password reset when using an external authentication |
| 954 | source like LDAP. |
| 955 | |
948 | 956 | .. function:: views.password_reset_done(request[, template_name]) |
949 | 957 | |
950 | 958 | The page shown after a user has reset their password. |