diff --git a/src/postorius/auth/decorators.py b/src/postorius/auth/decorators.py index b5ff2d9..0f12b55 100644 --- a/src/postorius/auth/decorators.py +++ b/src/postorius/auth/decorators.py @@ -40,7 +40,7 @@ login(request, user) return fn(request, **kwargs) return wrapper - + def list_owner_required(fn): """Check if the logged in user is the list owner of the given list. @@ -54,6 +54,8 @@ raise PermissionDenied if user.is_superuser: return fn(*args, **kwargs) + if getattr(user, 'is_list_owner', None): + return fn(*args, **kwargs) mlist = List.objects.get_or_404(fqdn_listname=fqdn_listname) if user.email not in mlist.owners: raise PermissionDenied @@ -75,6 +77,10 @@ raise PermissionDenied if user.is_superuser: return fn(*args, **kwargs) + if getattr(user, 'is_list_owner', None): + return fn(*args, **kwargs) + if getattr(user, 'is_list_moderator', None): + return fn(*args, **kwargs) mlist = List.objects.get_or_404(fqdn_listname=fqdn_listname) if user.email not in mlist.moderators and \ user.email not in mlist.owners: diff --git a/src/postorius/context_processors.py b/src/postorius/context_processors.py index 78f10b7..ec4bf58 100644 --- a/src/postorius/context_processors.py +++ b/src/postorius/context_processors.py @@ -29,4 +29,7 @@ else: extend_template = "postorius/base.html" - return {'extend_template': extend_template} + return { + 'extend_template': extend_template, + 'request': request, + } diff --git a/src/postorius/forms.py b/src/postorius/forms.py index 2a531eb..d54bdbf 100644 --- a/src/postorius/forms.py +++ b/src/postorius/forms.py @@ -73,6 +73,24 @@ "description"]] +class NewOwnerForm(forms.Form): + """Add a list owner.""" + owner_email = forms.EmailField( + label=_('Email Address'), + error_messages={ + 'required': _('Please enter an email adddress.'), + 'invalid': _('Please enter a valid email adddress.')}) + + +class NewModeratorForm(forms.Form): + """Add a list moderator.""" + moderator_email = forms.EmailField( + label=_('Email Address'), + error_messages={ + 'required': _('Please enter an email adddress.'), + 'invalid': _('Please enter a valid email adddress.')}) + + class ListNew(FieldsetForm): """ Form fields to add a new list. Languages are hard coded which should diff --git a/src/postorius/models.py b/src/postorius/models.py index 25e18af..5a0144a 100644 --- a/src/postorius/models.py +++ b/src/postorius/models.py @@ -25,6 +25,7 @@ from django.dispatch import receiver from django.http import Http404 from mailmanclient import Client, MailmanConnectionError +from social_auth.signals import socialauth_registered from urllib2 import HTTPError @@ -175,3 +176,9 @@ """Member model class. """ objects = MailmanRestManager('member', 'members') + + +@receiver(socialauth_registered, sender=None) +def social_auth_update_handler(sender, **kwargs): + print 'user was registered by social auth' + diff --git a/src/postorius/static/postorius/css/style.css b/src/postorius/static/postorius/css/style.css index a4cb9ab..1a1920f 100755 --- a/src/postorius/static/postorius/css/style.css +++ b/src/postorius/static/postorius/css/style.css @@ -1,6 +1,7 @@ body { color: #444; font-size: 13px; + background-color: #F7F7F7; } a { color: #069; @@ -34,6 +35,10 @@ -ms-text-shadow: 1px 1px 0 #d8d8d8; text-shadow: 1px 1px 0 #d8d8d8; } +footer { + padding-top: 10px; + text-align: right; +} .mm_canvas { position: relative; @@ -95,11 +100,19 @@ .mm_lists:hover { background-position: 0px -276px; } .mm_main { + background-color: #fff; clear: both; + border: 1px solid #bbb; + border-top-width: 0; + border-radius: 5px; + border-top-left-radius: 0px; + border-top-right-radius: 0px; + padding: 20px 40px; } .mm_subHeader { padding-top: 10px; + margin-bottom: 40px; } .mm_context { display: block; @@ -111,8 +124,9 @@ padding: 10px 0 30px 0; margin: 20px 0; border-style: solid; - border-width: 1px 0; - border-color: #e8e8e8 #fff #d8d8d8 #fff; + border-width: 1px; + border-color: #e8e8e8; + border-radius: 2px; background: -webkit-linear-gradient(top, white 0%,#EFEFEF 100%); background: -moz-linear-gradient(top, white 0%,#EFEFEF 100%); background: -ms-linear-gradient(top, white 0%,#EFEFEF 100%); @@ -130,9 +144,19 @@ margin-right: 10px; } .mm_nav li a { - font-weight: bold; font-size: 14px; } +.mm_nav .mm_nav_item a { + font-weight: bold; + padding: 5px; +} +.mm_nav .mm_nav_item a:hover, +.mm_nav .mm_nav_item a.mm_active { + text-decoration: none; + background: #fff; + border-radius: 3px; + box-shadow: inset 0px 0px 1px #888; +} .mm_nav li .btn { font-weight: normal; } diff --git a/src/postorius/templates/postorius/base.html b/src/postorius/templates/postorius/base.html index 4c2c304..35883f0 100644 --- a/src/postorius/templates/postorius/base.html +++ b/src/postorius/templates/postorius/base.html @@ -50,9 +50,15 @@ {% endif %} {% block main %}{% endblock main %} - + + diff --git a/src/postorius/templates/postorius/lists/confirm_delete.html b/src/postorius/templates/postorius/lists/confirm_delete.html index b0dec04..be8b0a9 100644 --- a/src/postorius/templates/postorius/lists/confirm_delete.html +++ b/src/postorius/templates/postorius/lists/confirm_delete.html @@ -1,10 +1,11 @@ {% extends extend_template %} {% load url from future %} {% load i18n %} +{% load nav_helpers %} {% block main %} - {% include 'postorius/menu/list_nav.html' %} -

{% trans 'Confirm Delete' %}

+ {% list_nav 'list_delete' 'Delete List' %} +

{% trans "Are you sure you want to permanently delete this list?" %}

{% trans "All settings and membership data will be lost!" %}

{% csrf_token %} diff --git a/src/postorius/templates/postorius/lists/held_messages.html b/src/postorius/templates/postorius/lists/held_messages.html index de3ceee..891d2f6 100644 --- a/src/postorius/templates/postorius/lists/held_messages.html +++ b/src/postorius/templates/postorius/lists/held_messages.html @@ -1,12 +1,12 @@ {% extends "postorius/base.html" %} {% load url from future %} {% load i18n %} +{% load nav_helpers %} {% block body_class %}list_summary{% endblock %} {% block main %} - {% include 'postorius/menu/list_nav.html' %} -

{% trans 'Held messages' %}

+ {% list_nav 'list_held_messages' 'Held Messages' %} diff --git a/src/postorius/templates/postorius/lists/index.html b/src/postorius/templates/postorius/lists/index.html index b3e3eb3..1ca70e7 100644 --- a/src/postorius/templates/postorius/lists/index.html +++ b/src/postorius/templates/postorius/lists/index.html @@ -7,12 +7,11 @@ {% trans "Mailing Lists" %} {% if user.is_superuser %} {% endif %} -

{% trans 'List Index' %}

diff --git a/src/postorius/templates/postorius/lists/mass_subscribe.html b/src/postorius/templates/postorius/lists/mass_subscribe.html index 8c7b94b..4a50595 100644 --- a/src/postorius/templates/postorius/lists/mass_subscribe.html +++ b/src/postorius/templates/postorius/lists/mass_subscribe.html @@ -1,12 +1,11 @@ {% extends "postorius/base.html" %} {% load url from future %} {% load i18n %} +{% load nav_helpers %} {% block main %} - {% if user.is_superuser or user.is_list_owner %} - {% include 'postorius/menu/list_nav.html' %} - {% endif %} -

{% trans "Mass Subscribe" %}

+ {% list_nav 'mass_subscribe' 'Mass Subscription' %} + {% csrf_token %} {{ form.as_p }} diff --git a/src/postorius/templates/postorius/lists/members.html b/src/postorius/templates/postorius/lists/members.html index 16deba1..11c231e 100644 --- a/src/postorius/templates/postorius/lists/members.html +++ b/src/postorius/templates/postorius/lists/members.html @@ -1,13 +1,64 @@ {% extends "postorius/base.html" %} {% load url from future %} {% load i18n %} +{% load nav_helpers %} {% block main %} - {% if user.is_superuser %} - {% include 'postorius/menu/list_nav.html' %} - {% endif %} + {% list_nav 'list_members' 'List Members' %} -

{% trans "List members" %}

+

{% trans "Owners" %}

+ + {{ owner_form.email.errors }} + {% csrf_token %} + + {{ owner_form.owner_email }} + + +
+ + + + + + + + {% for member in list.owners %} + + + + + {% endfor %} + +
{% trans 'Address' %} 
{{ member }} +
+ +

{% trans "Moderators" %}

+ {{ moderator_form.email.errors }} +
{% csrf_token %} + + {{ moderator_form.moderator_email }} + +
+ + + + + + + + + + {% for member in list.moderators %} + + + + + {% endfor %} + +
{% trans 'Address' %} 
{{ member }} +
+ +

{% trans "Members" %}

diff --git a/src/postorius/templates/postorius/lists/metrics.html b/src/postorius/templates/postorius/lists/metrics.html index 408bc09..cfd3437 100644 --- a/src/postorius/templates/postorius/lists/metrics.html +++ b/src/postorius/templates/postorius/lists/metrics.html @@ -1,13 +1,10 @@ {% extends "postorius/base.html" %} {% load url from future %} {% load i18n %} +{% load nav_helpers %} {% block main %} - {% if user.is_superuser or user.is_list_owner %} - {% include 'postorius/menu/list_nav.html' %} - {% endif %} - -

{% trans "List metrics" %}

+ {% list_nav 'list_metrics' 'Metrics' %}
diff --git a/src/postorius/templates/postorius/lists/settings.html b/src/postorius/templates/postorius/lists/settings.html index 278d324..3d0c6ac 100644 --- a/src/postorius/templates/postorius/lists/settings.html +++ b/src/postorius/templates/postorius/lists/settings.html @@ -1,10 +1,10 @@ {% extends extend_template %} {% load url from future %} {% load i18n %} +{% load nav_helpers %} {% block main %} - {% include 'postorius/menu/list_nav.html' %} -

{% trans 'List Settings' %}

+ {% list_nav 'list_settings' 'Settings' %} + {% endif %} diff --git a/src/postorius/templates/postorius/menu/mm_user_nav.html b/src/postorius/templates/postorius/menu/mm_user_nav.html index 9cb50e2..2e5d0c3 100644 --- a/src/postorius/templates/postorius/menu/mm_user_nav.html +++ b/src/postorius/templates/postorius/menu/mm_user_nav.html @@ -1,9 +1,10 @@ {% load url from future %} {% load i18n %} +{% load nav_helpers %}
- {% trans 'Users' %} » {{ mm_user.display_name }} <{{ mm_user.first_address }}> + {% trans 'Users' %} » {{ mm_user.display_name }} <{{ mm_user.first_address }}>
diff --git a/src/postorius/templates/postorius/menu/settings_nav.html b/src/postorius/templates/postorius/menu/settings_nav.html index 795ca45..6a9d4ed 100644 --- a/src/postorius/templates/postorius/menu/settings_nav.html +++ b/src/postorius/templates/postorius/menu/settings_nav.html @@ -3,8 +3,8 @@
{% trans "Settings" %}
diff --git a/src/postorius/templates/postorius/menu/user_nav.html b/src/postorius/templates/postorius/menu/user_nav.html index 9eef812..b15558b 100644 --- a/src/postorius/templates/postorius/menu/user_nav.html +++ b/src/postorius/templates/postorius/menu/user_nav.html @@ -3,9 +3,9 @@
{% trans 'Account' %}
diff --git a/src/postorius/templates/postorius/menu/users_nav.html b/src/postorius/templates/postorius/menu/users_nav.html new file mode 100644 index 0000000..2db6455 --- /dev/null +++ b/src/postorius/templates/postorius/menu/users_nav.html @@ -0,0 +1,11 @@ +{% load url from future %} +{% load i18n %} +{% load nav_helpers %} +
+ {% trans 'Users' %} » {{ title }} + +
diff --git a/src/postorius/templates/postorius/user_profile.html b/src/postorius/templates/postorius/user_profile.html index ae335d3..1f7e224 100644 --- a/src/postorius/templates/postorius/user_profile.html +++ b/src/postorius/templates/postorius/user_profile.html @@ -9,31 +9,31 @@
- + - + - + - + - + - + - + diff --git a/src/postorius/templates/postorius/users/index.html b/src/postorius/templates/postorius/users/index.html index 4b772e7..12a21bd 100644 --- a/src/postorius/templates/postorius/users/index.html +++ b/src/postorius/templates/postorius/users/index.html @@ -1,19 +1,12 @@ {% extends "postorius/base.html" %} {% load url from future %} {% load i18n %} +{% load nav_helpers %} {% block main %} -
- {% trans 'Users' %} - {% if user.is_superuser %} - - {% endif %} -
-

{% trans 'User Index' %}

+ {% if user.is_superuser %} + {% users_nav 'user_index' 'Mailman Users' %} + {% endif %}
{% trans 'Mailman display name' %}{% trans 'Mailman display name' %} {{ mm_user.display_name}}
{% trans 'User name' %}{% trans 'User name' %} {{ user.username}}
{% trans 'Firstname' %}{% trans 'Firstname' %}  
{% trans 'Lastname' %}{% trans 'Lastname' %}  
{% trans 'IRC handle' %}{% trans 'IRC handle' %}  
{% trans 'Website' %}{% trans 'Website' %}  
{% trans 'Twitter' %}{% trans 'Twitter' %}  
diff --git a/src/postorius/templates/postorius/users/new.html b/src/postorius/templates/postorius/users/new.html index 016af30..c41e2ad 100644 --- a/src/postorius/templates/postorius/users/new.html +++ b/src/postorius/templates/postorius/users/new.html @@ -1,18 +1,13 @@ {% extends extend_template %} {% load url from future %} {% load i18n %} +{% load nav_helpers %} {% block main %} -
- {% trans 'Users' %} - {% if user.is_superuser %} - - {% endif %} -
-

{% trans "Create a new User" %}

+ {% if user.is_superuser %} + {% users_nav 'user_new' 'Create User' %} + {% endif %} + {% csrf_token %} {{ form.as_p }}
diff --git a/src/postorius/templates/postorius/users/summary.html b/src/postorius/templates/postorius/users/summary.html index 7188389..5f11c49 100644 --- a/src/postorius/templates/postorius/users/summary.html +++ b/src/postorius/templates/postorius/users/summary.html @@ -1,17 +1,39 @@ {% extends extend_template %} {% load i18n %} +{% load url from future %} +{% load nav_helpers %} {% block main %} - {% include 'postorius/menu/mm_user_nav.html' %} -

{% trans 'User Info' %}

+ {% user_nav 'user_summary' 'User Info' %} -

Display name: {{ mm_user.display_name}}

- -

Valid email addresses for this account:

+

Email addresses for this account

+

Memberships

+
+ + + + + + + + + + {% for subscription in memberships %} + + + + + + + {% endfor %} + +
{% trans 'List Name' %}{% trans 'Subscription Address' %}{% trans 'Role' %}{% trans 'Delivery Mode' %}
{{ subscription.fqdn_listname }}{{ subscription.address }}{{ subscription.role }}{{ subscription.delivery_mode }}
+ + {% endblock main %} diff --git a/src/postorius/templatetags/__init__.py b/src/postorius/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/postorius/templatetags/__init__.py diff --git a/src/postorius/templatetags/nav_helpers.py b/src/postorius/templatetags/nav_helpers.py new file mode 100644 index 0000000..1ea4865 --- /dev/null +++ b/src/postorius/templatetags/nav_helpers.py @@ -0,0 +1,46 @@ +from django.core.urlresolvers import reverse +from django import template + + +register = template.Library() + + +@register.inclusion_tag('postorius/menu/list_nav.html', takes_context=True) +def list_nav(context, current, title=None): + if title is None: + title = '' + return dict(list=context['list'], + current=current, + user=context['request'].user, + title=title) + + +@register.inclusion_tag('postorius/menu/mm_user_nav.html', takes_context=True) +def user_nav(context, current, title=None): + if title is None: + title = '' + return dict(mm_user=context['mm_user'], + current=current, + user=context['request'].user, + title=title) + + +@register.inclusion_tag('postorius/menu/users_nav.html', takes_context=True) +def users_nav(context, current, title=None): + if title is None: + title = '' + return dict(current=current, + user=context['request'].user, + title=title) + + +@register.simple_tag +def page_url(view_name, *args, **kwargs): + return reverse(view_name, *args, **kwargs) + + +@register.simple_tag(takes_context=True) +def nav_active_class(context, current, view_name): + if current == view_name: + return 'mm_active' + return '' diff --git a/src/postorius/views/__init__.py b/src/postorius/views/__init__.py index edd5024..2d0054b 100644 --- a/src/postorius/views/__init__.py +++ b/src/postorius/views/__init__.py @@ -16,5 +16,7 @@ # You should have received a copy of the GNU General Public License along with # Postorius. If not, see . -from postorius.views.views import * from postorius.views.api import * +from postorius.views.list import * +from postorius.views.settings import * +from postorius.views.user import * diff --git a/src/postorius/views/generic.py b/src/postorius/views/generic.py index c1fc639..083a7df 100644 --- a/src/postorius/views/generic.py +++ b/src/postorius/views/generic.py @@ -17,9 +17,11 @@ # Postorius. If not, see . +from django.conf import settings from django.shortcuts import render_to_response, redirect from django.template import Context, loader, RequestContext from django.views.generic import TemplateView, View +from mailmanclient import Client from postorius.models import (Domain, List, Member, MailmanUser, MailmanApiError, Mailman404Error) @@ -36,6 +38,20 @@ def _get_list(self, fqdn_listname): return List.objects.get_or_404(fqdn_listname=fqdn_listname) + def _is_list_owner(self, user, mailing_list): + if not user.is_authenticated(): + return False + if user.email in mailing_list.owners: + return True + return False + + def _is_list_moderator(self, user, mailing_list): + if not user.is_authenticated(): + return False + if user.email in mailing_list.moderators: + return True + return False + def dispatch(self, request, *args, **kwargs): # get the list object. if 'fqdn_listname' in kwargs: @@ -43,6 +59,10 @@ self.mailing_list = self._get_list(kwargs['fqdn_listname']) except MailmanApiError: return utils.render_api_error(request) + request.user.is_list_owner = self._is_list_owner( + request.user, self.mailing_list) + request.user.is_list_moderator = self._is_list_moderator( + request.user, self.mailing_list) # set the template if 'template' in kwargs: self.template = kwargs['template'] @@ -71,6 +91,28 @@ user_obj.display_name = '' user_obj.first_address = self._get_first_address(user_obj) return user_obj + + def _get_list(self, list_id): + if getattr(self, 'lists', None) is None: + self.lists = {} + if self.lists.get(list_id) is None: + self.lists[list_id] = List.objects.get(fqdn_listname=list_id) + return self.lists[list_id] + + def _get_memberships(self): + client = Client('%s/3.0' % settings.REST_SERVER, + settings.API_USER, settings.API_PASS) + memberships = [] + for a in self.mm_user.addresses: + members = client._connection.call('members/find', + {'subscriber': a}) + for m in members[1]['entries']: + mlist = self._get_list(m['list_id']) + memberships.append(dict(fqdn_listname=mlist.fqdn_listname, + role=m['role'], + delivery_mode=m['delivery_mode'], + address=a)) + return memberships def dispatch(self, request, *args, **kwargs): # get the user object. diff --git a/src/postorius/views/list.py b/src/postorius/views/list.py new file mode 100644 index 0000000..d57ca48 --- /dev/null +++ b/src/postorius/views/list.py @@ -0,0 +1,564 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 1998-2012 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 . + + +import re +import sys +import json +import logging + + +from django.conf import settings +from django.contrib import messages +from django.contrib.auth import logout, authenticate, login +from django.contrib.auth.decorators import (login_required, + permission_required, + user_passes_test) +from django.contrib.auth.forms import (AuthenticationForm, PasswordResetForm, + SetPasswordForm, PasswordChangeForm) +from django.contrib.auth.models import User +from django.core.urlresolvers import reverse +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render_to_response, redirect +from django.template import Context, loader, RequestContext +from django.utils.decorators import method_decorator +from django.utils.translation import gettext as _ +from urllib2 import HTTPError + +from mailmanclient import Client +from postorius import utils +from postorius.models import (Domain, List, Member, MailmanUser, + MailmanApiError, Mailman404Error) +from postorius.forms import * +from postorius.auth.decorators import * +from postorius.views.generic import MailingListView, MailmanUserView + + +logger = logging.getLogger(__name__) + + +class ListMembersView(MailingListView): + """Display all members of a given list. + """ + + @method_decorator(list_owner_required) + def post(self, request, fqdn_listname): + if 'owner_email' in request.POST: + owner_form = NewOwnerForm(request.POST) + if owner_form.is_valid(): + self.mailing_list.add_owner( + owner_form.cleaned_data['owner_email']) + else: + owner_form = NewOwnerForm() + if 'moderator_email' in request.POST: + moderator_form = NewModeratorForm(request.POST) + if moderator_form.is_valid(): + self.mailing_list.add_moderator( + moderator_form.cleaned_data['moderator_email']) + else: + moderator_form = NewModeratorForm() + return render_to_response('postorius/lists/members.html', + {'list': self.mailing_list, + 'owner_form': owner_form, + 'moderator_form': moderator_form}, + context_instance=RequestContext(request)) + + @method_decorator(list_owner_required) + def get(self, request, fqdn_listname): + owner_form = NewOwnerForm() + moderator_form = NewModeratorForm() + return render_to_response('postorius/lists/members.html', + {'list': self.mailing_list, + 'owner_form': owner_form, + 'moderator_form': moderator_form}, + context_instance=RequestContext(request)) + + +class ListMetricsView(MailingListView): + """Shows common list metrics. + """ + + @method_decorator(list_owner_required) + def get(self, request, fqdn_listname): + return render_to_response('postorius/lists/metrics.html', + {'list': self.mailing_list}, + context_instance=RequestContext(request)) + + +class ListSummaryView(MailingListView): + """Shows common list metrics. + """ + + def get(self, request, fqdn_listname): + user_email = getattr(request.user, 'email', None) + return render_to_response( + 'postorius/lists/summary.html', + {'list': self.mailing_list, + 'subscribe_form': ListSubscribe(initial={'email': user_email})}, + context_instance=RequestContext(request)) + + +class ListSubsribeView(MailingListView): + """Subscribe a mailing list.""" + + @method_decorator(login_required) + def post(self, request, fqdn_listname): + try: + form = ListSubscribe(request.POST) + if form.is_valid(): + email = request.POST.get('email') + self.mailing_list.subscribe(email) + messages.success( + request, 'You are subscribed to %s.' % + self.mailing_list.fqdn_listname) + else: + messages.error(request, 'Something went wrong. ' + 'Please try again.') + except MailmanApiError: + return utils.render_api_error(request) + except HTTPError, e: + messages.error(request, e.msg) + return redirect('list_summary', self.mailing_list.fqdn_listname) + + +class ListUnsubscribeView(MailingListView): + """Unsubscribe from a mailing list.""" + + @method_decorator(login_required) + def get(self, request, *args, **kwargs): + email = kwargs['email'] + try: + self.mailing_list.unsubscribe(email) + messages.success(request, + '%s has been unsubscribed from this list.' % + email) + except MailmanApiError: + return utils.render_api_error(request) + except ValueError, e: + messages.error(request, e) + return redirect('list_summary', self.mailing_list.fqdn_listname) + + +class ListMassSubsribeView(MailingListView): + """Mass subscription.""" + + @method_decorator(list_owner_required) + def get(self, request, *args, **kwargs): + form = ListMassSubscription() + return render_to_response('postorius/lists/mass_subscribe.html', + {'form': form, 'list': self.mailing_list}, + context_instance=RequestContext(request)) + + def post(self, request, *args, **kwargs): + form = ListMassSubscription(request.POST) + if not form.is_valid(): + messages.error(request, 'Please fill out the form correctly.') + else: + emails = request.POST["emails"].splitlines() + for email in emails: + parts = email.split('@') + if len(parts) != 2 or '.' not in parts[1]: + messages.error(request, + 'The email address %s is not valid.' % + email) + else: + try: + self.mailing_list.subscribe(address=email) + messages.success( + request, + 'The address %s has been subscribed to %s.' % + (email, self.mailing_list.fqdn_listname)) + except MailmanApiError: + return utils.render_api_error(request) + except HTTPError, e: + messages.error(request, e) + return redirect('mass_subscribe', self.mailing_list.fqdn_listname) + + +@login_required +@user_passes_test(lambda u: u.is_superuser) +def list_new(request, template='postorius/lists/new.html'): + """ + Add a new mailing list. + If the request to the function is a GET request an empty form for + creating a new list will be displayed. If the request method is + POST the form will be evaluated. If the form is considered + correct the list gets created and otherwise the form with the data + filled in before the last POST request is returned. The user must + be logged in to create a new list. + """ + mailing_list = None + if request.method == 'POST': + try: + domains = Domain.objects.all() + except MailmanApiError: + return utils.render_api_error(request) + choosable_domains = [("", _("Choose a Domain"))] + for domain in domains: + choosable_domains.append((domain.mail_host, + domain.mail_host)) + form = ListNew(choosable_domains, request.POST) + if form.is_valid(): + #grab domain + domain = Domain.objects.get_or_404( + mail_host=form.cleaned_data['mail_host']) + #creating the list + try: + mailing_list = domain.create_list( + form.cleaned_data['listname']) + list_settings = mailing_list.settings + list_settings["description"] = form.cleaned_data['description'] + list_settings["owner_address"] = \ + form.cleaned_data['list_owner'] + list_settings["advertised"] = form.cleaned_data['advertised'] + list_settings.save() + messages.success(request, _("List created")) + return redirect("list_summary", + fqdn_listname=mailing_list.fqdn_listname) + #TODO catch correct Error class: + except HTTPError, e: + messages.error(request, e) + return render_to_response( + 'postorius/errors/generic.html', + {'error': e}, context_instance=RequestContext(request)) + else: + messages.success(_("New List created")) + else: + try: + domains = Domain.objects.all() + except MailmanApiError: + return utils.render_api_error(request) + choosable_domains = [("", _("Choose a Domain"))] + for domain in domains: + choosable_domains.append((domain.mail_host, domain.mail_host)) + form = ListNew(choosable_domains, + initial={'list_owner': request.user.email}) + return render_to_response(template, {'form': form}, + context_instance=RequestContext(request)) + + +def list_index(request, template='postorius/lists/index.html'): + """Show a table of all public mailing lists. + """ + lists = [] + error = None + domain = None + only_public = True + if request.user.is_superuser: + only_public = False + try: + lists = List.objects.all(only_public=only_public) + except MailmanApiError: + return utils.render_api_error(request) + if request.method == 'POST': + return redirect("list_summary", fqdn_listname=request.POST["list"]) + else: + return render_to_response(template, + {'error': error, + 'lists': lists}, + context_instance=RequestContext(request)) + + +@login_required +def list_subscriptions(request, option=None, fqdn_listname=None, + user_email=None, + template='postorius/lists/subscriptions.html', + *args, **kwargs): + """ + Display the information there is available for a list. This + function also enables subscribing or unsubscribing a user to a + list. For the latter two different forms are available for the + user to fill in which are evaluated in this function. + """ + # create Values for Template usage + message = None + error = None + form_subscribe = None + form_unsubscribe = None + if request.POST.get('fqdn_listname', ''): + fqdn_listname = request.POST.get('fqdn_listname', '') + # connect REST and catch issues getting the list + try: + the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + except AttributeError, e: + return render_to_response('postorius/errors/generic.html', + {'error': "REST API not found / Offline"}, + context_instance=RequestContext(request)) + # process submitted form + if request.method == 'POST': + form = False + # The form enables both subscribe and unsubscribe. As a result + # we must find out which was the case. + if request.POST.get('name', '') == "subscribe": + form = ListSubscribe(request.POST) + if form.is_valid(): + # the form was valid so try to subscribe the user + try: + email = form.cleaned_data['email'] + response = the_list.subscribe( + address=email, + display_name=form.cleaned_data.get('display_name', '')) + return render_to_response( + 'postorius/lists/summary.html', + {'list': the_list, 'option': option, + 'message': _("Subscribed ") + email}, + context_instance=RequestContext(request)) + except HTTPError, e: + return render_to_response( + 'postorius/errors/generic.html', + {'error': e}, + context_instance=RequestContext(request)) + else: # invalid subscribe form + form_subscribe = form + form_unsubscribe = ListUnsubscribe( + initial={'fqdn_listname': fqdn_listname, + 'name': 'unsubscribe'}) + elif request.POST.get('name', '') == "unsubscribe": + form = ListUnsubscribe(request.POST) + if form.is_valid(): + # the form was valid so try to unsubscribe the user + try: + email = form.cleaned_data["email"] + response = the_list.unsubscribe(address=email) + return render_to_response( + 'postorius/lists/summary.html', + {'list': the_list, + 'message': _("Unsubscribed ") + email}, + context_instance=RequestContext(request)) + except ValueError, e: + return render_to_response( + 'postorius/errors/generic.html', + {'error': e}, + context_instance=RequestContext(request)) + else: # invalid unsubscribe form + form_subscribe = ListSubscribe( + initial={'fqdn_listname': fqdn_listname, + 'option': option, + 'name': 'subscribe'}) + form_unsubscribe = ListUnsubscribe(request.POST) + else: + # the request was a GET request so set the two forms to empty + # forms + if option == "subscribe" or not option: + form_subscribe = ListSubscribe( + initial={'fqdn_listname': fqdn_listname, + 'email': request.user.username, + 'name': 'subscribe'}) + if option == "unsubscribe" or not option: + form_unsubscribe = ListUnsubscribe( + initial={'fqdn_listname': fqdn_listname, + 'email': request.user.username, + 'name': 'unsubscribe'}) + the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + return render_to_response(template, + {'form_subscribe': form_subscribe, + 'form_unsubscribe': form_unsubscribe, + 'message': message, + 'error': error, + 'list': the_list}, + context_instance=RequestContext(request)) + + +@list_owner_required +def list_delete(request, fqdn_listname): + """Deletes a list but asks for confirmation first. + """ + try: + the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + except MailmanApiError: + return utils.render_api_error(request) + if request.method == 'POST': + the_list.delete() + # let the user return to the list index page + lists = List.objects.all() + return redirect("list_index") + else: + submit_url = reverse('list_delete', + kwargs={'fqdn_listname': fqdn_listname}) + cancel_url = reverse('list_index',) + return render_to_response( + 'postorius/lists/confirm_delete.html', + {'submit_url': submit_url, 'cancel_url': cancel_url, + 'list': the_list}, + context_instance=RequestContext(request)) + + +@list_owner_required +def list_held_messages(request, fqdn_listname): + """Shows a list of held messages. + """ + try: + the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + except MailmanApiError: + return utils.render_api_error(request) + return render_to_response('postorius/lists/held_messages.html', + {'list': the_list}, + context_instance=RequestContext(request)) + + +@list_owner_required +def accept_held_message(request, fqdn_listname, msg_id): + """Accepts a held message. + """ + try: + the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + the_list.accept_message(msg_id) + except MailmanApiError: + return utils.render_api_error(request) + except HTTPError, e: + messages.error(request, e.msg) + return redirect('list_held_messages', the_list.fqdn_listname) + messages.success(request, 'The message has been accepted.') + return redirect('list_held_messages', the_list.fqdn_listname) + + +@list_owner_required +def discard_held_message(request, fqdn_listname, msg_id): + """Accepts a held message. + """ + try: + the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + the_list.discard_message(msg_id) + except MailmanApiError: + return utils.render_api_error(request) + except HTTPError, e: + messages.error(request, e.msg) + return redirect('list_held_messages', the_list.fqdn_listname) + messages.success(request, 'The message has been discarded.') + return redirect('list_held_messages', the_list.fqdn_listname) + + +@list_owner_required +def defer_held_message(request, fqdn_listname, msg_id): + """Accepts a held message. + """ + try: + the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + the_list.defer_message(msg_id) + except MailmanApiError: + return utils.render_api_error(request) + except HTTPError, e: + messages.error(request, e.msg) + return redirect('list_held_messages', the_list.fqdn_listname) + messages.success(request, 'The message has been defered.') + return redirect('list_held_messages', the_list.fqdn_listname) + + +@list_owner_required +def reject_held_message(request, fqdn_listname, msg_id): + """Accepts a held message. + """ + try: + the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + the_list.reject_message(msg_id) + except MailmanApiError: + return utils.render_api_error(request) + except HTTPError, e: + messages.error(request, e.msg) + return redirect('list_held_messages', the_list.fqdn_listname) + messages.success(request, 'The message has been rejected.') + return redirect('list_held_messages', the_list.fqdn_listname) + + +@list_owner_required +def list_settings(request, fqdn_listname=None, visible_section=None, + visible_option=None, + template='postorius/lists/settings.html'): + """ + View and edit the settings of a list. + The function requires the user to be logged in and have the + permissions necessary to perform the action. + + Use // + to show only parts of the settings + is optional / is used to differ in between section and option might + result in using //option + """ + message = "" + logger.debug(visible_section) + if visible_section is None: + visible_section = 'List Identity' + form_sections = [] + try: + the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + except MailmanApiError: + return utils.render_api_error(request) + #collect all Form sections for the links: + temp = ListSettings('', '') + for section in temp.layout: + try: + form_sections.append((section[0], + temp.section_descriptions[section[0]])) + except KeyError, e: + error = e + del temp + # Save a Form Processed by POST + if request.method == 'POST': + form = ListSettings(visible_section, visible_option, data=request.POST) + form.truncate() + if form.is_valid(): + list_settings = the_list.settings + for key in form.fields.keys(): + list_settings[key] = form.cleaned_data[key] + list_settings.save() + message = _("The list has been updated.") + else: + message = _("Validation Error - The list has not been updated.") + else: + #Provide a form with existing values + #create form and process layout into form.layout + form = ListSettings(visible_section, visible_option, data=None) + #create a Dict of all settings which are used in the form + used_settings = {} + for section in form.layout: + for option in section[1:]: + used_settings[option] = the_list.settings[option] + if option == u'acceptable_aliases': + used_settings[option] = '\n'.join(used_settings[option]) + # recreate the form using the settings + form = ListSettings(visible_section, visible_option, + data=used_settings) + form.truncate() + return render_to_response(template, + {'form': form, + 'form_sections': form_sections, + 'message': message, + 'list': the_list, + 'visible_option': visible_option, + 'visible_section': visible_section}, + context_instance=RequestContext(request)) + + +@login_required +def user_mailmansettings(request): + try: + the_user = MailmanUser.objects.get(address=request.user.email) + except MailmanApiError: + return utils.render_api_error(request) + + settingsform = MembershipSettings() + return render_to_response('postorius/user_mailmansettings.html', + {'mm_user': the_user, + 'settingsform': settingsform}, + context_instance=RequestContext(request)) + + +@login_required +def membership_settings(request): + """Display a list of all memberships. + """ diff --git a/src/postorius/views/settings.py b/src/postorius/views/settings.py new file mode 100644 index 0000000..97d65bd --- /dev/null +++ b/src/postorius/views/settings.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 1998-2012 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 . + + +import re +import sys +import json +import logging + + +from django.conf import settings +from django.contrib import messages +from django.contrib.auth import logout, authenticate, login +from django.contrib.auth.decorators import (login_required, + permission_required, + user_passes_test) +from django.contrib.auth.forms import (AuthenticationForm, PasswordResetForm, + SetPasswordForm, PasswordChangeForm) +from django.contrib.auth.models import User +from django.core.urlresolvers import reverse +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render_to_response, redirect +from django.template import Context, loader, RequestContext +from django.utils.decorators import method_decorator +from django.utils.translation import gettext as _ +from urllib2 import HTTPError + +from mailmanclient import Client +from postorius import utils +from postorius.models import (Domain, List, Member, MailmanUser, + MailmanApiError, Mailman404Error) +from postorius.forms import * +from postorius.auth.decorators import * +from postorius.views.generic import MailingListView, MailmanUserView + + +logger = logging.getLogger(__name__) + + +@login_required +@user_passes_test(lambda u: u.is_superuser) +def site_settings(request): + return render_to_response('postorius/site_settings.html', + context_instance=RequestContext(request)) + + +@login_required +@user_passes_test(lambda u: u.is_superuser) +def domain_index(request): + try: + existing_domains = Domain.objects.all() + except MailmanApiError: + return utils.render_api_error(request) + return render_to_response('postorius/domain_index.html', + {'domains': existing_domains}, + context_instance=RequestContext(request)) + + +@login_required +@user_passes_test(lambda u: u.is_superuser) +def domain_new(request): + message = None + if request.method == 'POST': + form = DomainNew(request.POST) + if form.is_valid(): + domain = Domain(mail_host=form.cleaned_data['mail_host'], + base_url=form.cleaned_data['web_host'], + description=form.cleaned_data['description']) + try: + domain.save() + except MailmanApiError: + return utils.render_api_error(request) + except HTTPError, e: + messages.error(request, e) + else: + messages.success(request, _("New Domain registered")) + return redirect("domain_index") + else: + form = DomainNew() + return render_to_response('postorius/domain_new.html', + {'form': form, 'message': message}, + context_instance=RequestContext(request)) diff --git a/src/postorius/views/user.py b/src/postorius/views/user.py new file mode 100644 index 0000000..386a61b --- /dev/null +++ b/src/postorius/views/user.py @@ -0,0 +1,274 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 1998-2012 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 . + + +import re +import sys +import json +import logging + + +from django.conf import settings +from django.contrib import messages +from django.contrib.auth import logout, authenticate, login +from django.contrib.auth.decorators import (login_required, + permission_required, + user_passes_test) +from django.contrib.auth.forms import (AuthenticationForm, PasswordResetForm, + SetPasswordForm, PasswordChangeForm) +from django.contrib.auth.models import User +from django.core.urlresolvers import reverse +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render_to_response, redirect +from django.template import Context, loader, RequestContext +from django.utils.decorators import method_decorator +from django.utils.translation import gettext as _ +from urllib2 import HTTPError + +from mailmanclient import Client +from postorius import utils +from postorius.models import (Domain, List, Member, MailmanUser, + MailmanApiError, Mailman404Error) +from postorius.forms import * +from postorius.auth.decorators import * +from postorius.views.generic import MailingListView, MailmanUserView + + +logger = logging.getLogger(__name__) + + +@login_required +def user_settings(request, tab="membership", + template='postorius/user_settings.html', + fqdn_listname=None): + """ + Change the user or the membership settings. + The user must be logged in to be allowed to change any settings. + TODO: * add CSS to display tabs ?? + * add missing functionality in REST server and client and + change to the correct calls here + """ + member = request.user.username + message = '' + form = None + the_list = None + membership_lists = [] + + try: + c = Client('%s/3.0' % settings.REST_SERVER, settings.API_USER, + settings.API_PASS) + if tab == "membership": + if fqdn_listname: + the_list = List.objects.get(fqdn_listname=fqdn_listname) + user_object = the_list.get_member(member) + else: + message = ("") + for mlist in List.objects.all(): + try: + mlist.get_member(member) + membership_lists.append(mlist) + except: + pass + else: + # address_choices for the 'address' field must be a list of + # tuples of length 2 + raise Exception("") + address_choices = [[addr, addr] for addr in user_object.address] + except AttributeError, e: + return render_to_response( + 'postorius/errors/generic.html', + {'error': str(e) + "REST API not found / Offline"}, + context_instance=RequestContext(request)) + except ValueError, e: + return render_to_response('postorius/errors/generic.html', + {'error': e}, + context_instance=RequestContext(request)) + except HTTPError, e: + return render_to_response( + 'postorius/errors/generic.html', + {'error': _("List ") + fqdn_listname + _(" does not exist")}, + context_instance=RequestContext(request)) + #----------------------------------------------------------------- + if request.method == 'POST': + # The form enables both user and member settings. As a result + # we must find out which was the case. + raise Exception("Please fix bug prior submitting the form") + if tab == "membership": + form = MembershipSettings(request.POST) + if form.is_valid(): + member_object = c.get_member(member, request.GET["list"]) + member_object.update(request.POST) + message = "The membership settings have been updated." + else: + # the post request came from the user tab + # the 'address' field need choices as a tuple of length 2 + addr_choices = [[request.POST["address"], request.POST["address"]]] + form = UserSettings(addr_choices, request.POST) + if form.is_valid(): + user_object.update(request.POST) + # to get the full list of addresses we need to + # reinstantiate the form with all the addresses + # TODO: should return the correct settings from the DB, + # not just the address_choices (add mock data to _User + # class and make the call with 'user_object.info') + form = UserSettings(address_choices) + message = "The user settings have been updated." + + else: + if tab == "membership" and fqdn_listname: + if fqdn_listname: + # TODO : fix LP:821069 in mailman.client + the_list = List.objects.get(fqdn_listname=fqdn_listname) + member_object = the_list.get_member(member) + # TODO: add delivery_mode and deliver_status from a + # list of tuples at one point, currently we hard code + # them in forms.py + # instantiate the form with the correct member info + """ + acknowledge_posts + hide_address + receive_list_copy + receive_own_postings + delivery_mode + delivery_status + """ + data = {} + form = MembershipSettings(data) + elif tab == "user": + # TODO: should return the correct settings from the DB, + # not just the address_choices (add mock data to _User + # class and make the call with 'user_object._info') The 'language' + # field must also be added as a list of tuples with correct + # values (is currently hard coded in forms.py). + data = {} # Todo https://bugs.launchpad.net/mailman/+bug/821438 + form = UserSettings(data) + + return render_to_response(template, + {'form': form, + 'tab': tab, + 'list': the_list, + 'membership_lists': membership_lists, + 'message': message, + 'member': member}, + context_instance=RequestContext(request)) + + +class UserSummaryView(MailmanUserView): + """Shows a summary of a user. + """ + + @method_decorator(user_passes_test(lambda u: u.is_superuser)) + def get(self, request, user_id): + settingsform = MembershipSettings() + memberships = self._get_memberships() + return render_to_response('postorius/users/summary.html', + {'mm_user': self.mm_user, + 'settingsform': settingsform, + 'memberships': memberships}, + context_instance=RequestContext(request)) + + +class UserSubscriptionsView(MailmanUserView): + """Shows the subscriptions of a user. + """ + + def get(self, request): + memberships = self._get_memberships() + return render_to_response('postorius/user_subscriptions.html', + {'memberships': memberships}, + context_instance=RequestContext(request)) + + +@user_passes_test(lambda u: u.is_superuser) +def user_index(request, template='postorius/users/index.html'): + """Show a table of all users. + """ + error = None + try: + mm_users = MailmanUser.objects.all() + except MailmanApiError: + return utils.render_api_error(request) + return render_to_response(template, + {'error': error, + 'mm_users': mm_users}, + context_instance=RequestContext(request)) + + +@user_passes_test(lambda u: u.is_superuser) +def user_new(request): + message = None + if request.method == 'POST': + form = UserNew(request.POST) + if form.is_valid(): + user = MailmanUser(display_name=form.cleaned_data['display_name'], + email=form.cleaned_data['email'], + password=form.cleaned_data['password']) + try: + user.save() + except MailmanApiError: + return utils.render_api_error(request) + except HTTPError, e: + messages.error(request, e) + else: + messages.success(request, _("New User registered")) + return redirect("user_index") + else: + form = UserNew() + return render_to_response('postorius/users/new.html', + {'form': form, 'message': message}, + context_instance=RequestContext(request)) + + +def user_logout(request): + logout(request) + return redirect('user_login') + + +def user_login(request, template='postorius/login.html'): + if request.method == 'POST': + form = AuthenticationForm(request.POST) + user = authenticate(username=request.POST.get('username'), + password=request.POST.get('password')) + if user is not None: + logger.debug(user) + if user.is_active: + login(request, user) + return redirect(request.GET.get('next', 'list_index')) + else: + form = AuthenticationForm() + return render_to_response(template, {'form': form}, + context_instance=RequestContext(request)) + + +@login_required() +def user_profile(request, user_email=None): + if not request.user.is_authenticated(): + return redirect('user_login') + #try: + # the_user = User.objects.get(email=user_email) + #except MailmanApiError: + # return utils.render_api_error(request) + return render_to_response('postorius/user_profile.html', + # {'mm_user': the_user}, + context_instance=RequestContext(request)) + + +@login_required +def user_todos(request): + return render_to_response('postorius/user_todos.html', + context_instance=RequestContext(request)) diff --git a/src/postorius/views/views.py b/src/postorius/views/views.py index 950ac64..28644dc 100644 --- a/src/postorius/views/views.py +++ b/src/postorius/views/views.py @@ -50,776 +50,3 @@ logger = logging.getLogger(__name__) - - -@login_required -@user_passes_test(lambda u: u.is_superuser) -def site_settings(request): - return render_to_response('postorius/site_settings.html', - context_instance=RequestContext(request)) - - -@login_required -@user_passes_test(lambda u: u.is_superuser) -def domain_index(request): - try: - existing_domains = Domain.objects.all() - except MailmanApiError: - return utils.render_api_error(request) - return render_to_response('postorius/domain_index.html', - {'domains': existing_domains}, - context_instance=RequestContext(request)) - - -@login_required -@user_passes_test(lambda u: u.is_superuser) -def domain_new(request): - message = None - if request.method == 'POST': - form = DomainNew(request.POST) - if form.is_valid(): - domain = Domain(mail_host=form.cleaned_data['mail_host'], - base_url=form.cleaned_data['web_host'], - description=form.cleaned_data['description']) - try: - domain.save() - except MailmanApiError: - return utils.render_api_error(request) - except HTTPError, e: - messages.error(request, e) - else: - messages.success(request, _("New Domain registered")) - return redirect("domain_index") - else: - form = DomainNew() - return render_to_response('postorius/domain_new.html', - {'form': form, 'message': message}, - context_instance=RequestContext(request)) - - -class ListMembersView(MailingListView): - """Display all members of a given list. - """ - - @method_decorator(list_owner_required) - def get(self, request, fqdn_listname): - return render_to_response('postorius/lists/members.html', - {'list': self.mailing_list}, - context_instance=RequestContext(request)) - - -class ListMetricsView(MailingListView): - """Shows common list metrics. - """ - - @method_decorator(list_owner_required) - def get(self, request, fqdn_listname): - return render_to_response('postorius/lists/metrics.html', - {'list': self.mailing_list}, - context_instance=RequestContext(request)) - - -class ListSummaryView(MailingListView): - """Shows common list metrics. - """ - - def get(self, request, fqdn_listname): - user_email = getattr(request.user, 'email', None) - return render_to_response( - 'postorius/lists/summary.html', - {'list': self.mailing_list, - 'subscribe_form': ListSubscribe(initial={'email': user_email})}, - context_instance=RequestContext(request)) - - -class ListSubsribeView(MailingListView): - """Subscribe a mailing list.""" - - @method_decorator(login_required) - def post(self, request, fqdn_listname): - try: - form = ListSubscribe(request.POST) - if form.is_valid(): - email = request.POST.get('email') - self.mailing_list.subscribe(email) - messages.success( - request, 'You are subscribed to %s.' % - self.mailing_list.fqdn_listname) - else: - messages.error(request, 'Something went wrong. ' - 'Please try again.') - except MailmanApiError: - return utils.render_api_error(request) - except HTTPError, e: - messages.error(request, e.msg) - return redirect('list_summary', self.mailing_list.fqdn_listname) - - -class ListUnsubscribeView(MailingListView): - """Unsubscribe from a mailing list.""" - - @method_decorator(login_required) - def get(self, request, *args, **kwargs): - email = kwargs['email'] - try: - self.mailing_list.unsubscribe(email) - messages.success(request, - '%s has been unsubscribed from this list.' % - email) - except MailmanApiError: - return utils.render_api_error(request) - except ValueError, e: - messages.error(request, e) - return redirect('list_summary', self.mailing_list.fqdn_listname) - - -class ListMassSubsribeView(MailingListView): - """Mass subscription.""" - - @method_decorator(list_owner_required) - def get(self, request, *args, **kwargs): - form = ListMassSubscription() - return render_to_response('postorius/lists/mass_subscribe.html', - {'form': form, 'list': self.mailing_list}, - context_instance=RequestContext(request)) - - def post(self, request, *args, **kwargs): - form = ListMassSubscription(request.POST) - if not form.is_valid(): - messages.error(request, 'Please fill out the form correctly.') - else: - emails = request.POST["emails"].splitlines() - for email in emails: - parts = email.split('@') - if len(parts) != 2 or '.' not in parts[1]: - messages.error(request, - 'The email address %s is not valid.' % - email) - else: - try: - self.mailing_list.subscribe(address=email) - messages.success( - request, - 'The address %s has been subscribed to %s.' % - (email, self.mailing_list.fqdn_listname)) - except MailmanApiError: - return utils.render_api_error(request) - except HTTPError, e: - messages.error(request, e) - return redirect('mass_subscribe', self.mailing_list.fqdn_listname) - - -@login_required -@user_passes_test(lambda u: u.is_superuser) -def list_new(request, template='postorius/lists/new.html'): - """ - Add a new mailing list. - If the request to the function is a GET request an empty form for - creating a new list will be displayed. If the request method is - POST the form will be evaluated. If the form is considered - correct the list gets created and otherwise the form with the data - filled in before the last POST request is returned. The user must - be logged in to create a new list. - """ - mailing_list = None - if request.method == 'POST': - try: - domains = Domain.objects.all() - except MailmanApiError: - return utils.render_api_error(request) - choosable_domains = [("", _("Choose a Domain"))] - for domain in domains: - choosable_domains.append((domain.mail_host, - domain.mail_host)) - form = ListNew(choosable_domains, request.POST) - if form.is_valid(): - #grab domain - domain = Domain.objects.get_or_404( - mail_host=form.cleaned_data['mail_host']) - #creating the list - try: - mailing_list = domain.create_list( - form.cleaned_data['listname']) - list_settings = mailing_list.settings - list_settings["description"] = form.cleaned_data['description'] - list_settings["owner_address"] = \ - form.cleaned_data['list_owner'] - list_settings["advertised"] = form.cleaned_data['advertised'] - list_settings.save() - messages.success(request, _("List created")) - return redirect("list_summary", - fqdn_listname=mailing_list.fqdn_listname) - #TODO catch correct Error class: - except HTTPError, e: - messages.error(request, e) - return render_to_response( - 'postorius/errors/generic.html', - {'error': e}, context_instance=RequestContext(request)) - else: - messages.success(_("New List created")) - else: - try: - domains = Domain.objects.all() - except MailmanApiError: - return utils.render_api_error(request) - choosable_domains = [("", _("Choose a Domain"))] - for domain in domains: - choosable_domains.append((domain.mail_host, domain.mail_host)) - form = ListNew(choosable_domains, - initial={'list_owner': request.user.email}) - return render_to_response(template, {'form': form}, - context_instance=RequestContext(request)) - - -def list_index(request, template='postorius/lists/index.html'): - """Show a table of all public mailing lists. - """ - lists = [] - error = None - domain = None - only_public = True - if request.user.is_superuser: - only_public = False - try: - lists = List.objects.all(only_public=only_public) - except MailmanApiError: - return utils.render_api_error(request) - if request.method == 'POST': - return redirect("list_summary", fqdn_listname=request.POST["list"]) - else: - return render_to_response(template, - {'error': error, - 'lists': lists}, - context_instance=RequestContext(request)) - - -@login_required -def list_subscriptions(request, option=None, fqdn_listname=None, - user_email=None, - template='postorius/lists/subscriptions.html', - *args, **kwargs): - """ - Display the information there is available for a list. This - function also enables subscribing or unsubscribing a user to a - list. For the latter two different forms are available for the - user to fill in which are evaluated in this function. - """ - # create Values for Template usage - message = None - error = None - form_subscribe = None - form_unsubscribe = None - if request.POST.get('fqdn_listname', ''): - fqdn_listname = request.POST.get('fqdn_listname', '') - # connect REST and catch issues getting the list - try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) - except AttributeError, e: - return render_to_response('postorius/errors/generic.html', - {'error': "REST API not found / Offline"}, - context_instance=RequestContext(request)) - # process submitted form - if request.method == 'POST': - form = False - # The form enables both subscribe and unsubscribe. As a result - # we must find out which was the case. - if request.POST.get('name', '') == "subscribe": - form = ListSubscribe(request.POST) - if form.is_valid(): - # the form was valid so try to subscribe the user - try: - email = form.cleaned_data['email'] - response = the_list.subscribe( - address=email, - display_name=form.cleaned_data.get('display_name', '')) - return render_to_response( - 'postorius/lists/summary.html', - {'list': the_list, 'option': option, - 'message': _("Subscribed ") + email}, - context_instance=RequestContext(request)) - except HTTPError, e: - return render_to_response( - 'postorius/errors/generic.html', - {'error': e}, - context_instance=RequestContext(request)) - else: # invalid subscribe form - form_subscribe = form - form_unsubscribe = ListUnsubscribe( - initial={'fqdn_listname': fqdn_listname, - 'name': 'unsubscribe'}) - elif request.POST.get('name', '') == "unsubscribe": - form = ListUnsubscribe(request.POST) - if form.is_valid(): - # the form was valid so try to unsubscribe the user - try: - email = form.cleaned_data["email"] - response = the_list.unsubscribe(address=email) - return render_to_response( - 'postorius/lists/summary.html', - {'list': the_list, - 'message': _("Unsubscribed ") + email}, - context_instance=RequestContext(request)) - except ValueError, e: - return render_to_response( - 'postorius/errors/generic.html', - {'error': e}, - context_instance=RequestContext(request)) - else: # invalid unsubscribe form - form_subscribe = ListSubscribe( - initial={'fqdn_listname': fqdn_listname, - 'option': option, - 'name': 'subscribe'}) - form_unsubscribe = ListUnsubscribe(request.POST) - else: - # the request was a GET request so set the two forms to empty - # forms - if option == "subscribe" or not option: - form_subscribe = ListSubscribe( - initial={'fqdn_listname': fqdn_listname, - 'email': request.user.username, - 'name': 'subscribe'}) - if option == "unsubscribe" or not option: - form_unsubscribe = ListUnsubscribe( - initial={'fqdn_listname': fqdn_listname, - 'email': request.user.username, - 'name': 'unsubscribe'}) - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) - return render_to_response(template, - {'form_subscribe': form_subscribe, - 'form_unsubscribe': form_unsubscribe, - 'message': message, - 'error': error, - 'list': the_list}, - context_instance=RequestContext(request)) - - -@list_owner_required -def list_delete(request, fqdn_listname): - """Deletes a list but asks for confirmation first. - """ - try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) - except MailmanApiError: - return utils.render_api_error(request) - if request.method == 'POST': - the_list.delete() - # let the user return to the list index page - lists = List.objects.all() - return redirect("list_index") - else: - submit_url = reverse('list_delete', - kwargs={'fqdn_listname': fqdn_listname}) - cancel_url = reverse('list_index',) - return render_to_response( - 'postorius/lists/confirm_delete.html', - {'submit_url': submit_url, 'cancel_url': cancel_url, - 'list': the_list}, - context_instance=RequestContext(request)) - - -@list_owner_required -def list_held_messages(request, fqdn_listname): - """Shows a list of held messages. - """ - try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) - except MailmanApiError: - return utils.render_api_error(request) - return render_to_response('postorius/lists/held_messages.html', - {'list': the_list}, - context_instance=RequestContext(request)) - - -@list_owner_required -def accept_held_message(request, fqdn_listname, msg_id): - """Accepts a held message. - """ - try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) - the_list.accept_message(msg_id) - except MailmanApiError: - return utils.render_api_error(request) - except HTTPError, e: - messages.error(request, e.msg) - return redirect('list_held_messages', the_list.fqdn_listname) - messages.success(request, 'The message has been accepted.') - return redirect('list_held_messages', the_list.fqdn_listname) - - -@list_owner_required -def discard_held_message(request, fqdn_listname, msg_id): - """Accepts a held message. - """ - try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) - the_list.discard_message(msg_id) - except MailmanApiError: - return utils.render_api_error(request) - except HTTPError, e: - messages.error(request, e.msg) - return redirect('list_held_messages', the_list.fqdn_listname) - messages.success(request, 'The message has been discarded.') - return redirect('list_held_messages', the_list.fqdn_listname) - - -@list_owner_required -def defer_held_message(request, fqdn_listname, msg_id): - """Accepts a held message. - """ - try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) - the_list.defer_message(msg_id) - except MailmanApiError: - return utils.render_api_error(request) - except HTTPError, e: - messages.error(request, e.msg) - return redirect('list_held_messages', the_list.fqdn_listname) - messages.success(request, 'The message has been defered.') - return redirect('list_held_messages', the_list.fqdn_listname) - - -@list_owner_required -def reject_held_message(request, fqdn_listname, msg_id): - """Accepts a held message. - """ - try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) - the_list.reject_message(msg_id) - except MailmanApiError: - return utils.render_api_error(request) - except HTTPError, e: - messages.error(request, e.msg) - return redirect('list_held_messages', the_list.fqdn_listname) - messages.success(request, 'The message has been rejected.') - return redirect('list_held_messages', the_list.fqdn_listname) - - -@list_owner_required -def list_settings(request, fqdn_listname=None, visible_section=None, - visible_option=None, - template='postorius/lists/settings.html'): - """ - View and edit the settings of a list. - The function requires the user to be logged in and have the - permissions necessary to perform the action. - - Use // - to show only parts of the settings - is optional / is used to differ in between section and option might - result in using //option - """ - message = "" - logger.debug(visible_section) - if visible_section is None: - visible_section = 'List Identity' - form_sections = [] - try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) - except MailmanApiError: - return utils.render_api_error(request) - #collect all Form sections for the links: - temp = ListSettings('', '') - for section in temp.layout: - try: - form_sections.append((section[0], - temp.section_descriptions[section[0]])) - except KeyError, e: - error = e - del temp - # Save a Form Processed by POST - if request.method == 'POST': - form = ListSettings(visible_section, visible_option, data=request.POST) - form.truncate() - if form.is_valid(): - list_settings = the_list.settings - for key in form.fields.keys(): - list_settings[key] = form.cleaned_data[key] - list_settings.save() - message = _("The list has been updated.") - else: - message = _("Validation Error - The list has not been updated.") - else: - #Provide a form with existing values - #create form and process layout into form.layout - form = ListSettings(visible_section, visible_option, data=None) - #create a Dict of all settings which are used in the form - used_settings = {} - for section in form.layout: - for option in section[1:]: - used_settings[option] = the_list.settings[option] - if option == u'acceptable_aliases': - used_settings[option] = '\n'.join(used_settings[option]) - # recreate the form using the settings - form = ListSettings(visible_section, visible_option, - data=used_settings) - form.truncate() - return render_to_response(template, - {'form': form, - 'form_sections': form_sections, - 'message': message, - 'list': the_list, - 'visible_option': visible_option, - 'visible_section': visible_section}, - context_instance=RequestContext(request)) - - -@login_required -def user_mailmansettings(request): - try: - the_user = MailmanUser.objects.get(address=request.user.email) - except MailmanApiError: - return utils.render_api_error(request) - - settingsform = MembershipSettings() - return render_to_response('postorius/user_mailmansettings.html', - {'mm_user': the_user, - 'settingsform': settingsform}, - context_instance=RequestContext(request)) - - -@login_required -def membership_settings(request): - """Display a list of all memberships. - """ - - -@login_required -def user_settings(request, tab="membership", - template='postorius/user_settings.html', - fqdn_listname=None): - """ - Change the user or the membership settings. - The user must be logged in to be allowed to change any settings. - TODO: * add CSS to display tabs ?? - * add missing functionality in REST server and client and - change to the correct calls here - """ - member = request.user.username - message = '' - form = None - the_list = None - membership_lists = [] - - try: - c = Client('%s/3.0' % settings.REST_SERVER, settings.API_USER, - settings.API_PASS) - if tab == "membership": - if fqdn_listname: - the_list = List.objects.get(fqdn_listname=fqdn_listname) - user_object = the_list.get_member(member) - else: - message = ("") - for mlist in List.objects.all(): - try: - mlist.get_member(member) - membership_lists.append(mlist) - except: - pass - else: - # address_choices for the 'address' field must be a list of - # tuples of length 2 - raise Exception("") - address_choices = [[addr, addr] for addr in user_object.address] - except AttributeError, e: - return render_to_response( - 'postorius/errors/generic.html', - {'error': str(e) + "REST API not found / Offline"}, - context_instance=RequestContext(request)) - except ValueError, e: - return render_to_response('postorius/errors/generic.html', - {'error': e}, - context_instance=RequestContext(request)) - except HTTPError, e: - return render_to_response( - 'postorius/errors/generic.html', - {'error': _("List ") + fqdn_listname + _(" does not exist")}, - context_instance=RequestContext(request)) - #----------------------------------------------------------------- - if request.method == 'POST': - # The form enables both user and member settings. As a result - # we must find out which was the case. - raise Exception("Please fix bug prior submitting the form") - if tab == "membership": - form = MembershipSettings(request.POST) - if form.is_valid(): - member_object = c.get_member(member, request.GET["list"]) - member_object.update(request.POST) - message = "The membership settings have been updated." - else: - # the post request came from the user tab - # the 'address' field need choices as a tuple of length 2 - addr_choices = [[request.POST["address"], request.POST["address"]]] - form = UserSettings(addr_choices, request.POST) - if form.is_valid(): - user_object.update(request.POST) - # to get the full list of addresses we need to - # reinstantiate the form with all the addresses - # TODO: should return the correct settings from the DB, - # not just the address_choices (add mock data to _User - # class and make the call with 'user_object.info') - form = UserSettings(address_choices) - message = "The user settings have been updated." - - else: - if tab == "membership" and fqdn_listname: - if fqdn_listname: - # TODO : fix LP:821069 in mailman.client - the_list = List.objects.get(fqdn_listname=fqdn_listname) - member_object = the_list.get_member(member) - # TODO: add delivery_mode and deliver_status from a - # list of tuples at one point, currently we hard code - # them in forms.py - # instantiate the form with the correct member info - """ - acknowledge_posts - hide_address - receive_list_copy - receive_own_postings - delivery_mode - delivery_status - """ - data = {} - form = MembershipSettings(data) - elif tab == "user": - # TODO: should return the correct settings from the DB, - # not just the address_choices (add mock data to _User - # class and make the call with 'user_object._info') The 'language' - # field must also be added as a list of tuples with correct - # values (is currently hard coded in forms.py). - data = {} # Todo https://bugs.launchpad.net/mailman/+bug/821438 - form = UserSettings(data) - - return render_to_response(template, - {'form': form, - 'tab': tab, - 'list': the_list, - 'membership_lists': membership_lists, - 'message': message, - 'member': member}, - context_instance=RequestContext(request)) - - -class UserSummaryView(MailmanUserView): - """Shows a summary of a user. - """ - - @method_decorator(user_passes_test(lambda u: u.is_superuser)) - def get(self, request, user_id): - settingsform = MembershipSettings() - return render_to_response('postorius/users/summary.html', - {'mm_user': self.mm_user, - 'settingsform': settingsform}, - context_instance=RequestContext(request)) - - -class UserSubscriptionsView(MailmanUserView): - """Shows a summary of a user. - """ - - def _get_list(self, list_id): - if getattr(self, 'lists', None) is None: - self.lists = {} - if self.lists.get(list_id) is None: - self.lists[list_id] = List.objects.get(fqdn_listname=list_id) - return self.lists[list_id] - - def _get_memberships(self): - client = Client('%s/3.0' % settings.REST_SERVER, - settings.API_USER, settings.API_PASS) - memberships = [] - for a in self.mm_user.addresses: - members = client._connection.call('members/find', - {'subscriber': a}) - for m in members[1]['entries']: - mlist = self._get_list(m['list_id']) - memberships.append(dict(fqdn_listname=mlist.fqdn_listname, - role=m['role'], - delivery_mode=m['delivery_mode'], - address=a)) - return memberships - - def get(self, request): - memberships = self._get_memberships() - return render_to_response('postorius/user_subscriptions.html', - {'memberships': memberships}, - context_instance=RequestContext(request)) - - -@user_passes_test(lambda u: u.is_superuser) -def user_index(request, template='postorius/users/index.html'): - """Show a table of all users. - """ - error = None - try: - mm_users = MailmanUser.objects.all() - except MailmanApiError: - return utils.render_api_error(request) - return render_to_response(template, - {'error': error, - 'mm_users': mm_users}, - context_instance=RequestContext(request)) - - -@user_passes_test(lambda u: u.is_superuser) -def user_new(request): - message = None - if request.method == 'POST': - form = UserNew(request.POST) - if form.is_valid(): - user = MailmanUser(display_name=form.cleaned_data['display_name'], - email=form.cleaned_data['email'], - password=form.cleaned_data['password']) - try: - user.save() - except MailmanApiError: - return utils.render_api_error(request) - except HTTPError, e: - messages.error(request, e) - else: - messages.success(request, _("New User registered")) - return redirect("user_index") - else: - form = UserNew() - return render_to_response('postorius/users/new.html', - {'form': form, 'message': message}, - context_instance=RequestContext(request)) - - -def user_logout(request): - logout(request) - return redirect('user_login') - - -def user_login(request, template='postorius/login.html'): - if request.method == 'POST': - form = AuthenticationForm(request.POST) - user = authenticate(username=request.POST.get('username'), - password=request.POST.get('password')) - if user is not None: - logger.debug(user) - if user.is_active: - login(request, user) - return redirect(request.GET.get('next', 'list_index')) - else: - form = AuthenticationForm() - return render_to_response(template, {'form': form}, - context_instance=RequestContext(request)) - - -@login_required() -def user_profile(request, user_email=None): - if not request.user.is_authenticated(): - return redirect('user_login') - #try: - # the_user = User.objects.get(email=user_email) - #except MailmanApiError: - # return utils.render_api_error(request) - return render_to_response('postorius/user_profile.html', - # {'mm_user': the_user}, - context_instance=RequestContext(request)) - - -@login_required -def user_todos(request): - return render_to_response('postorius/user_todos.html', - context_instance=RequestContext(request))