123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464 |
- # Copyright 2013 The Distro Tracker Developers
- # See the COPYRIGHT file at the top-level directory of this distribution and
- # at http://deb.li/DTAuthors
- #
- # This file is part of Distro Tracker. It is subject to the license terms
- # in the LICENSE file found in the top-level directory of this
- # distribution and at http://deb.li/DTLicense. No part of Distro Tracker,
- # including this file, may be copied, modified, propagated, or distributed
- # except according to the terms contained in the LICENSE file.
- from __future__ import unicode_literals
- from django.conf import settings
- from django.utils.http import is_safe_url
- from django.contrib import messages
- from django.contrib.auth import authenticate
- from django.contrib.auth import login
- from django.contrib.auth import logout
- from django.utils.decorators import method_decorator
- from django.shortcuts import render
- from django.shortcuts import get_object_or_404
- from django.shortcuts import redirect
- from django.contrib.auth.decorators import login_required
- from django.views.generic.base import View
- from django.views.generic.edit import CreateView
- from django.views.generic.edit import UpdateView
- from django.views.generic.edit import FormView
- from django.views.generic import TemplateView
- from django.utils.http import urlencode
- from django.core.urlresolvers import reverse_lazy
- from django.template.loader import render_to_string
- from django_email_accounts.models import User
- from django_email_accounts.models import UserEmail
- from django.core.mail import send_mail
- from django.core.exceptions import PermissionDenied
- from django.contrib.auth.forms import PasswordChangeForm
- from django.http import Http404
- from django_email_accounts.forms import (
- AddEmailToAccountForm,
- UserCreationForm,
- ResetPasswordForm,
- ForgotPasswordForm,
- ChangePersonalInfoForm,
- AuthenticationForm,
- )
- from django_email_accounts.models import (
- MergeAccountConfirmation,
- UserRegistrationConfirmation,
- AddEmailConfirmation,
- ResetPasswordConfirmation,
- )
- from django_email_accounts import run_hook
- class LoginView(FormView):
- form_class = AuthenticationForm
- success_url = reverse_lazy('accounts-profile')
- template_name = 'accounts/login.html'
- def form_valid(self, form):
- if not is_safe_url(url=self.success_url, host=self.request.get_host()):
- self.success_url = '/'
- login(self.request, form.get_user())
- return redirect(self.success_url)
- class LogoutView(View):
- success_url = '/'
- redirect_parameter = 'next'
- def get(self, request):
- user = request.user
- logout(request)
- next_url = request.GET.get(self.redirect_parameter, self.success_url)
- redirect_url = run_hook('post-logout-redirect', request, user, next_url)
- if redirect_url:
- return redirect(redirect_url)
- else:
- return redirect(next_url if next_url else '/')
- class RegisterUser(CreateView):
- """
- Provides a view that displays a registration form on a GET request and
- registers the user on a POST.
- ``template_name`` and ``success_url`` properties can be overridden when
- instantiating the view in order to customize the page displayed on a GET
- request and the URL to which the user should be redirected after a
- successful POST, respectively.
- Additionally, by overriding the ``confirmation_email_template`` and
- ``confirmation_email_subject`` it is possible to customize the subject and
- content of a confirmation email sent to the user being registered.
- Instead of providing a ``confirmation_email_template`` you may also override
- the :meth:`get_confirmation_email_content` to provide a custom rendered
- text content.
- The sender of the email can be changed by modifying the
- ``confirmation_email_from_address`` setting.
- """
- template_name = 'accounts/register.html'
- model = User
- success_url = reverse_lazy('accounts-register-success')
- confirmation_email_template = 'accounts/registration-confirmation-email.txt'
- confirmation_email_subject = 'Registration Confirmation'
- confirmation_email_from_address = settings.DEFAULT_FROM_EMAIL
- def get_confirmation_email_content(self, confirmation):
- return render_to_string(self.confirmation_email_template, {
- 'confirmation': confirmation,
- })
- def get_form_class(self):
- return UserCreationForm
- def form_valid(self, form):
- response = super(RegisterUser, self).form_valid(form)
- self.send_confirmation_mail(form.instance)
- return response
- def send_confirmation_mail(self, user):
- """
- Sends a confirmation email to the user. The user is inactive until the
- email is confirmed by clicking a URL found in the email.
- """
- confirmation = UserRegistrationConfirmation.objects.create_confirmation(
- user=user)
- send_mail(
- self.confirmation_email_subject,
- self.get_confirmation_email_content(confirmation),
- from_email=self.confirmation_email_from_address,
- recipient_list=[user.main_email])
- class LoginRequiredMixin(object):
- """
- A view mixin which makes sure that the user is logged in before accessing
- the view.
- """
- @method_decorator(login_required)
- def dispatch(self, *args, **kwargs):
- return super(LoginRequiredMixin, self).dispatch(*args, **kwargs)
- class MessageMixin(object):
- """
- A View mixin which adds a success info message to the list of messages
- managed by the :mod:`django.contrib.message` framework in case a form has
- been successfully processed.
- The message which is added is retrieved by calling the :meth:`get_message`
- method. Alternatively, a :attr:`message` attribute can be set if no
- calculations are necessary.
- """
- def form_valid(self, *args, **kwargs):
- message = self.get_message()
- if message:
- messages.info(self.request, message)
- return super(MessageMixin, self).form_valid(*args, **kwargs)
- def get_message(self):
- if self.message:
- return self.message
- class SetPasswordMixin(object):
- def form_valid(self, form):
- user = self.confirmation.user
- user.is_active = True
- password = form.cleaned_data['password1']
- user.set_password(password)
- user.save()
- # The confirmation key is no longer needed
- self.confirmation.delete()
- # Log the user in
- user = authenticate(username=user.main_email, password=password)
- login(self.request, user)
- return super(SetPasswordMixin, self).form_valid(form)
- def get_confirmation_instance(self, confirmation_key):
- self.confirmation = get_object_or_404(
- self.confirmation_class,
- confirmation_key=confirmation_key)
- return self.confirmation
- def post(self, request, confirmation_key):
- self.get_confirmation_instance(confirmation_key)
- return super(SetPasswordMixin, self).post(request, confirmation_key)
- def get(self, request, confirmation_key):
- self.get_confirmation_instance(confirmation_key)
- return super(SetPasswordMixin, self).get(request, confirmation_key)
- class RegistrationConfirmation(SetPasswordMixin, MessageMixin, FormView):
- form_class = ResetPasswordForm
- template_name = 'accounts/registration-confirmation.html'
- success_url = reverse_lazy('accounts-profile')
- message = 'You have successfully registered'
- confirmation_class = UserRegistrationConfirmation
- class ResetPasswordView(SetPasswordMixin, MessageMixin, FormView):
- form_class = ResetPasswordForm
- template_name = 'accounts/registration-reset-password.html'
- success_url = reverse_lazy('accounts-profile')
- message = 'You have successfully reset your password'
- confirmation_class = ResetPasswordConfirmation
- class ForgotPasswordView(FormView):
- form_class = ForgotPasswordForm
- success_url = reverse_lazy('accounts-password-reset-success')
- template_name = 'accounts/forgot-password.html'
- confirmation_email_template = \
- 'accounts/password-reset-confirmation-email.txt'
- confirmation_email_subject = 'Password Reset Confirmation'
- confirmation_email_from_address = settings.DEFAULT_FROM_EMAIL
- def get_confirmation_email_content(self, confirmation):
- return render_to_string(self.confirmation_email_template, {
- 'confirmation': confirmation,
- })
- def form_valid(self, form):
- # Create a ResetPasswordConfirmation instance
- email = form.cleaned_data['email']
- user = User.objects.get(emails__email=email)
- confirmation = \
- ResetPasswordConfirmation.objects.create_confirmation(user=user)
- # Send a confirmation email
- send_mail(
- self.confirmation_email_subject,
- self.get_confirmation_email_content(confirmation),
- from_email=self.confirmation_email_from_address,
- recipient_list=[email])
- return super(ForgotPasswordView, self).form_valid(form)
- class ChangePersonalInfoView(LoginRequiredMixin, MessageMixin, UpdateView):
- template_name = 'accounts/change-personal-info.html'
- form_class = ChangePersonalInfoForm
- model = User
- success_url = reverse_lazy('accounts-profile-modify')
- message = 'Successfully changed your information'
- def get_object(self, queryset=None):
- return self.request.user
- class PasswordChangeView(LoginRequiredMixin, MessageMixin, FormView):
- template_name = 'accounts/password-update.html'
- form_class = PasswordChangeForm
- success_url = reverse_lazy('accounts-profile-password-change')
- message = 'Successfully updated your password'
- def get_form_kwargs(self):
- kwargs = super(PasswordChangeView, self).get_form_kwargs()
- kwargs['user'] = self.request.user
- return kwargs
- def form_valid(self, form, *args, **kwargs):
- form.save()
- return super(PasswordChangeView, self).form_valid(form, *args, **kwargs)
- class AccountProfile(LoginRequiredMixin, View):
- template_name = 'accounts/profile.html'
- def get(self, request):
- return render(request, self.template_name, {
- 'user': request.user,
- })
- class ManageAccountEmailsView(LoginRequiredMixin, MessageMixin, FormView):
- """
- Render a page letting users add or remove emails to their accounts.
- Apart from the ``success_url``, a ``merge_accounts_url`` can be provided,
- if the name of the view is to differ from ``accounts-merge-confirmation``
- """
- form_class = AddEmailToAccountForm
- template_name = 'accounts/profile-manage-emails.html'
- success_url = reverse_lazy('accounts-manage-emails')
- merge_accounts_url = reverse_lazy('accounts-merge-confirmation')
- confirmation_email_template = 'accounts/add-email-confirmation-email.txt'
- confirmation_email_subject = 'Add Email To Account'
- confirmation_email_from_address = settings.DEFAULT_FROM_EMAIL
- def get_confirmation_email_content(self, confirmation):
- return render_to_string(self.confirmation_email_template, {
- 'confirmation': confirmation,
- })
- def form_valid(self, form):
- email = form.cleaned_data['email']
- user_email, _ = UserEmail.objects.get_or_create(email=email)
- if not user_email.user:
- # The email is not associated with an account yet.
- # Ask for confirmation to add it to this account.
- confirmation = AddEmailConfirmation.objects.create_confirmation(
- user=self.request.user,
- email=user_email)
- self.message = (
- 'Before the email is associated with this account, '
- 'you must follow the confirmation link sent to the address'
- )
- # Send a confirmation email
- send_mail(
- self.confirmation_email_subject,
- self.get_confirmation_email_content(confirmation),
- from_email=self.confirmation_email_from_address,
- recipient_list=[email])
- elif user_email.user == self.request.user:
- self.message = 'This email is already associated with your account.'
- else:
- # Offer the user to merge the two accounts
- return redirect(self.merge_accounts_url + '?' + urlencode({
- 'email': email,
- }))
- return super(ManageAccountEmailsView, self).form_valid(form)
- class AccountMergeConfirmView(LoginRequiredMixin, View):
- template_name = 'accounts/account-merge-confirm.html'
- success_url = reverse_lazy('accounts-merge-confirmed')
- confirmation_email_template = \
- 'accounts/merge-accounts-confirmation-email.txt'
- confirmation_email_subject = 'Merge Accounts'
- confirmation_email_from_address = settings.DEFAULT_FROM_EMAIL
- def get_confirmation_email_content(self, confirmation):
- return render_to_string(self.confirmation_email_template, {
- 'confirmation': confirmation,
- })
- def get_user_email(self, query_dict):
- if 'email' not in query_dict:
- raise Http404
- email = query_dict['email']
- user_email = get_object_or_404(UserEmail, email=email)
- return user_email
- def get(self, request):
- self.request = request
- user_email = self.get_user_email(self.request.GET)
- return render(request, self.template_name, {
- 'user_email': user_email,
- })
- def post(self, request):
- self.request = request
- user_email = self.get_user_email(self.request.POST)
- if not user_email.user or user_email.user == self.request.user:
- pass
- # Send a confirmation mail
- confirmation = MergeAccountConfirmation.objects.create_confirmation(
- initial_user=self.request.user,
- merge_with=user_email.user)
- send_mail(
- self.confirmation_email_subject,
- self.get_confirmation_email_content(confirmation),
- from_email=self.confirmation_email_from_address,
- recipient_list=[user_email.email])
- return redirect(self.success_url + '?' + urlencode({
- 'email': user_email.email,
- }))
- class AccountMergeFinalize(View):
- template_name = 'accounts/account-merge-finalize.html'
- success_url = reverse_lazy('accounts-merge-finalized')
- def get(self, request, confirmation_key):
- confirmation = get_object_or_404(
- MergeAccountConfirmation,
- confirmation_key=confirmation_key)
- return render(request, self.template_name, {
- 'confirmation': confirmation,
- })
- def post(self, request, confirmation_key):
- confirmation = get_object_or_404(
- MergeAccountConfirmation,
- confirmation_key=confirmation_key)
- initial_user = confirmation.initial_user
- merge_with = confirmation.merge_with
- # Move emails
- for email in merge_with.emails.all():
- initial_user.emails.add(email)
- # Run a post merge hook
- run_hook('post-merge', initial_user, merge_with)
- confirmation.delete()
- if request.user == confirmation.merge_with:
- logout(request)
- # The account is now obsolete and should be removed
- merge_with.delete()
- return redirect(self.success_url)
- class AccountMergeConfirmedView(TemplateView):
- template_name = 'accounts/accounts-merge-confirmed.html'
- def get_context_data(self, **kwargs):
- if 'email' not in self.request.GET:
- raise Http404
- email = self.request.GET['email']
- user_email = get_object_or_404(UserEmail, email=email)
- context = super(AccountMergeConfirmedView,
- self).get_context_data(**kwargs)
- context['email'] = user_email
- return context
- class ConfirmAddAccountEmail(View):
- template_name = 'accounts/new-email-added.html'
- def get(self, request, confirmation_key):
- confirmation = get_object_or_404(
- AddEmailConfirmation,
- confirmation_key=confirmation_key)
- user = confirmation.user
- user_email = confirmation.email
- confirmation.delete()
- # If the email has become associated with a different user in the mean
- # time, abort the operation.
- if user_email.user and user_email.user != user:
- raise PermissionDenied
- user_email.user = user
- user_email.save()
- return render(request, self.template_name, {
- 'new_email': user_email,
- })
|