Implemented email change confirmation.
[philo.git] / contrib / waldo / models.py
index 3436505..93edab1 100644 (file)
@@ -17,7 +17,7 @@ from django.views.decorators.cache import never_cache
 from django.views.decorators.csrf import csrf_protect
 from philo.models import MultiView, Page
 from philo.contrib.waldo.forms import LOGIN_FORM_KEY, LoginForm, RegistrationForm
 from django.views.decorators.csrf import csrf_protect
 from philo.models import MultiView, Page
 from philo.contrib.waldo.forms import LOGIN_FORM_KEY, LoginForm, RegistrationForm
-from philo.contrib.waldo.tokens import registration_token_generator
+from philo.contrib.waldo.tokens import registration_token_generator, email_token_generator
 import urlparse
 
 
 import urlparse
 
 
@@ -41,6 +41,7 @@ class LoginMultiView(MultiView):
        password_reset_page = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_password_reset_related')
        password_reset_confirmation_email = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_password_reset_confirmation_email_related')
        password_set_page = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_password_set_related')
        password_reset_page = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_password_reset_related')
        password_reset_confirmation_email = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_password_reset_confirmation_email_related')
        password_set_page = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_password_set_related')
+       password_change_page = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_password_change_related', blank=True, null=True)
        register_page = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_register_related')
        register_confirmation_email = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_register_confirmation_email_related')
        
        register_page = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_register_related')
        register_confirmation_email = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_register_confirmation_email_related')
        
@@ -48,18 +49,20 @@ class LoginMultiView(MultiView):
        def urlpatterns(self):
                urlpatterns = patterns('',
                        url(r'^login/$', self.login, name='login'),
        def urlpatterns(self):
                urlpatterns = patterns('',
                        url(r'^login/$', self.login, name='login'),
-                       url(r'^logout/$', self.logout, name='logout')
-               )
-               urlpatterns += patterns('',
+                       url(r'^logout/$', self.logout, name='logout'),
+                       
                        url(r'^password/reset/$', csrf_protect(self.password_reset), name='password_reset'),
                        url(r'^password/reset/$', csrf_protect(self.password_reset), name='password_reset'),
-                       url(r'^password/reset/(?P<uidb36>\w+)/(?P<token>[^/]+)/$',
-                               self.password_reset_confirm, name='password_reset_confirm')
-               )
-               urlpatterns += patterns('',
+                       url(r'^password/reset/(?P<uidb36>\w+)/(?P<token>[^/]+)/$', self.password_reset_confirm, name='password_reset_confirm'),
+                       
                        url(r'^register/$', csrf_protect(self.register), name='register'),
                        url(r'^register/$', csrf_protect(self.register), name='register'),
-                       url(r'^register/(?P<uidb36>\w+)/(?P<token>[^/]+)/$',
-                               self.register_confirm, name='register_confirm')
+                       url(r'^register/(?P<uidb36>\w+)/(?P<token>[^/]+)/$', self.register_confirm, name='register_confirm')
                )
                )
+               
+               if self.password_change_page:
+                       urlpatterns += patterns('',
+                               url(r'^password/change/$', csrf_protect(self.login_required(self.password_change)), name='password_change'),
+                       )
+               
                return urlpatterns
        
        def get_context(self, extra_dict=None):
                return urlpatterns
        
        def get_context(self, extra_dict=None):
@@ -177,8 +180,8 @@ class LoginMultiView(MultiView):
                if request.method == 'POST':
                        form = PasswordResetForm(request.POST)
                        if form.is_valid():
                if request.method == 'POST':
                        form = PasswordResetForm(request.POST)
                        if form.is_valid():
+                               current_site = Site.objects.get_current()
                                for user in form.users_cache:
                                for user in form.users_cache:
-                                       current_site = Site.objects.get_current()
                                        token = token_generator.make_token(user)
                                        link = 'http://%s/%s/%s/' % (current_site.domain, node.get_absolute_url().strip('/'), reverse('password_reset_confirm', urlconf=self, kwargs={'uidb36': int_to_base36(user.id), 'token': token}).strip('/'))
                                        context = {
                                        token = token_generator.make_token(user)
                                        link = 'http://%s/%s/%s/' % (current_site.domain, node.get_absolute_url().strip('/'), reverse('password_reset_confirm', urlconf=self, kwargs={'uidb36': int_to_base36(user.id), 'token': token}).strip('/'))
                                        context = {
@@ -186,7 +189,7 @@ class LoginMultiView(MultiView):
                                                'username': user.username
                                        }
                                        self.send_confirmation_email('Confirm password reset for account at %s' % current_site.domain, user.email, self.password_reset_confirmation_email, context)
                                                'username': user.username
                                        }
                                        self.send_confirmation_email('Confirm password reset for account at %s' % current_site.domain, user.email, self.password_reset_confirmation_email, context)
