Newer
Older
postorius / src / postorius / views / user.py
@Aurélien Bompard Aurélien Bompard on 18 May 2016 14 KB Use absorb_existing when adding an address
# -*- coding: utf-8 -*-
# Copyright (C) 1998-2016 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
# Postorius is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# Postorius is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# Postorius.  If not, see <http://www.gnu.org/licenses/>.


import logging

from django.forms import formset_factory
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect, render
from django.utils.decorators import method_decorator
from django.utils.translation import gettext as _
from django.http import Http404
from django.core.urlresolvers import reverse
from django.conf import settings

try:
    from urllib2 import HTTPError
except ImportError:
    from urllib.error import HTTPError

from postorius import utils
from postorius.models import (MailmanConnectionError, MailmanApiError, List,
                              AddressConfirmationProfile, MailmanUser,
                              Mailman404Error)
from postorius.forms import (UserPreferences, AddressActivationForm,
                             ChangeSubscriptionForm, ChangeDisplayNameForm)
from postorius.views.generic import MailmanUserView
from smtplib import SMTPException
from socket import error as socket_error
import errno
import uuid


logger = logging.getLogger(__name__)


class UserMailmanSettingsView(MailmanUserView):
    """The logged-in user's global Mailman Preferences."""

    @method_decorator(login_required)
    def post(self, request):
        try:
            mm_user = MailmanUser.objects.get(address=request.user.email)
            form = UserPreferences(request.POST)
            if form.is_valid():
                preferences = mm_user.preferences
                for key in form.fields.keys():
                    if form.cleaned_data[key] is not None:
                        # None: nothing set yet. Remember to remove this test
                        # when Mailman accepts None as a "reset to default"
                        # value.
                        preferences[key] = form.cleaned_data[key]
                preferences.save()
                messages.success(request,
                                 _('Your preferences have been updated.'))
            else:
                messages.error(request, _('Something went wrong.'))
        except MailmanApiError:
            return utils.render_api_error(request)
        except Mailman404Error as e:
            messages.error(request, e.msg)
        return redirect("user_mailmansettings")

    @method_decorator(login_required)
    def get(self, request):
        try:
            mm_user = MailmanUser.objects.get_or_create_from_django(
                request.user)
        except MailmanApiError:
            return utils.render_api_error(request)
        settingsform = UserPreferences(initial=mm_user.preferences)
        return render(request, 'postorius/user/mailman_settings.html',
                      {'mm_user': mm_user, 'settingsform': settingsform})


class UserAddressPreferencesView(MailmanUserView):
    """The logged-in user's address-based Mailman Preferences."""

    @method_decorator(login_required)
    def post(self, request):
        try:
            mm_user = MailmanUser.objects.get(address=request.user.email)
            formset_class = formset_factory(UserPreferences)
            formset = formset_class(request.POST)
            if formset.is_valid():
                for form, address in zip(formset.forms, mm_user.addresses):
                    preferences = address.preferences
                    for key in form.fields.keys():
                        if form.cleaned_data[key] is not None:
                            # None: nothing set yet. Remember to remove this
                            # test when Mailman accepts None as a
                            # "reset to default" value.
                            preferences[key] = form.cleaned_data[key]
                    preferences.save()
                messages.success(request,
                                 _('Your preferences have been updated.'))
            else:
                messages.error(request, _('Something went wrong.'))
        except MailmanApiError:
            return utils.render_api_error(request)
        except HTTPError as e:
            messages.error(request, e.msg)
        return redirect("user_address_preferences")

    @method_decorator(login_required)
    def get(self, request):
        try:
            helperform = UserPreferences()
            mm_user = MailmanUser.objects.get(address=request.user.email)
            addresses = mm_user.addresses
            AFormset = formset_factory(UserPreferences, extra=len(addresses))
            formset = AFormset()
            zipped_data = zip(formset.forms, addresses)
            for form, address in zipped_data:
                form.initial = address.preferences
        except MailmanApiError:
            return utils.render_api_error(request)
        except Mailman404Error:
            return render(request, 'postorius/user/address_preferences.html',
                          {'nolists': 'true'})
        return render(request, 'postorius/user/address_preferences.html',
                      {'mm_user': mm_user, 'addresses': addresses,
                       'helperform': helperform, 'formset': formset,
                       'zipped_data': zipped_data})


@login_required
def user_list_options(request, list_id):
    utils.set_other_emails(request.user)
    mlist = List.objects.get_or_404(fqdn_listname=list_id)
    mm_user = MailmanUser.objects.get(address=request.user.email)
    subscription = None
    for s in mm_user.subscriptions:
        if s.role == 'member' and s.list_id == list_id:
            subscription = s
            break
    if not subscription:
        raise Http404(_('Subscription does not exist'))
    preferences = subscription.preferences
    if request.method == 'POST':
        form = UserPreferences(request.POST)
        if form.is_valid():
            for key in form.cleaned_data.keys():
                if form.cleaned_data[key] is not None:
                    # None: nothing set yet. Remember to remove this test
                    # when Mailman accepts None as a "reset to default"
                    # value.
                    preferences[key] = form.cleaned_data[key]
            preferences.save()
            messages.success(request, _('Your preferences have been updated.'))
            return redirect('user_list_options', list_id)
        else:
            messages.error(request, _('Something went wrong.'))
    else:
        form = UserPreferences(initial=subscription.preferences)
    user_emails = [request.user.email] + request.user.other_emails
    subscription_form = ChangeSubscriptionForm(
        user_emails, initial={'email': subscription.email})
    return render(request, 'postorius/user/list_options.html',
                  {'form': form, 'list': mlist,
                   'change_subscription_form': subscription_form})


