Changeset 8162
- Timestamp:
- 07/31/08 15:47:53 (1 year ago)
- Files:
-
- django/trunk/django/conf/global_settings.py (modified) (1 diff)
- django/trunk/django/contrib/admin/templates/registration/password_reset_complete.html (added)
- django/trunk/django/contrib/admin/templates/registration/password_reset_confirm.html (added)
- django/trunk/django/contrib/admin/templates/registration/password_reset_done.html (modified) (1 diff)
- django/trunk/django/contrib/admin/templates/registration/password_reset_email.html (modified) (2 diffs)
- django/trunk/django/contrib/admin/templates/registration/password_reset_form.html (modified) (1 diff)
- django/trunk/django/contrib/auth/forms.py (modified) (4 diffs)
- django/trunk/django/contrib/auth/tests/basic.py (modified) (1 diff)
- django/trunk/django/contrib/auth/tests/forms.py (modified) (3 diffs)
- django/trunk/django/contrib/auth/tests/__init__.py (modified) (2 diffs)
- django/trunk/django/contrib/auth/tests/tokens.py (added)
- django/trunk/django/contrib/auth/tests/views.py (added)
- django/trunk/django/contrib/auth/tokens.py (added)
- django/trunk/django/contrib/auth/urls.py (modified) (1 diff)
- django/trunk/django/contrib/auth/views.py (modified) (3 diffs)
- django/trunk/django/utils/http.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/trunk/django/conf/global_settings.py
r8015 r8162 367 367 LOGIN_REDIRECT_URL = '/accounts/profile/' 368 368 369 # The number of days a password reset link is valid for 370 PASSWORD_RESET_TIMEOUT_DAYS = 3 371 369 372 ########### 370 373 # TESTING # django/trunk/django/contrib/admin/templates/registration/password_reset_done.html
r7294 r8162 10 10 <h1>{% trans 'Password reset successful' %}</h1> 11 11 12 <p>{% trans "We've e-mailed a newpassword to the e-mail address you submitted. You should be receiving it shortly." %}</p>12 <p>{% trans "We've e-mailed you instructions for setting your password to the e-mail address you submitted. You should be receiving it shortly." %}</p> 13 13 14 14 {% endblock %} django/trunk/django/contrib/admin/templates/registration/password_reset_email.html
r7294 r8162 1 {% load i18n %} 1 {% load i18n %}{% autoescape off %} 2 2 {% trans "You're receiving this e-mail because you requested a password reset" %} 3 3 {% blocktrans %}for your user account at {{ site_name }}{% endblocktrans %}. 4 4 5 {% blocktrans %}Your new password is: {{ new_password }}{% endblocktrans %} 6 7 {% trans "Feel free to change this password by going to this page:" %} 8 9 http://{{ domain }}/password_change/ 10 5 {% trans "Please go to the following page and choose a new password:" %} 6 {% block reset_link %} 7 {{ protocol }}://{{ domain }}/reset/{{ uid }}-{{ token }}/ 8 {% endblock %} 11 9 {% trans "Your username, in case you've forgotten:" %} {{ user.username }} 12 10 … … 14 12 15 13 {% blocktrans %}The {{ site_name }} team{% endblocktrans %} 14 15 {% endautoescape %} django/trunk/django/contrib/admin/templates/registration/password_reset_form.html
r7967 r8162 10 10 <h1>{% trans "Password reset" %}</h1> 11 11 12 <p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll reset your password and e-mail the new one to you." %}</p>12 <p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll e-mail instructions for setting a new one." %}</p> 13 13 14 14 <form action="" method="post"> django/trunk/django/contrib/auth/forms.py
r8159 r8162 1 1 from django.contrib.auth.models import User 2 2 from django.contrib.auth import authenticate 3 from django.contrib.auth.tokens import default_token_generator 3 4 from django.contrib.sites.models import Site 4 5 from django.template import Context, loader 5 6 from django import forms 6 7 from django.utils.translation import ugettext_lazy as _ 8 from django.utils.http import int_to_base36 7 9 8 10 class UserCreationForm(forms.ModelForm): … … 98 100 if len(self.users_cache) == 0: 99 101 raise forms.ValidationError(_("That e-mail address doesn't have an associated user account. Are you sure you've registered?")) 100 101 def save(self, domain_override=None, email_template_name='registration/password_reset_email.html'): 102 103 def save(self, domain_override=None, email_template_name='registration/password_reset_email.html', 104 use_https=False, token_generator=default_token_generator): 102 105 """ 103 Calculates a new password randomly and sends it to the user.106 Generates a one-use only link for restting password and sends to the user 104 107 """ 105 108 from django.core.mail import send_mail 106 109 for user in self.users_cache: 107 new_pass = User.objects.make_random_password()108 user.set_password(new_pass)109 user.save()110 110 if not domain_override: 111 111 current_site = Site.objects.get_current() … … 116 116 t = loader.get_template(email_template_name) 117 117 c = { 118 'new_password': new_pass,119 118 'email': user.email, 120 119 'domain': domain, 121 120 'site_name': site_name, 121 'uid': int_to_base36(user.id), 122 122 'user': user, 123 'token': token_generator.make_token(user), 124 'protocol': use_https and 'https' or 'http', 123 125 } 124 126 send_mail(_("Password reset on %s") % site_name, 125 127 t.render(Context(c)), None, [user.email]) 126 128 127 class PasswordChangeForm(forms.Form):129 class SetPasswordForm(forms.Form): 128 130 """ 129 A form that lets a user change his/her password. 131 A form that lets a user change set his/her password without 132 entering the old password 130 133 """ 131 old_password = forms.CharField(label=_("Old password"), max_length=30, widget=forms.PasswordInput) 132 new_password1 = forms.CharField(label=_("New password"), max_length=30, widget=forms.PasswordInput) 133 new_password2 = forms.CharField(label=_("New password confirmation"), max_length=30, widget=forms.PasswordInput) 134 134 new_password1 = forms.CharField(label=_("New password"), max_length=60, widget=forms.PasswordInput) 135 new_password2 = forms.CharField(label=_("New password confirmation"), max_length=60, widget=forms.PasswordInput) 136 135 137 def __init__(self, user, *args, **kwargs): 136 138 self.user = user 137 super(PasswordChangeForm, self).__init__(*args, **kwargs) 139 super(SetPasswordForm, self).__init__(*args, **kwargs) 140 141 def clean_new_password2(self): 142 password1 = self.cleaned_data.get('new_password1') 143 password2 = self.cleaned_data.get('new_password2') 144 if password1 and password2: 145 if password1 != password2: 146 raise forms.ValidationError(_("The two password fields didn't match.")) 147 return password2 148 149 def save(self, commit=True): 150 self.user.set_password(self.cleaned_data['new_password1']) 151 if commit: 152 self.user.save() 153 return self.user 154 155 class PasswordChangeForm(SetPasswordForm): 156 """ 157 A form that lets a user change his/her password by entering 158 their old password. 159 """ 160 old_password = forms.CharField(label=_("Old password"), max_length=60, widget=forms.PasswordInput) 138 161 139 162 def clean_old_password(self): … … 145 168 raise forms.ValidationError(_("Your old password was entered incorrectly. Please enter it again.")) 146 169 return old_password 170 PasswordChangeForm.base_fields.keyOrder = ['old_password', 'new_password1', 'new_password2'] 147 171 148 def clean_new_password2(self):149 password1 = self.cleaned_data.get('new_password1')150 password2 = self.cleaned_data.get('new_password2')151 if password1 and password2:152 if password1 != password2:153 raise forms.ValidationError(_("The two password fields didn't match."))154 return password2155 156 def save(self, commit=True):157 self.user.set_password(self.cleaned_data['new_password1'])158 if commit:159 self.user.save()160 return self.user161 162 172 class AdminPasswordChangeForm(forms.Form): 163 173 """ django/trunk/django/contrib/auth/tests/basic.py
r7967 r8162 55 55 u'!' 56 56 """ 57 58 from django.test import TestCase59 from django.core import mail60 61 class PasswordResetTest(TestCase):62 fixtures = ['authtestdata.json']63 urls = 'django.contrib.auth.urls'64 65 def test_email_not_found(self):66 "Error is raised if the provided email address isn't currently registered"67 response = self.client.get('/password_reset/')68 self.assertEquals(response.status_code, 200)69 response = self.client.post('/password_reset/', {'email': 'not_a_real_email@email.com'})70 self.assertContains(response, "That e-mail address doesn't have an associated user account")71 self.assertEquals(len(mail.outbox), 0)72 73 def test_email_found(self):74 "Email is sent if a valid email address is provided for password reset"75 response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})76 self.assertEquals(response.status_code, 302)77 self.assertEquals(len(mail.outbox), 1)django/trunk/django/contrib/auth/tests/forms.py
r7967 r8162 3 3 >>> from django.contrib.auth.models import User 4 4 >>> from django.contrib.auth.forms import UserCreationForm, AuthenticationForm 5 >>> from django.contrib.auth.forms import PasswordChangeForm 5 >>> from django.contrib.auth.forms import PasswordChangeForm, SetPasswordForm 6 6 7 7 The user already exists. … … 96 96 [] 97 97 98 SetPasswordForm: 99 100 The two new passwords do not match. 101 102 >>> data = { 103 ... 'new_password1': 'abc123', 104 ... 'new_password2': 'abc', 105 ... } 106 >>> form = SetPasswordForm(user, data) 107 >>> form.is_valid() 108 False 109 >>> form["new_password2"].errors 110 [u"The two password fields didn't match."] 111 112 The success case. 113 114 >>> data = { 115 ... 'new_password1': 'abc123', 116 ... 'new_password2': 'abc123', 117 ... } 118 >>> form = SetPasswordForm(user, data) 119 >>> form.is_valid() 120 True 121 122 PasswordChangeForm: 123 98 124 The old password is incorrect. 99 125 … … 133 159 True 134 160 161 Regression test - check the order of fields: 162 163 >>> PasswordChangeForm(user, {}).fields.keys() 164 ['old_password', 'new_password1', 'new_password2'] 165 135 166 """ django/trunk/django/contrib/auth/tests/__init__.py
r7967 r8162 1 from django.contrib.auth.tests.basic import BASIC_TESTS, PasswordResetTest 1 from django.contrib.auth.tests.basic import BASIC_TESTS 2 from django.contrib.auth.tests.views import PasswordResetTest 2 3 from django.contrib.auth.tests.forms import FORM_TESTS 4 from django.contrib.auth.tests.tokens import TOKEN_GENERATOR_TESTS 3 5 4 6 __test__ = { … … 6 8 'PASSWORDRESET_TESTS': PasswordResetTest, 7 9 'FORM_TESTS': FORM_TESTS, 10 'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS 8 11 } django/trunk/django/contrib/auth/urls.py
r7808 r8162 9 9 ('^password_change/$', 'django.contrib.auth.views.password_change'), 10 10 ('^password_change/done/$', 'django.contrib.auth.views.password_change_done'), 11 ('^password_reset/$', 'django.contrib.auth.views.password_reset') 11 ('^password_reset/$', 'django.contrib.auth.views.password_reset'), 12 ('^password_reset/done/$', 'django.contrib.auth.views.password_reset_done'), 13 ('^reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', 'django.contrib.auth.views.password_reset_confirm'), 14 ('^reset/done/$', 'django.contrib.auth.views.password_reset_complete'), 12 15 ) 13 16 django/trunk/django/contrib/auth/views.py
r7967 r8162 2 2 from django.contrib.auth.decorators import login_required 3 3 from django.contrib.auth.forms import AuthenticationForm 4 from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm, AdminPasswordChangeForm 4 from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm, PasswordChangeForm, AdminPasswordChangeForm 5 from django.contrib.auth.tokens import default_token_generator 5 6 from django.core.exceptions import PermissionDenied 6 7 from django.shortcuts import render_to_response, get_object_or_404 7 8 from django.contrib.sites.models import Site, RequestSite 8 from django.http import HttpResponseRedirect 9 from django.http import HttpResponseRedirect, Http404 9 10 from django.template import RequestContext 10 from django.utils.http import urlquote 11 from django.utils.http import urlquote, base36_to_int 11 12 from django.utils.html import escape 12 13 from django.utils.translation import ugettext as _ … … 66 67 return HttpResponseRedirect('%s?%s=%s' % (login_url, urlquote(redirect_field_name), urlquote(next))) 67 68 69 # 4 views for password reset: 70 # - password_reset sends the mail 71 # - password_reset_done shows a success message for the above 72 # - password_reset_confirm checks the link the user clicked and 73 # prompts for a new password 74 # - password_reset_complete shows a success message for the above 75 68 76 def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html', 69 77 email_template_name='registration/password_reset_email.html', 70 password_reset_form=PasswordResetForm ):78 password_reset_form=PasswordResetForm, token_generator=default_token_generator): 71 79 if request.method == "POST": 72 80 form = password_reset_form(request.POST) 73 81 if form.is_valid(): 82 opts = {} 83 opts['use_https'] = request.is_secure() 84 opts['token_generator'] = token_generator 74 85 if is_admin_site: 75 form.save(domain_override=request.META['HTTP_HOST'])86 opts['domain_override'] = request.META['HTTP_HOST'] 76 87 else: 77 if Site._meta.installed:78 form.save(email_template_name=email_template_name)79 else:80 form.save(domain_override=RequestSite(request).domain, email_template_name=email_template_name)88 opts['email_template_name'] = email_template_name 89 if not Site._meta.installed: 90 opts['domain_override'] = RequestSite(request).domain 91 form.save(**opts) 81 92 return HttpResponseRedirect('%sdone/' % request.path) 82 93 else: … … 87 98 88 99 def password_reset_done(request, template_name='registration/password_reset_done.html'): 100 return render_to_response(template_name, context_instance=RequestContext(request)) 101 102 def password_reset_confirm(request, uidb36=None, token=None, template_name='registration/password_reset_confirm.html', 103 token_generator=default_token_generator, set_password_form=SetPasswordForm): 104 """ 105 View that checks the hash in a password reset link and presents a 106 form for entering a new password. 107 """ 108 assert uidb36 is not None and token is not None # checked by URLconf 109 try: 110 uid_int = base36_to_int(uidb36) 111 except ValueError: 112 raise Http404 113 114 user = get_object_or_404(User, id=uid_int) 115 context_instance = RequestContext(request) 116 117 if token_generator.check_token(user, token): 118 context_instance['validlink'] = True 119 if request.method == 'POST': 120 form = set_password_form(user, request.POST) 121 if form.is_valid(): 122 form.save() 123 return HttpResponseRedirect("../done/") 124 else: 125 form = set_password_form(None) 126 else: 127 context_instance['validlink'] = False 128 form = None 129 context_instance['form'] = form 130 return render_to_response(template_name, context_instance=context_instance) 131 132 def password_reset_complete(request, template_name='registration/password_reset_complete.html'): 89 133 return render_to_response(template_name, context_instance=RequestContext(request)) 90 134 django/trunk/django/utils/http.py
r6634 r8162 66 66 rfcdate = formatdate(epoch_seconds) 67 67 return '%s GMT' % rfcdate[:25] 68 69 # Base 36 functions: useful for generating compact URLs 70 71 def base36_to_int(s): 72 """ 73 Convertd a base 36 string to an integer 74 """ 75 return int(s, 36) 76 77 def int_to_base36(i): 78 """ 79 Converts an integer to a base36 string 80 """ 81 digits = "0123456789abcdefghijklmnopqrstuvwxyz" 82 factor = 0 83 # Find starting factor 84 while True: 85 factor += 1 86 if i < 36 ** factor: 87 factor -= 1 88 break 89 base36 = [] 90 # Construct base36 representation 91 while factor >= 0: 92 j = 36 ** factor 93 base36.append(digits[i / j]) 94 i = i % j 95 factor -= 1 96 return ''.join(base36)