-                                       messages.add_message(request, messages.SUCCESS, "An email has been sent to the address you provided with details on resetting your password.")
+                                       messages.add_message(request, messages.SUCCESS, "An email has been sent to the address you provided with details on resetting your password.", fail_silently=True)
                                return HttpResponseRedirect('')
                else:
                        form = PasswordResetForm()
                                return HttpResponseRedirect('')
                else:
                        form = PasswordResetForm()
@@ -224,6 +227,20 @@ class LoginMultiView(MultiView):
                
                raise Http404
        
                
                raise Http404
        
+       def password_change(self, request, node=None, extra_context=None):
+               if request.method == 'POST':
+                       form = PasswordChangeForm(request.user, request.POST)
+                       if form.is_valid():
+                               form.save()
+                               messages.add_message(request, messages.SUCCESS, 'Password changed successfully.', fail_silently=True)
+                               return HttpResponseRedirect('')
+               else:
+                       form = PasswordChangeForm(request.user)
+               
+               context = self.get_context({'form': form})
+               context.update(extra_context or {})
+               return self.password_change_page.render_to_response(node, request, extra_context=context)
+       
        def register(self, request, node=None, extra_context=None, token_generator=registration_token_generator):
                if request.user.is_authenticated():
                        return HttpResponseRedirect(node.get_absolute_url())
        def register(self, request, node=None, extra_context=None, token_generator=registration_token_generator):
                if request.user.is_authenticated():
                        return HttpResponseRedirect(node.get_absolute_url())
@@ -239,7 +256,7 @@ class LoginMultiView(MultiView):
                                        'link': link
                                }
                                self.send_confirmation_email('Confirm account creation at %s' % current_site.name, user.email, self.register_confirmation_email, context)
                                        'link': link
                                }
                                self.send_confirmation_email('Confirm account creation at %s' % current_site.name, user.email, self.register_confirmation_email, context)
-                               messages.add_message(request, messages.SUCCESS, 'An email has been sent to %s with details on activating your account.' % user.email)
+                               messages.add_message(request, messages.SUCCESS, 'An email has been sent to %s with details on activating your account.' % user.email, fail_silently=True)
                                return HttpResponseRedirect('')
                else:
                        form = RegistrationForm()
                                return HttpResponseRedirect('')
                else:
                        form = RegistrationForm()
@@ -290,7 +307,8 @@ class AccountMultiView(LoginMultiView):
        to include in the account, and fields from the account profile to use in
        the account.
        """
        to include in the account, and fields from the account profile to use in
        the account.
        """
-       manage_account_page = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_manage_account_page')
+       manage_account_page = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_manage_account_related')
+       email_change_confirmation_email = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_email_change_confirmation_email_related')
        user_fields = ['first_name', 'last_name', 'email']
        required_user_fields = user_fields
        account_profile = None
        user_fields = ['first_name', 'last_name', 'email']
        required_user_fields = user_fields
        account_profile = None
@@ -300,7 +318,8 @@ class AccountMultiView(LoginMultiView):
        def urlpatterns(self):
                urlpatterns = super(AccountMultiView, self).urlpatterns
                urlpatterns += patterns('',
        def urlpatterns(self):
                urlpatterns = super(AccountMultiView, self).urlpatterns
                urlpatterns += patterns('',
-                       url(r'^account/$', self.login_required(self.account_view), name='account')
+                       url(r'^account/$', self.login_required(self.account_view), name='account'),
+                       url(r'^account/email/(?P<uidb36>\w+)/(?P<email>[\w.]+[+][\w.]+)/(?P<token>[^/]+)/$', self.email_change_confirm, name='email_change_confirm')
                )
                return urlpatterns
        
                )
                return urlpatterns
        
@@ -331,14 +350,33 @@ class AccountMultiView(LoginMultiView):
                
                return form_instances
        
                
                return form_instances
        