class UserSubscriptionPreferencesView(MailmanUserView):
    """The logged-in user's subscription-based Mailman Preferences."""

    @method_decorator(login_required)
    def post(self, request):
        try:
            subscriptions = self._get_memberships()
            formset_class = formset_factory(UserPreferences)
            formset = formset_class(request.POST)
            if formset.is_valid():
                for form, subscription in zip(formset.forms, subscriptions):
                    preferences = subscription.preferences
                    for key in form.cleaned_data.keys():
                        if form.cleaned_data[key] is not None:
                            # None: nothing set yet. Remember to remove this
                            # test when Mailman accepts None as a
                            # "reset to default" value.
                            preferences[key] = form.cleaned_data[key]
                    preferences.save()
                messages.success(request,
                                 _('Your preferences have been updated.'))
            else:
                messages.error(request, _('Something went wrong.'))
        except MailmanApiError:
            return utils.render_api_error(request)
        except HTTPError as e:
            messages.error(request, e.msg)
        return redirect("user_subscription_preferences")

    @method_decorator(login_required)
    def get(self, request):
        try:
            subscriptions = self._get_memberships()
            Mformset = formset_factory(
                UserPreferences, extra=len(subscriptions))
            formset = Mformset()
            zipped_data = zip(formset.forms, subscriptions)
            for form, subscription in zipped_data:
                form.initial = subscription.preferences
        except MailmanApiError:
            return utils.render_api_error(request)
        except Mailman404Error:
            return render(request,
                          'postorius/user/subscription_preferences.html',
                          {'nolists': 'true'})
        return render(request, 'postorius/user/subscription_preferences.html',
                      {'zipped_data': zipped_data, 'formset': formset})


@login_required
def user_subscriptions(request):
    """Shows the subscriptions of a user."""
    utils.set_other_emails(request.user)
    try:
        mm_user = MailmanUser.objects.get_or_create_from_django(request.user)
    except MailmanApiError:
        return utils.render_api_error(request)
    memberships = [m for m in mm_user.subscriptions if m.role == 'member']
    return render(request, 'postorius/user/subscriptions.html',
                  {'memberships': memberships})


@login_required()
def user_profile(request):
    utils.set_other_emails(request.user)
    try:
        mm_user = MailmanUser.objects.get_or_create_from_django(request.user)
    except MailmanApiError:
        return utils.render_api_error(request)
    if request.method == 'POST':
        if request.POST.get('formname') == 'displayname':
            display_name_form = ChangeDisplayNameForm(request.POST)
            form = AddressActivationForm(
                initial={'user_email': request.user.email})
            if display_name_form.is_valid():
                name = display_name_form.cleaned_data['display_name']
                try:
                    mm_user.display_name = name
                    mm_user.save()
                except MailmanApiError:
                    return utils.render_api_error(request)
                except HTTPError as e:
                    messages.error(request, e)
                else:
                    messages.success(request, _('Display name changed'))
                return redirect('user_profile')
        else:
            display_name_form = ChangeDisplayNameForm(
                initial={'display_name': mm_user.display_name})
            form = AddressActivationForm(request.POST)
            if form.is_valid():
                profile, c = (
                    AddressConfirmationProfile.objects.update_or_create(
                        email=form.cleaned_data['email'], user=request.user,
                        defaults={'activation_key': uuid.uuid4().hex}))
                try:
                    profile.send_confirmation_link(request)
                    messages.success(request, _(
                                     'Please follow the instructions sent via'
                                     ' email to confirm the address'))
                    return redirect('user_profile')
                except (SMTPException, socket_error) as e:
                    if (not isinstance(e, SMTPException) and
                            e.errno != errno.ECONNREFUSED):
                        raise e
                    profile.delete()
                    messages.error(request,
                                   _('Currently emails can not be added,'
                                     ' please try again later'))
    else:
        form = AddressActivationForm(
            initial={'user_email': request.user.email})
        display_name_form = ChangeDisplayNameForm(
            initial={'display_name': mm_user.display_name})
    return render(request, 'postorius/user/profile.html',
                  {'mm_user': mm_user, 'form': form,
                   'name_form': display_name_form})


@login_required()
def address_activation_link(request, activation_key):
    """
    Checks the given activation_key. If it is valid, the saved address will be
    added to mailman. Also, the corresponding profile record will be removed.
    If the key is not valid, it will be ignored.
    """
    try:
        profile = AddressConfirmationProfile.objects.get(
            activation_key=activation_key)
        if request.user != profile.user:
            return redirect('{}?next={}'.format(
                reverse(settings.LOGIN_URL), request.path))
        if not profile.is_expired:
            # Add the address to the user record in Mailman.
            logger.info('Adding address %s to %s', profile.email,
                        request.user.email)
            try:
                try:
                    mailman_user = MailmanUser.objects.get(
                        address=request.user.email)
                except Mailman404Error:
                    mailman_user = MailmanUser.objects.create(
                        request.user.email, '')
                # If the adress already exists, it's an import artefact: it's been
                # validated by email, so it's safe to merge.
                mm_address = mailman_user.add_address(
                    profile.email, absorb_existing=True)
                # The address has just been verified.
                if not mm_address.verified_on:
                    mm_address.verify()
            except (MailmanApiError, MailmanConnectionError):
                messages.error(request, _('The address could not be added.'))
                return
            # Reset the other_emails cache
            if hasattr(request.user, 'other_emails'):
                del request.user.other_emails
            utils.set_other_emails(request.user)
            messages.success(request,
                             _('The email address has been activated!'))
        else:
            messages.error(request, _('The activation link has expired,'
                                      ' please add the email again!'))
        profile.delete()
    except AddressConfirmationProfile.DoesNotExist:
        messages.error(request, _('The activation link is invalid'))
    return redirect('user_profile')