...
 
Commits (136)
image: maxking/mailman-ci-runner
django-1.8:
py35-django-1.11:
stage: test
script:
- tox -e py35-django111
py36-django-1.11:
stage: test
script:
- tox -e py27-django18
- tox -e py36-django111
django-1.10:
py36-django-20:
stage: test
script:
- tox -e py27-django110
- tox -e py36-django20
django-1.11:
py35-django-20:
stage: test
script:
- tox -e py27-django111
- tox -e py35-django20
coverage:
stage: test
......@@ -29,17 +35,16 @@ pep8:
script:
- tox -e pep8
django-1.11-git:
git-heads:
stage: test
script:
- tox -e py27-django111-head
allow_failure: true
- tox -e py36-head
#django-latest:
# stage: test
# script:
# - tox -e py27-django-latest
# allow_failure: true
django-latest:
stage: test
allow_failure: true
script:
- tox -e py36-django-latest
pages:
stage: deploy
......
#!/usr/bin/python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 1998-2017 by the Free Software Foundation, Inc.
# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
......
Django>1.8,<1.10
git+https://gitlab.com/mailman/mailmanclient.git
git+https://gitlab.com/mailman/postorius.git
git+https://gitlab.com/mailman/django-mailman3.git
django-allauth
django-gravatar
# -*- coding: utf-8 -*-
# Copyright (C) 1998-2017 by the Free Software Foundation, Inc.
# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
......@@ -28,7 +28,6 @@ https://docs.djangoproject.com/en/1.9/ref/settings/
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
import django
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
......@@ -77,19 +76,18 @@ INSTALLED_APPS = (
'allauth.socialaccount.providers.github',
'allauth.socialaccount.providers.gitlab',
'allauth.socialaccount.providers.google',
#'allauth.socialaccount.providers.facebook',
# 'allauth.socialaccount.providers.facebook',
'allauth.socialaccount.providers.twitter',
'allauth.socialaccount.providers.stackexchange',
)
MIDDLEWARE_CLASSES = (
MIDDLEWARE = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
......@@ -287,6 +285,9 @@ LOGGING = {
}
POSTORIUS_TEMPLATE_BASE_URL = "http://localhost:8000"
try:
from settings_local import *
except ImportError:
......
# -*- coding: utf-8 -*-
# Copyright (C) 1998-2017 by the Free Software Foundation, Inc.
# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
......
# -*- coding: utf-8 -*-
# Copyright (C) 1998-2017 by the Free Software Foundation, Inc.
# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
......@@ -19,17 +19,21 @@
from django.conf.urls import include, url
from django.contrib import admin
from django.core.urlresolvers import reverse_lazy
from django.urls import reverse_lazy
from django.views.generic import RedirectView
from django.views.defaults import page_not_found, server_error
urlpatterns = [
url(r'^$', RedirectView.as_view(
url=reverse_lazy('list_index'),
permanent=True)),
url(r'^postorius/', include('postorius.urls')),
#url(r'^hyperkitty/', include('hyperkitty.urls')),
url(r'', include('django_mailman3.urls')),
url(r'^accounts/', include('allauth.urls')),
# Add some testing routes to test 400/500 error pages without having to
# introduce errors.
url(r'500/$', server_error),
url(r'400/$', page_not_found),
# Django admin
url(r'^admin/', include(admin.site.urls)),
url(r'^admin/', admin.site.urls),
]
# Copyright (C) 2012-2017 by the Free Software Foundation, Inc.
# Copyright (C) 2012-2018 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
......@@ -41,16 +41,19 @@ setup(
keywords='email mailman django',
url=" https://gitlab.com/mailman/postorius",
classifiers=[
"Programming Language :: Python",
"Framework :: Django",
"Development Status :: 4 - Beta",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Topic :: Communications :: Email :: Mailing List Servers",
"Programming Language :: Python :: 3",
],
packages=find_packages('src'),
package_dir={'': 'src'},
include_package_data=True,
install_requires=[
'Django>=1.8',
'Django<1.12',
'django-mailman3',
'mailmanclient~=3.1.1a1'
'Django>=1.11',
'django-mailman3>=1.2.0a1',
'mailmanclient>=3.2.0b2'
],
tests_require=[
"mock",
......
# -*- coding: utf-8 -*-
# Copyright (C) 1998-2017 by the Free Software Foundation, Inc.
# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
......@@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License along with
# Postorius. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, unicode_literals
__version__ = '1.1.2'
__version__ = '1.2.0'
default_app_config = 'postorius.apps.PostoriusConfig'
# -*- coding: utf-8 -*-
# Copyright (C) 2016-2017 by the Free Software Foundation, Inc.
# Copyright (C) 2016-2018 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
......@@ -17,8 +17,6 @@
# Postorius. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, unicode_literals
from django.apps import AppConfig
......
# -*- coding: utf-8 -*-
# Copyright (C) 1998-2017 by the Free Software Foundation, Inc.
# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
......@@ -18,11 +18,10 @@
"""Postorius view decorators."""
from __future__ import absolute_import, unicode_literals
from django.core.exceptions import PermissionDenied
from postorius.auth.utils import set_user_access_props
from postorius.auth.utils import set_list_access_props
def list_owner_required(fn):
......@@ -33,11 +32,11 @@ def list_owner_required(fn):
def wrapper(*args, **kwargs):
user = args[0].user
list_id = kwargs['list_id']
if not user.is_authenticated():
if not user.is_authenticated:
raise PermissionDenied
if user.is_superuser:
return fn(*args, **kwargs)
set_user_access_props(user, list_id)
set_list_access_props(user, list_id)
if user.is_list_owner:
return fn(*args, **kwargs)
else:
......@@ -53,11 +52,11 @@ def list_moderator_required(fn):
def wrapper(*args, **kwargs):
user = args[0].user
list_id = kwargs['list_id']
if not user.is_authenticated():
if not user.is_authenticated:
raise PermissionDenied
if user.is_superuser:
return fn(*args, **kwargs)
set_user_access_props(user, list_id)
set_list_access_props(user, list_id)
if user.is_list_owner or user.is_list_moderator:
return fn(*args, **kwargs)
else:
......
# -*- coding: utf-8 -*-
# Copyright (C) 2018 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/>.
#
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from postorius.auth.utils import set_domain_access_props, set_list_access_props
class ListOwnerMixin(LoginRequiredMixin, UserPassesTestMixin):
"""Mixin to allow access to only List owners."""
raise_exception = True
def test_func(self):
user = self.request.user
mlist_id = self.kwargs['list_id']
if user.is_superuser:
return True
set_list_access_props(user, mlist_id)
return user.is_list_owner
class ListModeratorMixin(LoginRequiredMixin, UserPassesTestMixin):
"""Mixin to allow access to only List Moderators."""
raise_exception = True
def test_func(self):
user = self.request.user
mlist_id = self.kwargs['list_id']
if user.is_superuser:
return True
set_list_access_props(user, mlist_id)
return user.is_list_owner or user.is_list_moderator
class DomainOwnerMixin(LoginRequiredMixin, UserPassesTestMixin):
"""Mixin to allow access to only Domain Owner."""
raise_exception = True
def test_func(self):
user = self.request.user
domain = self.kwargs['domain']
if user.is_superuser:
return True
set_domain_access_props(user, domain)
return user.is_domain_owner
class SuperUserRequiredMixin(LoginRequiredMixin, UserPassesTestMixin):
"""Mixin to allow access to only Django Superusers."""
def test_func(self):
return self.request.user.is_superuser
# -*- coding: utf-8 -*-
# Copyright (C) 1998-2017 by the Free Software Foundation, Inc.
# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
......@@ -20,24 +20,41 @@
Authentication and authorization-related utilities.
"""
from __future__ import absolute_import, unicode_literals
from allauth.account.models import EmailAddress
from django.utils import six
from postorius.models import List
from postorius.models import Domain, List
def user_is_in_list_roster(user, mailing_list, roster):
if not user.is_authenticated():
"""Checks if a user is in a MailingList roster.
:param user: User to check access permissions for.
:type user: django.contrib.auth.model.User
:param mailing_list: MailingList to check permissions for.
:type mailing_list: postorius.models.List
:param roster: Access permissions required.
:type roster: str
"""
if not user.is_authenticated:
return False
addresses = set(EmailAddress.objects.filter(
user=user, verified=True).values_list("email", flat=True))
if addresses & set(getattr(mailing_list, roster)):
roster_addresses = set(
[member.email for member in getattr(mailing_list, roster)]
)
if addresses & roster_addresses:
return True # At least one address is in the roster
return False
def set_user_access_props(user, mlist):
def set_list_access_props(user, mlist):
"""Update user's access permissions of a MailingList.
:param user: The user to check permissions for.
:type user: django.contrib.auth.model.User
:param mlist: MailingList to check permissions for.
:type mlist: postorius.models.List
"""
if isinstance(mlist, six.string_types):
mlist = List.objects.get_or_404(mlist)
if not hasattr(user, 'is_list_owner'):
......@@ -46,3 +63,25 @@ def set_user_access_props(user, mlist):
if not hasattr(user, 'is_list_moderator'):
user.is_list_moderator = user_is_in_list_roster(
user, mlist, "moderators")
def set_domain_access_props(user, domain):
"""Update user's access permissions for a domain.
:param user: The user to check permissions for.
:type user: django.contrib.auth.model.User
:param domain: Domain to check permissions for.
:type domain: postorius.models.Domain
"""
# TODO: This is very slow as it involves first iterating over every domain
# owner and then each of their addresses. Create an API in Core to
# facilitate this.
if isinstance(domain, six.string_types):
domain = Domain.objects.get_or_404(domain)
owner_addresses = []
for owner in domain.owners:
owner_addresses.extend(owner.addresses)
owner_addresses = set([each.email for each in owner_addresses])
user_addresses = set(EmailAddress.objects.filter(
user=user, verified=True).values_list("email", flat=True))
user.is_domain_owner = owner_addresses & user_addresses
# -*- coding: utf-8 -*-
# Copyright (C) 2012-2017 by the Free Software Foundation, Inc.
# Copyright (C) 2012-2018 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
......@@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License along with
# Postorius. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, unicode_literals
import logging
from postorius import __version__
......
......@@ -12,6 +12,7 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import os
......@@ -49,8 +50,8 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
project = u'Postorius'
copyright = u'2012, The Free Software Foundation'
project = 'Postorius'
copyright = '2012, The Free Software Foundation'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
......@@ -188,8 +189,8 @@ htmlhelp_basename = 'postoriusdoc'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual])
latex_documents = [
('index', 'postoriusweb.tex', u'Postorius Documentation',
u'Mailman Coders', 'manual'),
('index', 'postoriusweb.tex', 'Postorius Documentation',
'Mailman Coders', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
......@@ -221,18 +222,18 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'postorius', u'Postorius Documentation',
[u'Mailman Coders'], 1)
('index', 'postorius', 'Postorius Documentation',
['Mailman Coders'], 1)
]
# -- Options for Epub output --------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = u'postorius'
epub_author = u'Mailman Coders'
epub_publisher = u'Mailman Coders'
epub_copyright = u'2012, The Free Software Foundation'
epub_title = 'postorius'
epub_author = 'Mailman Coders'
epub_publisher = 'Mailman Coders'
epub_copyright = '2012, The Free Software Foundation'
# The language of the text. It defaults to the language option
# or en if the language is not set.
......
......@@ -2,6 +2,8 @@
Deployment
============
Postorius support Python 3.5+ and Django 1.11+.
.. note::
This guide covers deployment options of Postorius.
......@@ -124,6 +126,7 @@ postorius project directory and run:
::
$ mkdir locale
$ python manage.py compilemessages
$ python manage.py collectstatic
......
Postorius - The New Mailman Web UI
==================================
Copyright (C) 2009-2016 by the Free Software Foundation, Inc.
Copyright (C) 2009-2018 by the Free Software Foundation, Inc.
This is Postorius, the new official web interface for the GNU Mailman 3
list management system.
......
......@@ -18,17 +18,56 @@ You should have received a copy of the GNU Lesser General Public License
along with Postorius. If not, see <http://www.gnu.org/licenses/>.
1.2
===
(2018-07-10)
* Postorius now runs only on Python 3.4+ and supports Django 1.8 and 1.11+
* Added the ability to set and edit ``alias_domain`` to the ``domains`` forms.
* List Create form now allows selecting the ``style``. A ``style`` is how a new
mailing list is configured.
* Minimum supported Mailman Core version is now 3.2.0. This is because the
``styles`` attribute for MailingList resource is exposed in 3.2, which
contains all the default ``styles`` supported by Core and their human readable
description.
* Account subscription page now lists all the memberships with their respective
roles. This avoids repeated API calls for the way data was displayed
before. (Closes #205)
* Postorius now supports only Django 1.11+.
* Duplicate MailingList names doesn't return a 500 error page and instead adds
an error to the New MailingList form. (Fixes #237)
* Pending subscription requests page is now paginated. (See !298)
* Add owners/moderators form now allows specifying a Display Name, along with
their email. (Fixes #254)
* Members views now show total number of members at the top. (See !315)
* Fixed a bug where GET on views that only expect a POST would cause 500 server
errors instead of 405 method not allowed. (Fixes #185)
* Member preferences form can now be saved without having to change all the
fields. (Fixes #178)
* Fixed a bug where the 'Delete' button to remove list owners didn't work due to
wrong URL being rendered in the templates. (Fixes #274)
* Require Explicit Destination is added to the Message Acceptance form.
(Closes #277)
* Delete Domain page now shows some extra warning information about all the
mailing lists that would be deleted after deleting the Domain. (See !250)
* Superusers can now view Mailman Core's current version and REST API version
being used under 'System Information' menu in the top navigation bar. (See !325)
* Fixed a bug where 500 error template wouldn't render properly due to missing
context variables in views that render that templates (See !334)
* Postorius now allows adding and editing templates for email headers, footers
and some of the automatic responses sent out by Mailman. (See !327)
1.1.2
=====
(2017-12-27)
* Added a new ``reset_passwords`` command that resets _all_ user's passwords
inside of Core. This password is different from the one Postorius maintains
and is used for logging users in.
inside of Core. This password is different from the one Postorius
maintains. The Postorius password is the one used for logging users in.
* Postorius now sets the 'Display Name' of the user in Core correctly. This
fixes a security vulnerability where user's display_name would be set as their
Core's password.
=======
1.1.1
=====
......
# -*- coding: utf-8 -*-
# Copyright (C) 1998-2017 by the Free Software Foundation, Inc.
# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
......
......@@ -12,6 +12,8 @@ Installation
Install Postorius
=================
Postorius supports Python 3.5+ and Django 1.11+.
Latest release
--------------
......
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2018 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/>.
#
from postorius.forms.domain_forms import * # noqa
from postorius.forms.fields import * # noqa
from postorius.forms.member_forms import * # noqa
from postorius.forms.list_forms import * # noqa
from postorius.forms.user_forms import * # noqa
# -*- coding: utf-8 -*-
# Copyright (C) 2012-2018 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/>.
from django import forms
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from django.contrib.sites.models import Site
from postorius.forms.fields import SiteModelChoiceField
def _get_web_host_help():
# Using a function is necessary, otherwise reverse() will be called before
# URLConfs are loaded.
return (_(
'The domain from which you want the web UI to be served from. '
'This can be same or different from the Mail Host. '
'You can edit the list of available web hosts <a href="%s">here</a>.'
) % reverse("admin:sites_site_changelist"))
class DomainForm(forms.Form):
"""
Form to add a domain.
"""
mail_host = forms.CharField(
label=_('Mail Host'),
error_messages={'required': _('Please enter a domain name'),
'invalid': _('Please enter a valid domain name.')},
required=True,
help_text=_(
'The domain for your mailing lists. For example when you want '
'lists like [email protected], enter example.com here.'),
)
description = forms.CharField(
label=_('Description'),
required=False)
alias_domain = forms.CharField(
label=_('Alias Domain'),
error_messages={
'invalid': _('Please enter a valid domain name or nothing.')},
required=False,
help_text=_('Normally empty. Used only for unusual Postfix configs.'),
)
site = SiteModelChoiceField(
label=_('Web Host'),
error_messages={'required': _('Please enter a domain name'),
'invalid': _('Please enter a valid domain name.')},
required=True,
queryset=Site.objects.order_by("name").all(),
initial=lambda: Site.objects.get_current(),
help_text=_get_web_host_help,
)
def clean_mail_host(self):
mail_host = self.cleaned_data['mail_host']
try:
validate_email('[email protected]' + mail_host)
except ValidationError:
raise forms.ValidationError(_("Please enter a valid domain name"))
return mail_host
def clean_alias_domain(self):
alias_domain = self.cleaned_data['alias_domain']
if alias_domain != '':
try:
validate_email('[email protected]' + alias_domain)
except ValidationError:
raise forms.ValidationError(
_("Please enter a valid domain name or nothing."))
return alias_domain
class DomainEditForm(DomainForm):
"""
Form to edit domains
separte from the DomainForm, so that the mail_host can't be changed.
"""
mail_host = None
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2018 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/>.
#
from django import forms
from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _
class ListOfStringsField(forms.Field):
widget = forms.widgets.Textarea
def prepare_value(self, value):
if isinstance(value, list):
value = '\n'.join(value)
return value
def to_python(self, value):
"Returns a list of Unicode object."
if value in self.empty_values:
return []
result = []
for line in value.splitlines():
line = line.strip()
if not line:
continue
result.append(smart_text(line))
return result
class NullBooleanRadioSelect(forms.RadioSelect):
"""
This is necessary to detect that such a field has not been changed.
"""
def value_from_datadict(self, data, files, name):
value = data.get(name, None)
return {'2': True,
True: True,
'True': True,
'3': False,
'False': False,
False: False}.get(value, None)
class SiteModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return "%s (%s)" % (obj.name, obj.domain)
class MultipleChoiceForm(forms.Form):
class MultipleChoiceField(forms.MultipleChoiceField):
def validate(self, value):
pass
choices = MultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
)
def clean_choices(self):
if len(self.cleaned_data['choices']) < 1:
raise forms.ValidationError(_('Make at least one selection'))
return self.cleaned_data['choices']
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2018 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/>.
#
from django import forms
from django.utils.translation import ugettext_lazy as _
class MemberForm(forms.Form):
"""Assign a role to the member"""
email = forms.EmailField(
label=_('Email Address'),
error_messages={
'required': _('Please enter an email address.'),
'invalid': _('Please enter a valid email address.')})
display_name = forms.CharField(
label=_('Display Name'),
required=False,
error_messages={
'invalid': _('Please enter an display name.')})
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2018 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/>.
#
from django import forms
from django.utils.translation import ugettext_lazy as _
from postorius.forms.fields import NullBooleanRadioSelect
class UserPreferences(forms.Form):
"""
Form handling the user's global, address and subscription based preferences
"""
def __init__(self, *args, **kwargs):
self._preferences = kwargs.pop('preferences', None)
super(UserPreferences, self).__init__(*args, **kwargs)
@property
def initial(self):
# Redirect to the preferences, this allows setting the preferences
# after instanciation and it will also set the initial data.
return self._preferences or {}
@initial.setter
def initial(self, value):
pass
choices = ((True, _('Yes')), (False, _('No')))
delivery_mode_choices = (("regular", _('Regular')),
("plaintext_digests", _('Plain Text Digests')),
("mime_digests", _('Mime Digests')),
("summary_digests", _('Summary Digests')))
delivery_status_choices = (
("enabled", _('Enabled')), ("by_user", _('Disabled')))
delivery_status = forms.ChoiceField(
widget=forms.RadioSelect,
choices=delivery_status_choices,
required=False,
label=_('Delivery status'),
help_text=_(
'Set this option to Enabled to receive messages posted to this '
'mailing list. Set it to Disabled if you want to stay subscribed, '
'but don\'t want mail delivered to you for a while (e.g. you\'re '
'going on vacation). If you disable mail delivery, don\'t forget '
'to re-enable it when you come back; it will not be automatically '
're-enabled.'))
delivery_mode = forms.ChoiceField(
widget=forms.Select(),
choices=delivery_mode_choices,
required=False,
label=_('Delivery mode'),
help_text=_(
'If you select summary digests , you\'ll get posts bundled '
'together (usually one per day but possibly more on busy lists), '
'instead of singly when they\'re sent. Your mail reader may or '
'may not support MIME digests. In general MIME digests are '
'preferred, but if you have a problem reading them, select '
'plain text digests.'))
receive_own_postings = forms.NullBooleanField(
widget=NullBooleanRadioSelect(choices=choices),
required=False,
label=_('Receive own postings'),
help_text=_(
'Ordinarily, you will get a copy of every message you post to the '
'list. If you don\'t want to receive this copy, set this option '
'to No.'
))
acknowledge_posts = forms.NullBooleanField(
widget=NullBooleanRadioSelect(choices=choices),
required=False,
label=_('Acknowledge posts'),
help_text=_(
'Receive acknowledgement mail when you send mail to the list?'))
hide_address = forms.NullBooleanField(
widget=NullBooleanRadioSelect(choices=choices),
required=False,
label=_('Hide address'),
help_text=_(
'When someone views the list membership, your email address is '
'normally shown (in an obscured fashion to thwart spam '
'harvesters). '
'If you do not want your email address to show up on this '
'membership roster at all, select Yes for this option.'))
receive_list_copy = forms.NullBooleanField(
widget=NullBooleanRadioSelect(choices=choices),
required=False,
label=_('Receive list copies (possible duplicates)'),
help_text=_(
'When you are listed explicitly in the To: or Cc: headers of a '
'list message, you can opt to not receive another copy from the '
'mailing list. Select No to receive copies. '
'Select Yes to avoid receiving copies from the mailing list'))
class Meta:
"""
Class to define the name of the fieldsets and what should be
included in each.
"""
layout = [["User Preferences", "acknowledge_posts", "hide_address",
"receive_list_copy", "receive_own_postings",
"delivery_mode", "delivery_status"]]
def save(self):
if not self.changed_data:
return
for key in self.changed_data:
if self.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.
self._preferences[key] = self.cleaned_data[key]
self._preferences.save()
class UserPreferencesFormset(forms.BaseFormSet):
def __init__(self, *args, **kwargs):
self._preferences = kwargs.pop('preferences')
kwargs["initial"] = self._preferences
super(UserPreferencesFormset, self).__init__(*args, **kwargs)
def _construct_form(self, i, **kwargs):
form = super(UserPreferencesFormset, self)._construct_form(i, **kwargs)
form._preferences = self._preferences[i]
return form
def save(self):
for form in self.forms:
form.save()
This diff is collapsed.
# -*- coding: utf-8 -*-
# Copyright (C) 1998-2017 by the Free Software Foundation, Inc.
# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
......
# -*- coding: utf-8 -*-
# Copyright (C) 2017 by the Free Software Foundation, Inc.
# Copyright (C) 2017-2018 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
......
# -*- coding: utf-8 -*-
# Copyright (C) 2015-2017 by the Free Software Foundation, Inc.
# Copyright (C) 2015-2018 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
......@@ -16,11 +16,13 @@
# You should have received a copy of the GNU General Public License along with
# Postorius. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, unicode_literals
from postorius import utils
from postorius.models import MailmanApiError
from mailmanclient import MailmanConnectionError
import logging
logger = logging.getLogger(__name__)
__all__ = [
......@@ -37,6 +39,6 @@ class PostoriusMiddleware(object):
return self.get_response(request)
def process_exception(self, request, exception):
if isinstance(exception, MailmanApiError) or isinstance(
exception, MailmanConnectionError):
if isinstance(exception, (MailmanApiError, MailmanConnectionError)):
logger.exception('Mailman REST API not available')
return utils.render_api_error(request)
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
......@@ -20,7 +19,8 @@ class Migration(migrations.Migration):
('email', models.EmailField(max_length=254)),
('activation_key', models.CharField(max_length=40)),
('created', models.DateTimeField()),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)),
],
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
......
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-08-13 09:48
from __future__ import unicode_literals
from django.db import migrations
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-07-02 04:44
from __future__ import unicode_literals
from django.db import migrations, models
# flake8: noqa
class Migration(migrations.Migration):
initial = True
dependencies = [
('postorius', '0003_drop_addressconfirmationprofile'),
]
operations = [
migrations.CreateModel(
name='EmailTemplate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(choices=[('list:admin:action:post', 'Sent to the list administrators when moderator approval for a posting is required.'), ('list:admin:action:subscribe', 'Sent to the list administrators when moderator approval for a subscription request is required.'), ('list:admin:action:unsubscribe', 'Sent to the list administrators when moderator approval for an unsubscription request is required.'), ('list:admin:notice:subscribe', 'Sent to the list administrators to notify them when a new member has been subscribed.'), ('list:admin:notice:unrecognized', 'Sent to the list administrators when a bounce message in an unrecognized format has been received.'), ('list:admin:notice:unsubscribe', 'Sent to the list administrators to notify them when a member has been unsubscribed.'), ('list:member:digest:footer', 'The footer for a digest message.'), ('list:member:digest:header', 'The header for a digest message.'), ('list:member:digest:masthead', 'The digest “masthead”; i.e. a common introduction for all digest messages.'), ('list:member:regular:footer', 'The footer for a regular (non-digest) message.'), ('list:member:regular:header', 'The header for a regular (non-digest) message.'), ('list:user:action:subscribe', 'The message sent to subscribers when a subscription confirmation is required.'), ('list:user:action:unsubscribe', 'The message sent to subscribers when an unsubscription confirmation is required.'), ('list:user:notice:goodbye', 'The notice sent to a member when they unsubscribe from a mailing list.'), ('list:user:notice:hold', 'The notice sent to a poster when their message is being held or moderator approval.'), ('list:user:notice:no-more-today', 'Sent to a user when the maximum number of autoresponses has been reached for that day.'), ('list:user:notice:post', 'Notice sent to a poster when their message has been received by the mailing list.'), ('list:user:notice:probe', 'A bounce probe sent to a member when their subscription has been disabled due to bounces.'), ('list:user:notice:refuse', 'Notice sent to a poster when their message has been rejected by the list’s moderator.'), ('list:user:notice:welcome', 'The notice sent to a member when they are subscribed to the mailing list.')], help_text='Choose the template you want to customize.', max_length=100)),
('data', models.TextField(help_text="You can use these variables in the templates. \n$hyperkitty_url: Permalink to archived message in Hyperkitty\n$listname: Name of the Mailing List e.g. [email protected] \n$list_id: The List-ID header e.g. ant.example.com \n$display_name: Display name of the mailing list e.g. Ant \n$short_listname: Local part of the listname e.g. ant \n$domain: The domain part of the listname e.g. example.com \n$info: The mailing list's longer descriptive text \n$request_email: The email address for -request address \n$owner_email: The email address for -owner address \n$site_email: The email address to reach the owners of the site \n$language: The two letter language code for list's preferred language e.g. fr, en, de \n")),
('language', models.CharField(blank=True, choices=[('af', 'Afrikaans'), ('ar', 'Arabic'), ('ast', 'Asturian'), ('az', 'Azerbaijani'), ('bg', 'Bulgarian'), ('be', 'Belarusian'), ('bn', 'Bengali'), ('br', 'Breton'), ('bs', 'Bosnian'), ('ca', 'Catalan'), ('cs', 'Czech'), ('cy', 'Welsh'), ('da', 'Danish'), ('de', 'German'), ('dsb', 'Lower Sorbian'), ('el', 'Greek'), ('en', 'English'), ('en-au', 'Australian English'), ('en-gb', 'British English'), ('eo', 'Esperanto'), ('es', 'Spanish'), ('es-ar', 'Argentinian Spanish'), ('es-co', 'Colombian Spanish'), ('es-mx', 'Mexican Spanish'), ('es-ni', 'Nicaraguan Spanish'), ('es-ve', 'Venezuelan Spanish'), ('et', 'Estonian'), ('eu', 'Basque'), ('fa', 'Persian'), ('fi', 'Finnish'), ('fr', 'French'), ('fy', 'Frisian'), ('ga', 'Irish'), ('gd', 'Scottish Gaelic'), ('gl', 'Galician'), ('he', 'Hebrew'), ('hi', 'Hindi'), ('hr', 'Croatian'), ('hsb', 'Upper Sorbian'), ('hu', 'Hungarian'), ('ia', 'Interlingua'), ('id', 'Indonesian'), ('io', 'Ido'), ('is', 'Icelandic'), ('it', 'Italian'), ('ja', 'Japanese'), ('ka', 'Georgian'), ('kk', 'Kazakh'), ('km', 'Khmer'), ('kn', 'Kannada'), ('ko', 'Korean'), ('lb', 'Luxembourgish'), ('lt', 'Lithuanian'), ('lv', 'Latvian'), ('mk', 'Macedonian'), ('ml', 'Malayalam'), ('mn', 'Mongolian'), ('mr', 'Marathi'), ('my', 'Burmese'), ('nb', 'Norwegian Bokmål'), ('ne', 'Nepali'), ('nl', 'Dutch'), ('nn', 'Norwegian Nynorsk'), ('os', 'Ossetic'), ('pa', 'Punjabi'), ('pl', 'Polish'), ('pt', 'Portuguese'), ('pt-br', 'Brazilian Portuguese'), ('ro', 'Romanian'), ('ru', 'Russian'), ('sk', 'Slovak'), ('sl', 'Slovenian'), ('sq', 'Albanian'), ('sr', 'Serbian'), ('sr-latn', 'Serbian Latin'), ('sv', 'Swedish'), ('sw', 'Swahili'), ('ta', 'Tamil'), ('te', 'Telugu'), ('th', 'Thai'), ('tr', 'Turkish'), ('tt', 'Tatar'), ('udm', 'Udmurt'), ('uk', 'Ukrainian'), ('ur', 'Urdu'), ('vi', 'Vietnamese'), ('zh-hans', 'Simplified Chinese'), ('zh-hant', 'Traditional Chinese')], help_text="Language for the template, this should be the list's preferred language.", max_length=5)),
('craeted_at', models.DateTimeField(auto_now_add=True)),
('modified_at', models.DateTimeField(auto_now=True)),
('context', models.CharField(choices=[('site', 'Site Wide'), ('domain', 'Domain Wide'), ('list', 'MailingList Wide')], max_length=50)),
('identifier', models.CharField(blank=True, max_length=100)),
],
),
migrations.AlterUniqueTogether(
name='emailtemplate',
unique_together=set([('name', 'identifier', 'language')]),
),
]
# Generated by Django 2.0.6 on 2018-07-07 18:07
from django.db import migrations, models
# flake8: noqa
class Migration(migrations.Migration):
dependencies = [
('postorius', '0004_create_email_template'),
]
operations = [
migrations.AlterField(
model_name='emailtemplate',
name='language',
field=models.CharField(blank=True, choices=[('af', 'Afrikaans'), ('ar', 'Arabic'), ('ast', 'Asturian'), ('az', 'Azerbaijani'), ('bg', 'Bulgarian'), ('be', 'Belarusian'), ('bn', 'Bengali'), ('br', 'Breton'), ('bs', 'Bosnian'), ('ca', 'Catalan'), ('cs', 'Czech'), ('cy', 'Welsh'), ('da', 'Danish'), ('de', 'German'), ('dsb', 'Lower Sorbian'), ('el', 'Greek'), ('en', 'English'), ('en-au', 'Australian English'), ('en-gb', 'British English'), ('eo', 'Esperanto'), ('es', 'Spanish'), ('es-ar', 'Argentinian Spanish'), ('es-co', 'Colombian Spanish'), ('es-mx', 'Mexican Spanish'), ('es-ni', 'Nicaraguan Spanish'), ('es-ve', 'Venezuelan Spanish'), ('et', 'Estonian'), ('eu', 'Basque'), ('fa', 'Persian'), ('fi', 'Finnish'), ('fr', 'French'), ('fy', 'Frisian'), ('ga', 'Irish'), ('gd', 'Scottish Gaelic'), ('gl', 'Galician'), ('he', 'Hebrew'), ('hi', 'Hindi'), ('hr', 'Croatian'), ('hsb', 'Upper Sorbian'), ('hu', 'Hungarian'), ('ia', 'Interlingua'), ('id', 'Indonesian'), ('io', 'Ido'), ('is', 'Icelandic'), ('it', 'Italian'), ('ja', 'Japanese'), ('ka', 'Georgian'), ('kab', 'Kabyle'), ('kk', 'Kazakh'), ('km', 'Khmer'), ('kn', 'Kannada'), ('ko', 'Korean'), ('lb', 'Luxembourgish'), ('lt', 'Lithuanian'), ('lv', 'Latvian'), ('mk', 'Macedonian'), ('ml', 'Malayalam'), ('mn', 'Mongolian'), ('mr', 'Marathi'