-       def account_view(self, request, node=None, extra_context=None):
+       def account_view(self, request, node=None, extra_context=None, token_generator=email_token_generator):
                if request.method == 'POST':
                        form_instances = self.get_account_form_instances(request.user, request.POST)
                if request.method == 'POST':
                        form_instances = self.get_account_form_instances(request.user, request.POST)
+                       current_email = request.user.email
                        
                        for form in form_instances:
                                if not form.is_valid():
                                        break
                        else:
                        
                        for form in form_instances:
                                if not form.is_valid():
                                        break
                        else:
+                               # When the user_form is validated, it changes the model instance, i.e. request.user, in place.
+                               email = request.user.email
+                               if current_email != email:
+                                       
+                                       request.user.email = current_email
+                                       
+                                       for form in form_instances:
+                                               form.cleaned_data.pop('email', None)
+                                       
+                                       current_site = Site.objects.get_current()
+                                       token = token_generator.make_token(request.user, email)
+                                       link = 'http://%s/%s/%s/' % (current_site.domain, node.get_absolute_url().strip('/'), reverse('email_change_confirm', urlconf=self, kwargs={'uidb36': int_to_base36(request.user.id), 'email': email.replace('@', '+'), 'token': token}).strip('/'))
+                                       context = {
+                                               'link': link
+                                       }
+                                       self.send_confirmation_email('Confirm account email change at %s' % current_site.domain, email, self.email_change_confirmation_email, context)
+                                       messages.add_message(request, messages.SUCCESS, "An email has be sent to %s to confirm the email change." % email)
+                                       
                                for form in form_instances:
                                        form.save()
                                messages.add_message(request, messages.SUCCESS, "Account information saved.", fail_silently=True)
                                for form in form_instances:
                                        form.save()
                                messages.add_message(request, messages.SUCCESS, "Account information saved.", fail_silently=True)
@@ -369,7 +407,7 @@ class AccountMultiView(LoginMultiView):
        def account_required(self, view):
                def inner(request, *args, **kwargs):
                        if not self.has_valid_account(request.user):
        def account_required(self, view):
                def inner(request, *args, **kwargs):
                        if not self.has_valid_account(request.user):
-                               messages.add_message(request, messages.ERROR, "You need to add some account information before you can post listings.")
+                               messages.add_message(request, messages.ERROR, "You need to add some account information before you can post listings.", fail_silently=True)
                                return self.account_view(request, *args, **kwargs)
                        return view(request, *args, **kwargs)
                
                                return self.account_view(request, *args, **kwargs)
                        return view(request, *args, **kwargs)
                
@@ -377,8 +415,35 @@ class AccountMultiView(LoginMultiView):
                return inner
        
        def post_register_confirm_redirect(self, request, node):
                return inner
        
        def post_register_confirm_redirect(self, request, node):
-               messages.add_message(request, messages.INFO, 'Welcome! Please fill in some more information.')
+               messages.add_message(request, messages.INFO, 'Welcome! Please fill in some more information.', fail_silently=True)
                return HttpResponseRedirect('/%s/%s/' % (node.get_absolute_url().strip('/'), reverse('account', urlconf=self).strip('/')))
        
                return HttpResponseRedirect('/%s/%s/' % (node.get_absolute_url().strip('/'), reverse('account', urlconf=self).strip('/')))
        
+       def email_change_confirm(self, request, node=None, extra_context=None, uidb36=None, token=None, email=None, token_generator=email_token_generator):
+               """
+               Checks that a given hash in an email change link is valid. If so, changes the email and redirects to the account page.
+               """
+               assert uidb36 is not None and token is not None and email is not None
+               
+               try:
+                       uid_int = base36_to_int(uidb36)
+               except:
+                       raise Http404
+               
+               user = get_object_or_404(User, id=uid_int)
+               
+               email = email.replace('+', '@')
+               
+               if email == user.email:
+                       # Then short-circuit.
+                       raise Http404
+               
+               if token_generator.check_token(user, email, token):
+                       user.email = email
+                       user.save()
+                       messages.add_message(request, messages.SUCCESS, 'Email changed successfully.')
+                       return HttpResponseRedirect('/%s/%s/' % (node.get_absolute_url().strip('/'), reverse('account', urlconf=self).strip('/')))
+               
+               raise Http404
+       
        class Meta:
                abstract = True
\ No newline at end of file
        class Meta:
                abstract = True
\ No newline at end of file