diff options
Diffstat (limited to 'django/contrib/auth')
| -rw-r--r-- | django/contrib/auth/admin.py | 2 | ||||
| -rw-r--r-- | django/contrib/auth/decorators.py | 4 | ||||
| -rw-r--r-- | django/contrib/auth/forms.py | 103 | ||||
| -rw-r--r-- | django/contrib/auth/management/commands/createsuperuser.py | 2 | ||||
| -rw-r--r-- | django/contrib/auth/models.py | 3 | ||||
| -rw-r--r-- | django/contrib/auth/tests/__init__.py | 5 | ||||
| -rw-r--r-- | django/contrib/auth/tests/basic.py | 21 | ||||
| -rw-r--r-- | django/contrib/auth/tests/forms.py | 33 | ||||
| -rw-r--r-- | django/contrib/auth/tests/tokens.py | 29 | ||||
| -rw-r--r-- | django/contrib/auth/tests/views.py | 88 | ||||
| -rw-r--r-- | django/contrib/auth/tokens.py | 66 | ||||
| -rw-r--r-- | django/contrib/auth/urls.py | 11 | ||||
| -rw-r--r-- | django/contrib/auth/views.py | 64 |
13 files changed, 344 insertions, 87 deletions
diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py index 998692a6cb..f97935d599 100644 --- a/django/contrib/auth/admin.py +++ b/django/contrib/auth/admin.py @@ -1,6 +1,6 @@ from django.contrib.auth.models import User, Group from django.core.exceptions import PermissionDenied -from django import oldforms, template +from django import template from django.shortcuts import render_to_response from django.http import HttpResponseRedirect from django.utils.translation import ugettext, ugettext_lazy as _ diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py index 25bc20780e..1371c62eea 100644 --- a/django/contrib/auth/decorators.py +++ b/django/contrib/auth/decorators.py @@ -1,7 +1,7 @@ try: - from functools import wraps, update_wrapper + from functools import update_wrapper except ImportError: - from django.utils.functional import wraps, update_wrapper # Python 2.3, 2.4 fallback. + from django.utils.functional import update_wrapper # Python 2.3, 2.4 fallback. from django.contrib.auth import REDIRECT_FIELD_NAME from django.http import HttpResponseRedirect diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 13ddcd3841..13647865d4 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -1,10 +1,11 @@ from django.contrib.auth.models import User from django.contrib.auth import authenticate +from django.contrib.auth.tokens import default_token_generator from django.contrib.sites.models import Site from django.template import Context, loader -from django.core import validators from django import forms from django.utils.translation import ugettext_lazy as _ +from django.utils.http import int_to_base36 class UserCreationForm(forms.ModelForm): """ @@ -13,13 +14,13 @@ class UserCreationForm(forms.ModelForm): username = forms.RegexField(label=_("Username"), max_length=30, regex=r'^\w+$', help_text = _("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."), error_message = _("This value must contain only letters, numbers and underscores.")) - password1 = forms.CharField(label=_("Password"), max_length=60, widget=forms.PasswordInput) - password2 = forms.CharField(label=_("Password confirmation"), max_length=60, widget=forms.PasswordInput) - + password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput) + password2 = forms.CharField(label=_("Password confirmation"), widget=forms.PasswordInput) + class Meta: model = User fields = ("username",) - + def clean_username(self): username = self.cleaned_data["username"] try: @@ -27,14 +28,14 @@ class UserCreationForm(forms.ModelForm): except User.DoesNotExist: return username raise forms.ValidationError(_("A user with that username already exists.")) - + def clean_password2(self): password1 = self.cleaned_data["password1"] password2 = self.cleaned_data["password2"] if password1 != password2: raise forms.ValidationError(_("The two password fields didn't match.")) return password2 - + def save(self, commit=True): user = super(UserCreationForm, self).save(commit=False) user.set_password(self.cleaned_data["password1"]) @@ -48,8 +49,8 @@ class AuthenticationForm(forms.Form): username/password logins. """ username = forms.CharField(label=_("Username"), max_length=30) - password = forms.CharField(label=_("Password"), max_length=30, widget=forms.PasswordInput) - + password = forms.CharField(label=_("Password"), widget=forms.PasswordInput) + def __init__(self, request=None, *args, **kwargs): """ If request is passed in, the form will validate that cookies are @@ -60,36 +61,36 @@ class AuthenticationForm(forms.Form): self.request = request self.user_cache = None super(AuthenticationForm, self).__init__(*args, **kwargs) - + def clean(self): username = self.cleaned_data.get('username') password = self.cleaned_data.get('password') - + if username and password: self.user_cache = authenticate(username=username, password=password) if self.user_cache is None: raise forms.ValidationError(_("Please enter a correct username and password. Note that both fields are case-sensitive.")) elif not self.user_cache.is_active: raise forms.ValidationError(_("This account is inactive.")) - + # TODO: determine whether this should move to its own method. if self.request: if not self.request.session.test_cookie_worked(): raise forms.ValidationError(_("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.")) - + return self.cleaned_data - + def get_user_id(self): if self.user_cache: return self.user_cache.id return None - + def get_user(self): return self.user_cache class PasswordResetForm(forms.Form): - email = forms.EmailField(label=_("E-mail"), max_length=40) - + email = forms.EmailField(label=_("E-mail"), max_length=75) + def clean_email(self): """ Validates that a user exists with the given e-mail address. @@ -98,16 +99,14 @@ class PasswordResetForm(forms.Form): self.users_cache = User.objects.filter(email__iexact=email) if len(self.users_cache) == 0: raise forms.ValidationError(_("That e-mail address doesn't have an associated user account. Are you sure you've registered?")) - - def save(self, domain_override=None, email_template_name='registration/password_reset_email.html'): + + def save(self, domain_override=None, email_template_name='registration/password_reset_email.html', + use_https=False, token_generator=default_token_generator): """ - Calculates a new password randomly and sends it to the user. + Generates a one-use only link for resetting password and sends to the user """ from django.core.mail import send_mail for user in self.users_cache: - new_pass = User.objects.make_random_password() - user.set_password(new_pass) - user.save() if not domain_override: current_site = Site.objects.get_current() site_name = current_site.name @@ -116,36 +115,29 @@ class PasswordResetForm(forms.Form): site_name = domain = domain_override t = loader.get_template(email_template_name) c = { - 'new_password': new_pass, 'email': user.email, 'domain': domain, 'site_name': site_name, + 'uid': int_to_base36(user.id), 'user': user, + 'token': token_generator.make_token(user), + 'protocol': use_https and 'https' or 'http', } send_mail(_("Password reset on %s") % site_name, t.render(Context(c)), None, [user.email]) -class PasswordChangeForm(forms.Form): +class SetPasswordForm(forms.Form): """ - A form that lets a user change his/her password. + A form that lets a user change set his/her password without + entering the old password """ - old_password = forms.CharField(label=_("Old password"), max_length=30, widget=forms.PasswordInput) - new_password1 = forms.CharField(label=_("New password"), max_length=30, widget=forms.PasswordInput) - new_password2 = forms.CharField(label=_("New password confirmation"), max_length=30, widget=forms.PasswordInput) - + new_password1 = forms.CharField(label=_("New password"), widget=forms.PasswordInput) + new_password2 = forms.CharField(label=_("New password confirmation"), widget=forms.PasswordInput) + def __init__(self, user, *args, **kwargs): self.user = user - super(PasswordChangeForm, self).__init__(*args, **kwargs) - - def clean_old_password(self): - """ - Validates that the old_password field is correct. - """ - old_password = self.cleaned_data["old_password"] - if not self.user.check_password(old_password): - raise forms.ValidationError(_("Your old password was entered incorrectly. Please enter it again.")) - return old_password - + super(SetPasswordForm, self).__init__(*args, **kwargs) + def clean_new_password2(self): password1 = self.cleaned_data.get('new_password1') password2 = self.cleaned_data.get('new_password2') @@ -153,24 +145,41 @@ class PasswordChangeForm(forms.Form): if password1 != password2: raise forms.ValidationError(_("The two password fields didn't match.")) return password2 - + def save(self, commit=True): self.user.set_password(self.cleaned_data['new_password1']) if commit: self.user.save() return self.user +class PasswordChangeForm(SetPasswordForm): + """ + A form that lets a user change his/her password by entering + their old password. + """ + old_password = forms.CharField(label=_("Old password"), widget=forms.PasswordInput) + + def clean_old_password(self): + """ + Validates that the old_password field is correct. + """ + old_password = self.cleaned_data["old_password"] + if not self.user.check_password(old_password): + raise forms.ValidationError(_("Your old password was entered incorrectly. Please enter it again.")) + return old_password +PasswordChangeForm.base_fields.keyOrder = ['old_password', 'new_password1', 'new_password2'] + class AdminPasswordChangeForm(forms.Form): """ A form used to change the password of a user in the admin interface. """ - password1 = forms.CharField(label=_("Password"), max_length=60, widget=forms.PasswordInput) - password2 = forms.CharField(label=_("Password (again)"), max_length=60, widget=forms.PasswordInput) - + password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput) + password2 = forms.CharField(label=_("Password (again)"), widget=forms.PasswordInput) + def __init__(self, user, *args, **kwargs): self.user = user super(AdminPasswordChangeForm, self).__init__(*args, **kwargs) - + def clean_password2(self): password1 = self.cleaned_data.get('password1') password2 = self.cleaned_data.get('password2') @@ -178,7 +187,7 @@ class AdminPasswordChangeForm(forms.Form): if password1 != password2: raise forms.ValidationError(_("The two password fields didn't match.")) return password2 - + def save(self, commit=True): """ Saves the new password. diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py index 4299762c74..91e39f7235 100644 --- a/django/contrib/auth/management/commands/createsuperuser.py +++ b/django/contrib/auth/management/commands/createsuperuser.py @@ -7,7 +7,7 @@ import os import re import sys from optparse import make_option -from django.contrib.auth.models import User, UNUSABLE_PASSWORD +from django.contrib.auth.models import User from django.core import validators from django.core.management.base import BaseCommand, CommandError diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index a0ed4f366f..3ea184a7f8 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -358,6 +358,9 @@ class AnonymousUser(object): def has_perm(self, perm): return False + def has_perms(self, perm_list): + return False + def has_module_perms(self, module): return False diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py index 6242303f46..2458800b22 100644 --- a/django/contrib/auth/tests/__init__.py +++ b/django/contrib/auth/tests/__init__.py @@ -1,8 +1,11 @@ -from django.contrib.auth.tests.basic import BASIC_TESTS, PasswordResetTest +from django.contrib.auth.tests.basic import BASIC_TESTS +from django.contrib.auth.tests.views import PasswordResetTest from django.contrib.auth.tests.forms import FORM_TESTS +from django.contrib.auth.tests.tokens import TOKEN_GENERATOR_TESTS __test__ = { 'BASIC_TESTS': BASIC_TESTS, 'PASSWORDRESET_TESTS': PasswordResetTest, 'FORM_TESTS': FORM_TESTS, + 'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS } diff --git a/django/contrib/auth/tests/basic.py b/django/contrib/auth/tests/basic.py index 76dbdc9cb9..2071710279 100644 --- a/django/contrib/auth/tests/basic.py +++ b/django/contrib/auth/tests/basic.py @@ -54,24 +54,3 @@ u'joe@somewhere.org' >>> u.password u'!' """ - -from django.test import TestCase -from django.core import mail - -class PasswordResetTest(TestCase): - fixtures = ['authtestdata.json'] - urls = 'django.contrib.auth.urls' - - def test_email_not_found(self): - "Error is raised if the provided email address isn't currently registered" - response = self.client.get('/password_reset/') - self.assertEquals(response.status_code, 200) - response = self.client.post('/password_reset/', {'email': 'not_a_real_email@email.com'}) - self.assertContains(response, "That e-mail address doesn't have an associated user account") - self.assertEquals(len(mail.outbox), 0) - - def test_email_found(self): - "Email is sent if a valid email address is provided for password reset" - response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'}) - self.assertEquals(response.status_code, 302) - self.assertEquals(len(mail.outbox), 1) diff --git a/django/contrib/auth/tests/forms.py b/django/contrib/auth/tests/forms.py index 1e1e0a95d4..01f4995bb7 100644 --- a/django/contrib/auth/tests/forms.py +++ b/django/contrib/auth/tests/forms.py @@ -2,7 +2,7 @@ FORM_TESTS = """ >>> from django.contrib.auth.models import User >>> from django.contrib.auth.forms import UserCreationForm, AuthenticationForm ->>> from django.contrib.auth.forms import PasswordChangeForm +>>> from django.contrib.auth.forms import PasswordChangeForm, SetPasswordForm The user already exists. @@ -95,6 +95,32 @@ True >>> form.non_field_errors() [] +SetPasswordForm: + +The two new passwords do not match. + +>>> data = { +... 'new_password1': 'abc123', +... 'new_password2': 'abc', +... } +>>> form = SetPasswordForm(user, data) +>>> form.is_valid() +False +>>> form["new_password2"].errors +[u"The two password fields didn't match."] + +The success case. + +>>> data = { +... 'new_password1': 'abc123', +... 'new_password2': 'abc123', +... } +>>> form = SetPasswordForm(user, data) +>>> form.is_valid() +True + +PasswordChangeForm: + The old password is incorrect. >>> data = { @@ -132,4 +158,9 @@ The success case. >>> form.is_valid() True +Regression test - check the order of fields: + +>>> PasswordChangeForm(user, {}).fields.keys() +['old_password', 'new_password1', 'new_password2'] + """ diff --git a/django/contrib/auth/tests/tokens.py b/django/contrib/auth/tests/tokens.py new file mode 100644 index 0000000000..6d3a964fe7 --- /dev/null +++ b/django/contrib/auth/tests/tokens.py @@ -0,0 +1,29 @@ +TOKEN_GENERATOR_TESTS = """ +>>> from django.contrib.auth.models import User, AnonymousUser +>>> from django.contrib.auth.tokens import PasswordResetTokenGenerator +>>> from django.conf import settings +>>> u = User.objects.create_user('tokentestuser', 'test2@example.com', 'testpw') +>>> p0 = PasswordResetTokenGenerator() +>>> tk1 = p0.make_token(u) +>>> p0.check_token(u, tk1) +True + +Tests to ensure we can use the token after n days, but no greater. +Use a mocked version of PasswordResetTokenGenerator so we can change +the value of 'today' + +>>> class Mocked(PasswordResetTokenGenerator): +... def __init__(self, today): +... self._today_val = today +... def _today(self): +... return self._today_val + +>>> from datetime import date, timedelta +>>> p1 = Mocked(date.today() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS)) +>>> p1.check_token(u, tk1) +True +>>> p2 = Mocked(date.today() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS + 1)) +>>> p2.check_token(u, tk1) +False + +""" diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py new file mode 100644 index 0000000000..9abdc3baaf --- /dev/null +++ b/django/contrib/auth/tests/views.py @@ -0,0 +1,88 @@ + +import re +from django.contrib.auth.models import User +from django.test import TestCase +from django.core import mail + +class PasswordResetTest(TestCase): + fixtures = ['authtestdata.json'] + urls = 'django.contrib.auth.urls' + + def test_email_not_found(self): + "Error is raised if the provided email address isn't currently registered" + response = self.client.get('/password_reset/') + self.assertEquals(response.status_code, 200) + response = self.client.post('/password_reset/', {'email': 'not_a_real_email@email.com'}) + self.assertContains(response, "That e-mail address doesn't have an associated user account") + self.assertEquals(len(mail.outbox), 0) + + def test_email_found(self): + "Email is sent if a valid email address is provided for password reset" + response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'}) + self.assertEquals(response.status_code, 302) + self.assertEquals(len(mail.outbox), 1) + self.assert_("http://" in mail.outbox[0].body) + + def _test_confirm_start(self): + # Start by creating the email + response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'}) + self.assertEquals(response.status_code, 302) + self.assertEquals(len(mail.outbox), 1) + return self._read_signup_email(mail.outbox[0]) + + def _read_signup_email(self, email): + urlmatch = re.search(r"https?://[^/]*(/.*reset/\S*)", email.body) + self.assert_(urlmatch is not None, "No URL found in sent email") + return urlmatch.group(), urlmatch.groups()[0] + + def test_confirm_valid(self): + url, path = self._test_confirm_start() + response = self.client.get(path) + # redirect to a 'complete' page: + self.assertEquals(response.status_code, 200) + self.assert_("Please enter your new password" in response.content) + + def test_confirm_invalid(self): + url, path = self._test_confirm_start() + # Lets munge the token in the path, but keep the same length, + # in case the URL conf will reject a different length + path = path[:-5] + ("0"*4) + path[-1] + + response = self.client.get(path) + self.assertEquals(response.status_code, 200) + self.assert_("The password reset link was invalid" in response.content) + + def test_confirm_invalid_post(self): + # Same as test_confirm_invalid, but trying + # to do a POST instead. + url, path = self._test_confirm_start() + path = path[:-5] + ("0"*4) + path[-1] + + response = self.client.post(path, {'new_password1': 'anewpassword', + 'new_password2':' anewpassword'}) + # Check the password has not been changed + u = User.objects.get(email='staffmember@example.com') + self.assert_(not u.check_password("anewpassword")) + + def test_confirm_complete(self): + url, path = self._test_confirm_start() + response = self.client.post(path, {'new_password1': 'anewpassword', + 'new_password2': 'anewpassword'}) + # It redirects us to a 'complete' page: + self.assertEquals(response.status_code, 302) + # Check the password has been changed + u = User.objects.get(email='staffmember@example.com') + self.assert_(u.check_password("anewpassword")) + + # Check we can't use the link again + response = self.client.get(path) + self.assertEquals(response.status_code, 200) + self.assert_("The password reset link was invalid" in response.content) + + def test_confirm_different_passwords(self): + url, path = self._test_confirm_start() + response = self.client.post(path, {'new_password1': 'anewpassword', + 'new_password2':' x'}) + self.assertEquals(response.status_code, 200) + self.assert_("The two password fields didn't match" in response.content) + diff --git a/django/contrib/auth/tokens.py b/django/contrib/auth/tokens.py new file mode 100644 index 0000000000..c9b353583c --- /dev/null +++ b/django/contrib/auth/tokens.py @@ -0,0 +1,66 @@ +from datetime import date +from django.conf import settings +from django.utils.http import int_to_base36, base36_to_int + +class PasswordResetTokenGenerator(object): + """ + Stratgy object used to generate and check tokens for the password + reset mechanism. + """ + def make_token(self, user): + """ + Returns a token that can be used once to do a password reset + for the given user. + """ + return self._make_token_with_timestamp(user, self._num_days(self._today())) + + def check_token(self, user, token): + """ + Check that a password reset token is correct for a given user. + """ + # Parse the tokem + try: + ts_b36, hash = token.split("-") + except ValueError: + return False + + try: + ts = base36_to_int(ts_b36) + except ValueError: + return False + + # Check that the timestamp/uid has not been tampered with + if self._make_token_with_timestamp(user, ts) != token: + return False + + # Check the timestamp is within limit + if (self._num_days(self._today()) - ts) > settings.PASSWORD_RESET_TIMEOUT_DAYS: + return False + + return True + + def _make_token_with_timestamp(self, user, timestamp): + # timestamp is number of days since 2001-1-1. Converted to + # base 36, this gives us a 3 digit string until about 2121 + ts_b36 = int_to_base36(timestamp) + + # By hashing on the internal state of the user and using state + # that is sure to change (the password salt will change as soon as + # the password is set, at least for current Django auth, and + # last_login will also change), we produce a hash that will be + # invalid as soon as it is used. + # We limit the hash to 20 chars to keep URL short + from django.utils.hashcompat import sha_constructor + hash = sha_constructor(settings.SECRET_KEY + unicode(user.id) + + user.password + unicode(user.last_login) + + unicode(timestamp)).hexdigest()[::2] + return "%s-%s" % (ts_b36, hash) + + def _num_days(self, dt): + return (dt - date(2001,1,1)).days + + def _today(self): + # Used for mocking in tests + return date.today() + +default_token_generator = PasswordResetTokenGenerator() diff --git a/django/contrib/auth/urls.py b/django/contrib/auth/urls.py index 5ddfcf15e8..72052c3795 100644 --- a/django/contrib/auth/urls.py +++ b/django/contrib/auth/urls.py @@ -5,9 +5,12 @@ from django.conf.urls.defaults import * urlpatterns = patterns('', - ('^logout/$', 'django.contrib.auth.views.logout'), - ('^password_change/$', 'django.contrib.auth.views.password_change'), - ('^password_change/done/$', 'django.contrib.auth.views.password_change_done'), - ('^password_reset/$', 'django.contrib.auth.views.password_reset') + (r'^logout/$', 'django.contrib.auth.views.logout'), + (r'^password_change/$', 'django.contrib.auth.views.password_change'), + (r'^password_change/done/$', 'django.contrib.auth.views.password_change_done'), + (r'^password_reset/$', 'django.contrib.auth.views.password_reset'), + (r'^password_reset/done/$', 'django.contrib.auth.views.password_reset_done'), + (r'^reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', 'django.contrib.auth.views.password_reset_confirm'), + (r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete'), ) diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 0a52240631..e503f87caa 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -1,13 +1,15 @@ +from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth.decorators import login_required from django.contrib.auth.forms import AuthenticationForm -from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm, AdminPasswordChangeForm +from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm, PasswordChangeForm, AdminPasswordChangeForm +from django.contrib.auth.tokens import default_token_generator from django.core.exceptions import PermissionDenied from django.shortcuts import render_to_response, get_object_or_404 from django.contrib.sites.models import Site, RequestSite -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, Http404 from django.template import RequestContext -from django.utils.http import urlquote +from django.utils.http import urlquote, base36_to_int from django.utils.html import escape from django.utils.translation import ugettext as _ from django.contrib.auth.models import User @@ -65,19 +67,29 @@ def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_N login_url = settings.LOGIN_URL return HttpResponseRedirect('%s?%s=%s' % (login_url, urlquote(redirect_field_name), urlquote(next))) +# 4 views for password reset: +# - password_reset sends the mail +# - password_reset_done shows a success message for the above +# - password_reset_confirm checks the link the user clicked and +# prompts for a new password +# - password_reset_complete shows a success message for the above + def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html', email_template_name='registration/password_reset_email.html', - password_reset_form=PasswordResetForm): + password_reset_form=PasswordResetForm, token_generator=default_token_generator): if request.method == "POST": form = password_reset_form(request.POST) if form.is_valid(): + opts = {} + opts['use_https'] = request.is_secure() + opts['token_generator'] = token_generator if is_admin_site: - form.save(domain_override=request.META['HTTP_HOST']) + opts['domain_override'] = request.META['HTTP_HOST'] else: - if Site._meta.installed: - form.save(email_template_name=email_template_name) - else: - form.save(domain_override=RequestSite(request).domain, email_template_name=email_template_name) + opts['email_template_name'] = email_template_name + if not Site._meta.installed: + opts['domain_override'] = RequestSite(request).domain + form.save(**opts) return HttpResponseRedirect('%sdone/' % request.path) else: form = password_reset_form() @@ -88,6 +100,40 @@ def password_reset(request, is_admin_site=False, template_name='registration/pas def password_reset_done(request, template_name='registration/password_reset_done.html'): return render_to_response(template_name, context_instance=RequestContext(request)) +def password_reset_confirm(request, uidb36=None, token=None, template_name='registration/password_reset_confirm.html', + token_generator=default_token_generator, set_password_form=SetPasswordForm): + """ + View that checks the hash in a password reset link and presents a + form for entering a new password. + """ + assert uidb36 is not None and token is not None # checked by URLconf + try: + uid_int = base36_to_int(uidb36) + except ValueError: + raise Http404 + + user = get_object_or_404(User, id=uid_int) + context_instance = RequestContext(request) + + if token_generator.check_token(user, token): + context_instance['validlink'] = True + if request.method == 'POST': + form = set_password_form(user, request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect("../done/") + else: + form = set_password_form(None) + else: + context_instance['validlink'] = False + form = None + context_instance['form'] = form + return render_to_response(template_name, context_instance=context_instance) + +def password_reset_complete(request, template_name='registration/password_reset_complete.html'): + return render_to_response(template_name, context_instance=RequestContext(request, + {'login_url': settings.LOGIN_URL})) + def password_change(request, template_name='registration/password_change_form.html'): if request.method == "POST": form = PasswordChangeForm(request.user, request.POST) |
