summaryrefslogtreecommitdiff
path: root/src/mailman/app
diff options
context:
space:
mode:
authorBarry Warsaw2016-07-16 15:44:07 -0400
committerBarry Warsaw2016-07-16 15:44:07 -0400
commitdbde6231ec897379ed38ed4cd015b8ab20ed5fa1 (patch)
tree1226d06a238314262a1d04d0bbf9c4dc0b72c309 /src/mailman/app
parent3387791beb7112dbe07664041f117fdcc20df53d (diff)
downloadmailman-dbde6231ec897379ed38ed4cd015b8ab20ed5fa1.tar.gz
mailman-dbde6231ec897379ed38ed4cd015b8ab20ed5fa1.tar.zst
mailman-dbde6231ec897379ed38ed4cd015b8ab20ed5fa1.zip
New template system. Closes #249
The new template system is introduced for API 3.1. See ``src/mailman/rest/docs/templates.rst`` for details.
Diffstat (limited to 'src/mailman/app')
-rw-r--r--src/mailman/app/bounces.py33
-rw-r--r--src/mailman/app/docs/moderator.rst6
-rw-r--r--src/mailman/app/docs/pipelines.rst12
-rw-r--r--src/mailman/app/membership.py13
-rw-r--r--src/mailman/app/moderator.py36
-rw-r--r--src/mailman/app/notifications.py62
-rw-r--r--src/mailman/app/registrar.py26
-rw-r--r--src/mailman/app/subscriptions.py13
-rw-r--r--src/mailman/app/templates.py104
-rw-r--r--src/mailman/app/tests/test_bounces.py53
-rw-r--r--src/mailman/app/tests/test_notifications.py32
-rw-r--r--src/mailman/app/tests/test_registrar.py3
-rw-r--r--src/mailman/app/tests/test_subscriptions.py3
-rw-r--r--src/mailman/app/tests/test_templates.py125
14 files changed, 150 insertions, 371 deletions
diff --git a/src/mailman/app/bounces.py b/src/mailman/app/bounces.py
index 977b21038..98076dc9b 100644
--- a/src/mailman/app/bounces.py
+++ b/src/mailman/app/bounces.py
@@ -32,9 +32,9 @@ from mailman.interfaces.bounce import UnrecognizedBounceDisposition
from mailman.interfaces.listmanager import IListManager
from mailman.interfaces.pending import IPendable, IPendings
from mailman.interfaces.subscriptions import ISubscriptionService
+from mailman.interfaces.template import ITemplateLoader
from mailman.utilities.email import split_email
-from mailman.utilities.i18n import make
-from mailman.utilities.string import oneline
+from mailman.utilities.string import expand, oneline, wrap
from string import Template
from zope.component import getUtility
from zope.interface import implementer
@@ -184,12 +184,19 @@ def send_probe(member, msg):
"""
mlist = getUtility(IListManager).get_by_list_id(
member.mailing_list.list_id)
- text = make('probe.txt', mlist, member.preferred_language.code,
- listname=mlist.fqdn_listname,
- address=member.address.email,
- optionsurl=member.options_url,
- owneraddr=mlist.owner_address,
- )
+ template = getUtility(ITemplateLoader).get(
+ 'list:user:notice:probe', mlist,
+ language=member.preferred_language.code,
+ # For backward compatibility.
+ code=member.preferred_language.code,
+ )
+ text = wrap(expand(template, mlist, dict(
+ sender_email=member.subscriber.email,
+ # For backward compatibility.
+ address=member.address.email,
+ email=member.address.email,
+ owneraddr=mlist.owner_address,
+ )))
message_id = msg['message-id']
if isinstance(message_id, bytes):
message_id = message_id.decode('ascii')
@@ -240,11 +247,11 @@ def maybe_forward(mlist, msg):
# The notification is either going to go to the list's administrators
# (owners and moderators), or to the site administrators. Most of the
# notification is exactly the same in either case.
- adminurl = mlist.script_url('admin') + '/bounce'
subject = _('Uncaught bounce notification')
- text = MIMEText(
- make('unrecognized.txt', mlist, adminurl=adminurl),
- _charset=mlist.preferred_language.charset)
+ template = getUtility(ITemplateLoader).get(
+ 'list:admin:notice:unrecognized', mlist)
+ text = expand(template, mlist)
+ text_part = MIMEText(text, _charset=mlist.preferred_language.charset)
attachment = MIMEMessage(msg)
if (mlist.forward_unrecognized_bounces_to
is UnrecognizedBounceDisposition.administrators):
@@ -258,6 +265,6 @@ def maybe_forward(mlist, msg):
# Create the notification and send it.
notice = OwnerNotification(mlist, subject, **keywords)
notice.set_type('multipart/mixed')
- notice.attach(text)
+ notice.attach(text_part)
notice.attach(attachment)
notice.send(mlist)
diff --git a/src/mailman/app/docs/moderator.rst b/src/mailman/app/docs/moderator.rst
index f4339667f..988e8bebc 100644
--- a/src/mailman/app/docs/moderator.rst
+++ b/src/mailman/app/docs/moderator.rst
@@ -355,9 +355,9 @@ Jeff is a member of the mailing list, and chooses to unsubscribe.
Your authorization is required for a mailing list unsubscription
request approval:
<BLANKLINE>
- By: jeff@example.org
- From: ant@example.com
- ...
+ For: jeff@example.org
+ List: ant@example.com
+ <BLANKLINE>
Membership changes
diff --git a/src/mailman/app/docs/pipelines.rst b/src/mailman/app/docs/pipelines.rst
index 46880b225..4022251f7 100644
--- a/src/mailman/app/docs/pipelines.rst
+++ b/src/mailman/app/docs/pipelines.rst
@@ -49,14 +49,12 @@ etc.
Precedence: list
Subject: [Test] My first post
List-Id: <test.example.com>
- Archived-At: <http://lists.example.com/.../4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB>
- List-Archive: <http://lists.example.com/archives/test@example.com>
+ Archived-At: <http://example.com/.../4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB>
+ List-Archive: <http://example.com/archives/test@example.com>
List-Help: <mailto:test-request@example.com?subject=help>
List-Post: <mailto:test@example.com>
- List-Subscribe: <http://lists.example.com/listinfo/test@example.com>,
- <mailto:test-join@example.com>
- List-Unsubscribe: <http://lists.example.com/listinfo/test@example.com>,
- <mailto:test-leave@example.com>
+ List-Subscribe: <mailto:test-join@example.com>
+ List-Unsubscribe: <mailto:test-leave@example.com>
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
@@ -65,7 +63,6 @@ etc.
_______________________________________________
Test mailing list
test@example.com
- http://lists.example.com/listinfo/test@example.com
<BLANKLINE>
The message metadata has information about recipients and other stuff.
@@ -139,7 +136,6 @@ delivered to end recipients.
_______________________________________________
Test mailing list
test@example.com
- http://lists.example.com/listinfo/test@example.com
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
diff --git a/src/mailman/app/membership.py b/src/mailman/app/membership.py
index 8b4d7ff30..534eed15d 100644
--- a/src/mailman/app/membership.py
+++ b/src/mailman/app/membership.py
@@ -29,9 +29,10 @@ from mailman.interfaces.bans import IBanManager
from mailman.interfaces.member import (
AlreadySubscribedError, MemberRole, MembershipIsBannedError,
NotAMemberError, SubscriptionEvent)
+from mailman.interfaces.template import ITemplateLoader
from mailman.interfaces.user import IUser
from mailman.interfaces.usermanager import IUserManager
-from mailman.utilities.i18n import make
+from mailman.utilities.string import expand
from zope.component import getUtility
@@ -132,11 +133,11 @@ def delete_member(mlist, email, admin_notif=None, userack=None):
user = getUtility(IUserManager).get_user(email)
display_name = user.display_name
subject = _('$mlist.display_name unsubscription notification')
- text = make('adminunsubscribeack.txt',
- mailing_list=mlist,
- listname=mlist.display_name,
- member=formataddr((display_name, email)),
- )
+ text = expand(getUtility(ITemplateLoader).get(
+ 'list:admin:notice:unsubscribe', mlist),
+ mlist, dict(
+ member=formataddr((display_name, email)),
+ ))
msg = OwnerNotification(mlist, subject, text,
roster=mlist.administrators)
msg.send(mlist)
diff --git a/src/mailman/app/moderator.py b/src/mailman/app/moderator.py
index 2f8c19cfc..8ce631941 100644
--- a/src/mailman/app/moderator.py
+++ b/src/mailman/app/moderator.py
@@ -31,8 +31,9 @@ from mailman.interfaces.listmanager import ListDeletingEvent
from mailman.interfaces.member import NotAMemberError
from mailman.interfaces.messages import IMessageStore
from mailman.interfaces.requests import IListRequests, RequestType
+from mailman.interfaces.template import ITemplateLoader
from mailman.utilities.datetime import now
-from mailman.utilities.i18n import make
+from mailman.utilities.string import expand, wrap
from zope.component import getUtility
@@ -190,12 +191,14 @@ def hold_unsubscription(mlist, email):
if mlist.admin_immed_notify:
subject = _(
'New unsubscription request from $mlist.display_name by $email')
- text = make('unsubauth.txt',
- mailing_list=mlist,
- email=email,
- listname=mlist.fqdn_listname,
- admindb_url=mlist.script_url('admindb'),
- )
+ template = getUtility(ITemplateLoader).get(
+ 'list:admin:action:unsubscribe', mlist)
+ text = wrap(expand(template, mlist, dict(
+ # For backward compatibility.
+ mailing_list=mlist,
+ member=email,
+ email=email,
+ )))
# This message should appear to come from the <list>-owner so as
# to avoid any useless bounce processing.
msg = UserNotification(
@@ -230,7 +233,7 @@ def handle_unsubscription(mlist, id, action, comment=None):
pass
slog.info('%s: deleted %s', mlist.fqdn_listname, email)
else:
- raise AssertionError('Unexpected action: {0}'.format(action))
+ raise AssertionError('Unexpected action: {}'.format(action))
# Delete the request from the database.
requestdb.delete_request(id)
@@ -246,14 +249,15 @@ def send_rejection(mlist, request, recip, comment, origmsg=None, lang=None):
lang = (mlist.preferred_language
if member is None
else member.preferred_language)
- text = make('refuse.txt',
- mailing_list=mlist,
- language=lang.code,
- listname=mlist.fqdn_listname,
- request=request,
- reason=comment,
- adminaddr=mlist.owner_address,
- )
+ template = getUtility(ITemplateLoader).get(
+ 'list:user:notice:refuse', mlist)
+ text = wrap(expand(template, mlist, dict(
+ language=lang.code,
+ reason=comment,
+ # For backward compatibility.
+ request=request,
+ adminaddr=mlist.owner_address,
+ )))
with _.using(lang.code):
# add in original message, but not wrap/filled
if origmsg:
diff --git a/src/mailman/app/notifications.py b/src/mailman/app/notifications.py
index 04a56b55d..11a21a379 100644
--- a/src/mailman/app/notifications.py
+++ b/src/mailman/app/notifications.py
@@ -26,33 +26,14 @@ from mailman.config import config
from mailman.core.i18n import _
from mailman.email.message import OwnerNotification, UserNotification
from mailman.interfaces.member import DeliveryMode
-from mailman.interfaces.templates import ITemplateLoader
-from mailman.utilities.i18n import make
+from mailman.interfaces.template import ITemplateLoader
from mailman.utilities.string import expand, wrap
-from urllib.error import URLError
from zope.component import getUtility
log = logging.getLogger('mailman.error')
-def _get_message(uri_template, mlist, language):
- if not uri_template:
- return ''
- try:
- uri = expand(uri_template, dict(
- listname=mlist.fqdn_listname,
- language=language.code,
- ))
- message = getUtility(ITemplateLoader).get(uri)
- except URLError:
- log.exception('Message URI not found ({0}): {1}'.format(
- mlist.fqdn_listname, uri_template))
- return ''
- else:
- return wrap(message)
-
-
@public
def send_welcome_message(mlist, member, language, text=''):
"""Send a welcome message to a subscriber.
@@ -67,29 +48,18 @@ def send_welcome_message(mlist, member, language, text=''):
:param language: The language of the response.
:type language: ILanguage
"""
- welcome_message = _get_message(mlist.welcome_message_uri, mlist, language)
- options_url = member.options_url
- # Try to find a non-empty display name. We first look at the directly
- # subscribed record, which will either be the address or the user. That's
- # handled automatically by going through member.subscriber. If that
- # doesn't give us something useful, try whatever user is linked to the
- # subscriber.
- if member.subscriber.display_name:
- display_name = member.subscriber.display_name
- # If an unlinked address is subscribed tehre will be no .user.
- elif member.user is not None and member.user.display_name:
- display_name = member.user.display_name
- else:
- display_name = ''
+ welcome_message = wrap(getUtility(ITemplateLoader).get(
+ 'list:user:notice:welcome', mlist, language=language.code))
+ display_name = member.display_name
# Get the text from the template.
- text = expand(welcome_message, dict(
+ text = expand(welcome_message, mlist, dict(
+ user_name=display_name,
+ user_email=member.address.email,
+ # For backward compatibility.
+ user_address=member.address.email,
fqdn_listname=mlist.fqdn_listname,
list_name=mlist.display_name,
- listinfo_uri=mlist.script_url('listinfo'),
list_requests=mlist.request_address,
- user_name=display_name,
- user_address=member.address.email,
- user_options_uri=options_url,
))
digmode = ('' # noqa
if member.delivery_mode is DeliveryMode.regular
@@ -117,8 +87,8 @@ def send_goodbye_message(mlist, address, language):
:param language: the language of the response
:type language: string
"""
- goodbye_message = _get_message(mlist.goodbye_message_uri,
- mlist, language)
+ goodbye_message = wrap(getUtility(ITemplateLoader).get(
+ 'list:user:notice:goodbye', mlist, language=language.code))
msg = UserNotification(
address, mlist.bounces_address,
_('You have been unsubscribed from the $mlist.display_name '
@@ -140,10 +110,10 @@ def send_admin_subscription_notice(mlist, address, display_name):
"""
with _.using(mlist.preferred_language.code):
subject = _('$mlist.display_name subscription notification')
- text = make('adminsubscribeack.txt',
- mailing_list=mlist,
- listname=mlist.display_name,
- member=formataddr((display_name, address)),
- )
+ text = expand(
+ getUtility(ITemplateLoader).get('list:admin:notice:subscribe', mlist),
+ mlist, dict(
+ member=formataddr((display_name, address)),
+ ))
msg = OwnerNotification(mlist, subject, text, roster=mlist.administrators)
msg.send(mlist)
diff --git a/src/mailman/app/registrar.py b/src/mailman/app/registrar.py
index 541006f4b..7cefbd518 100644
--- a/src/mailman/app/registrar.py
+++ b/src/mailman/app/registrar.py
@@ -21,13 +21,13 @@ import logging
from mailman import public
from mailman.app.subscriptions import SubscriptionWorkflow
-from mailman.core.i18n import _
from mailman.database.transaction import flush
from mailman.email.message import UserNotification
from mailman.interfaces.pending import IPendable, IPendings
from mailman.interfaces.registrar import ConfirmationNeededEvent, IRegistrar
-from mailman.interfaces.templates import ITemplateLoader
+from mailman.interfaces.template import ITemplateLoader
from mailman.interfaces.workflow import IWorkflowStateManager
+from mailman.utilities.string import expand
from zope.component import getUtility
from zope.interface import implementer
@@ -84,18 +84,22 @@ def handle_ConfirmationNeededEvent(event):
# encode the token, they can reply to the robot and keep the token in
# the Subject header, or they can click on the URL in the body of the
# message and confirm through the web.
- subject = 'confirm ' + event.token
+ subject = 'confirm {}'.format(event.token)
confirm_address = event.mlist.confirm_address(event.token)
- # For i18n interpolation.
- confirm_url = event.mlist.domain.confirm_url(event.token) # noqa
email_address = event.email
- domain_name = event.mlist.domain.mail_host # noqa
- contact_address = event.mlist.owner_address # noqa
# Send a verification email to the address.
template = getUtility(ITemplateLoader).get(
- 'mailman:///{}/{}/confirm.txt'.format(
- event.mlist.fqdn_listname,
- event.mlist.preferred_language.code))
- text = _(template)
+ 'list:user:action:confirm', event.mlist)
+ text = expand(template, event.mlist, dict(
+ token=event.token,
+ subject=subject,
+ confirm_email=confirm_address,
+ user_email=email_address,
+ # For backward compatibility.
+ confirm_address=confirm_address,
+ email_address=email_address,
+ domain_name=event.mlist.domain.mail_host,
+ contact_address=event.mlist.owner_address,
+ ))
msg = UserNotification(email_address, confirm_address, subject, text)
msg.send(event.mlist, add_precedence=False)
diff --git a/src/mailman/app/subscriptions.py b/src/mailman/app/subscriptions.py
index e360d9615..3042623d1 100644
--- a/src/mailman/app/subscriptions.py
+++ b/src/mailman/app/subscriptions.py
@@ -36,11 +36,12 @@ from mailman.interfaces.pending import IPendable, IPendings
from mailman.interfaces.registrar import ConfirmationNeededEvent
from mailman.interfaces.subscriptions import (
ISubscriptionService, SubscriptionPendingError, TokenOwner)
+from mailman.interfaces.template import ITemplateLoader
from mailman.interfaces.user import IUser
from mailman.interfaces.usermanager import IUserManager
from mailman.interfaces.workflow import IWorkflowStateManager
from mailman.utilities.datetime import now
-from mailman.utilities.i18n import make
+from mailman.utilities.string import expand, wrap
from zope.component import getUtility
from zope.event import notify
from zope.interface import implementer
@@ -257,11 +258,11 @@ class SubscriptionWorkflow(Workflow):
'from $self.address.email')
username = formataddr(
(self.subscriber.display_name, self.address.email))
- text = make('subauth.txt',
- mailing_list=self.mlist,
- username=username,
- listname=self.mlist.fqdn_listname,
- )
+ template = getUtility(ITemplateLoader).get(
+ 'list:admin:action:subscribe', self.mlist)
+ text = wrap(expand(template, self.mlist, dict(
+ member=username,
+ )))
# This message should appear to come from the <list>-owner so as
# to avoid any useless bounce processing.
msg = UserNotification(
diff --git a/src/mailman/app/templates.py b/src/mailman/app/templates.py
deleted file mode 100644
index c01b9af47..000000000
--- a/src/mailman/app/templates.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# Copyright (C) 2012-2016 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Template loader."""
-
-from contextlib import closing
-from mailman import public
-from mailman.interfaces.languages import ILanguageManager
-from mailman.interfaces.listmanager import IListManager
-from mailman.interfaces.templates import ITemplateLoader
-from mailman.utilities.i18n import TemplateNotFoundError, find
-from urllib.error import URLError
-from urllib.parse import urlparse
-from urllib.request import BaseHandler, build_opener, install_opener, urlopen
-from urllib.response import addinfourl
-from zope.component import getUtility
-from zope.interface import implementer
-
-
-class MailmanHandler(BaseHandler):
- # Handle internal mailman: URLs.
- def mailman_open(self, req):
- list_manager = getUtility(IListManager)
- # Parse urls of the form:
- #
- # mailman:///<fqdn_listname|list_id>/<language>/<template_name>
- #
- # where only the template name is required.
- mlist = code = template = None
- # Parse the full requested URL and be sure it's something we handle.
- original_url = req.get_full_url()
- parsed = urlparse(original_url)
- assert parsed.scheme == 'mailman'
- # The path can contain one, two, or three components. Since no empty
- # path components are legal, filter them out.
- parts = [p for p in parsed.path.split('/') if p]
- if len(parts) == 0:
- raise URLError('No template specified')
- elif len(parts) == 1:
- template = parts[0]
- elif len(parts) == 2:
- part0, template = parts
- # Is part0 a language code or a mailing list? This is rather
- # tricky because if it's a mailing list, it could be a list-id and
- # that will contain dots, as could the language code.
- language = getUtility(ILanguageManager).get(part0)
- if language is None:
- # part0 must be a fqdn-listname or list-id.
- mlist = (list_manager.get(part0)
- if '@' in part0 else
- list_manager.get_by_list_id(part0))
- if mlist is None:
- raise URLError('Bad language or list name')
- else:
- code = language.code
- elif len(parts) == 3:
- part0, code, template = parts
- # part0 could be an fqdn-listname or a list-id.
- mlist = (getUtility(IListManager).get(part0)
- if '@' in part0 else
- getUtility(IListManager).get_by_list_id(part0))
- if mlist is None:
- raise URLError('Missing list')
- language = getUtility(ILanguageManager).get(code)
- if language is None:
- raise URLError('No such language')
- code = language.code
- else:
- raise URLError('No such file')
- # Find the template, mutating any missing template exception.
- try:
- path, fp = find(template, mlist, code)
- except TemplateNotFoundError:
- raise URLError('No such file')
- return addinfourl(fp, {}, original_url)
-
-
-@public
-@implementer(ITemplateLoader)
-class TemplateLoader:
- """Loader of templates, with caching and support for mailman:// URIs."""
-
- def __init__(self):
- opener = build_opener(MailmanHandler())
- install_opener(opener)
-
- def get(self, uri):
- """See `ITemplateLoader`."""
- with closing(urlopen(uri)) as fp:
- return fp.read()
diff --git a/src/mailman/app/tests/test_bounces.py b/src/mailman/app/tests/test_bounces.py
index ffc1cf2b4..685f02907 100644
--- a/src/mailman/app/tests/test_bounces.py
+++ b/src/mailman/app/tests/test_bounces.py
@@ -177,6 +177,7 @@ class TestSendProbe(unittest.TestCase):
"""Test sending of the probe message."""
layer = ConfigLayer
+ maxDiff = None
def setUp(self):
self._mlist = create_list('test@example.com')
@@ -240,13 +241,24 @@ Message-ID: <first>
self.assertEqual(notice.get_content_type(), 'text/plain')
# The interesting bits are the parts that have been interpolated into
# the message. For now the best we can do is know that the
- # interpolation values appear in the message. When Python 2.7 is our
- # minimum requirement, we can use assertRegexpMatches().
- body = notice.get_payload()
- self.assertIn('test@example.com', body)
- self.assertIn('anne@example.com', body)
- self.assertIn('http://example.com/anne@example.com', body)
- self.assertIn('test-owner@example.com', body)
+ # interpolation values appear in the message.
+ self.assertMultiLineEqual(notice.get_payload(), """\
+This is a probe message. You can ignore this message.
+
+The test@example.com mailing list has received a number of bounces
+from you, indicating that there may be a problem delivering messages
+to anne@example.com. A sample is attached below. Please examine this
+message to make sure there are no problems with your email address.
+You may want to check with your mail administrator for more help.
+
+You don't need to do anything to remain an enabled member of the
+mailing list.
+
+If you have any questions or problems, you can contact the mailing
+list owner at
+
+ test-owner@example.com
+""")
def test_headers(self):
# Check the headers of the outer message.
@@ -285,7 +297,8 @@ Message-ID: <first>
self._var_dir = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, self._var_dir)
xx_template_path = os.path.join(
- self._var_dir, 'templates', 'site', 'xx', 'probe.txt')
+ self._var_dir, 'templates', 'site', 'xx',
+ 'list:user:notice:probe.txt')
os.makedirs(os.path.dirname(xx_template_path))
config.push('xx template dir', """\
[paths.testing]
@@ -300,7 +313,6 @@ Message-ID: <first>
blah blah blah
$listname
$address
-$optionsurl
$owneraddr
""", file=fp)
@@ -322,7 +334,9 @@ $owneraddr
notice = message.get_payload(0).get_payload()
self.assertMultiLineEqual(notice, """\
blah blah blah test@example.com anne@example.com
-http://example.com/anne@example.com test-owner@example.com""")
+test-owner@example.com
+
+""")
class TestProbe(unittest.TestCase):
@@ -364,6 +378,7 @@ class TestMaybeForward(unittest.TestCase):
"""Test forwarding of unrecognized bounces."""
layer = ConfigLayer
+ maxDiff = None
def setUp(self):
config.push('test config', """
@@ -425,9 +440,12 @@ Message-ID: <first>
payload = msg.get_payload(0)
self.assertEqual(payload.get_content_type(), 'text/plain')
body = payload.get_payload()
- self.assertEqual(
- body.splitlines()[-1],
- 'http://lists.example.com/admin/test@example.com/bounce')
+ self.assertMultiLineEqual(body, """\
+The attached message was received as a bounce, but either the bounce format
+was not recognized, or no member addresses could be extracted from it. This
+mailing list has been configured to send all unrecognized bounce messages to
+the list administrator(s).
+""")
# The second attachment should be a message/rfc822 containing the
# original bounce message.
payload = msg.get_payload(1)
@@ -470,9 +488,12 @@ Message-ID: <first>
payload = msg.get_payload(0)
self.assertEqual(payload.get_content_type(), 'text/plain')
body = payload.get_payload()
- self.assertEqual(
- body.splitlines()[-1],
- 'http://lists.example.com/admin/test@example.com/bounce')
+ self.assertMultiLineEqual(body, """\
+The attached message was received as a bounce, but either the bounce format
+was not recognized, or no member addresses could be extracted from it. This
+mailing list has been configured to send all unrecognized bounce messages to
+the list administrator(s).
+""")
# The second attachment should be a message/rfc822 containing the
# original bounce message.
payload = msg.get_payload(1)
diff --git a/src/mailman/app/tests/test_notifications.py b/src/mailman/app/tests/test_notifications.py
index 09873f9bb..3230fd892 100644
--- a/src/mailman/app/tests/test_notifications.py
+++ b/src/mailman/app/tests/test_notifications.py
@@ -18,19 +18,20 @@
"""Test notifications."""
import os
-import shutil
-import tempfile
import unittest
+from contextlib import ExitStack
from mailman.app.lifecycle import create_list
from mailman.config import config
from mailman.interfaces.languages import ILanguageManager
from mailman.interfaces.member import MemberRole
+from mailman.interfaces.template import ITemplateManager
from mailman.interfaces.usermanager import IUserManager
from mailman.testing.helpers import (
get_queue_messages, set_preferred, subscribe)
from mailman.testing.layers import ConfigLayer
from mailman.utilities.datetime import now
+from tempfile import TemporaryDirectory
from zope.component import getUtility
@@ -41,36 +42,37 @@ class TestNotifications(unittest.TestCase):
maxDiff = None
def setUp(self):
+ resources = ExitStack()
+ self.addCleanup(resources.close)
+ self.var_dir = resources.enter_context(TemporaryDirectory())
self._mlist = create_list('test@example.com')
- self._mlist.welcome_message_uri = 'mailman:///welcome.txt'
self._mlist.display_name = 'Test List'
- self.var_dir = tempfile.mkdtemp()
- self.addCleanup(shutil.rmtree, self.var_dir)
+ getUtility(ITemplateManager).set(
+ 'user:ack:welcome', self._mlist.list_id, 'mailman:///welcome.txt')
config.push('template config', """\
[paths.testing]
template_dir: {}/templates
""".format(self.var_dir))
- self.addCleanup(config.pop, 'template config')
+ resources.callback(config.pop, 'template config')
# Populate the template directories with a few fake templates.
path = os.path.join(self.var_dir, 'templates', 'site', 'en')
os.makedirs(path)
- with open(os.path.join(path, 'welcome.txt'), 'w') as fp:
+ full_path = os.path.join(path, 'list:user:notice:welcome.txt')
+ with open(full_path, 'w', encoding='utf-8') as fp:
print("""\
Welcome to the $list_name mailing list.
Posting address: $fqdn_listname
Help and other requests: $list_requests
Your name: $user_name
- Your address: $user_address
- Your options: $user_options_uri""", file=fp)
+ Your address: $user_address""", file=fp)
# Write a list-specific welcome message.
path = os.path.join(self.var_dir, 'templates', 'lists',
'test@example.com', 'xx')
os.makedirs(path)
- with open(os.path.join(path, 'welcome.txt'), 'w') as fp:
+ full_path = os.path.join(path, 'list:user:notice:welcome.txt')
+ with open(full_path, 'w', encoding='utf-8') as fp:
print('You just joined the $list_name mailing list!', file=fp)
- # Let assertMultiLineEqual work without bounds.
- self.maxDiff = None
def test_welcome_message(self):
subscribe(self._mlist, 'Anne', email='anne@example.com')
@@ -86,13 +88,13 @@ Welcome to the Test List mailing list.
Help and other requests: test-request@example.com
Your name: Anne Person
Your address: anne@example.com
- Your options: http://example.com/anne@example.com
""")
def test_more_specific_welcome_message_nonenglish(self):
- # mlist.welcome_message_uri can contain placeholders for the fqdn list
+ # The welcome message url can contain placeholders for the fqdn list
# name and language.
- self._mlist.welcome_message_uri = (
+ getUtility(ITemplateManager).set(
+ 'user:ack:welcome', self._mlist.list_id,
'mailman:///$listname/$language/welcome.txt')
# Add the xx language and subscribe Anne using it.
manager = getUtility(ILanguageManager)
diff --git a/src/mailman/app/tests/test_registrar.py b/src/mailman/app/tests/test_registrar.py
index cec90c88b..2107f9648 100644
--- a/src/mailman/app/tests/test_registrar.py
+++ b/src/mailman/app/tests/test_registrar.py
@@ -247,7 +247,8 @@ class TestRegistrar(unittest.TestCase):
self.assertEqual(message['To'], 'ant-owner@example.com')
self.assertEqual(message['Subject'], 'Ant subscription notification')
self.assertEqual(message.get_payload(), """\
-anne@example.com has been successfully subscribed to Ant.""")
+anne@example.com has been successfully subscribed to Ant.
+""")
def test_no_admin_notify_mchanges(self):
# Even when a user gets subscribed via the subscription policy
diff --git a/src/mailman/app/tests/test_subscriptions.py b/src/mailman/app/tests/test_subscriptions.py
index 11512bcfe..9f02593a9 100644
--- a/src/mailman/app/tests/test_subscriptions.py
+++ b/src/mailman/app/tests/test_subscriptions.py
@@ -450,7 +450,8 @@ Your authorization is required for a mailing list subscription request
approval:
For: anne@example.com
- List: test@example.com""")
+ List: test@example.com
+""")
def test_get_moderator_approval_no_notifications(self):
# When the subscription is held for moderator approval, and the list
diff --git a/src/mailman/app/tests/test_templates.py b/src/mailman/app/tests/test_templates.py
deleted file mode 100644
index 59bf74a0b..000000000
--- a/src/mailman/app/tests/test_templates.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# Copyright (C) 2012-2016 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Test the template downloader API."""
-
-import os
-import shutil
-import tempfile
-import unittest
-
-from mailman.app.lifecycle import create_list
-from mailman.config import config
-from mailman.interfaces.templates import ITemplateLoader
-from mailman.testing.layers import ConfigLayer
-from urllib.error import URLError
-from zope.component import getUtility
-
-
-class TestTemplateLoader(unittest.TestCase):
- """Test the template downloader API."""
-
- layer = ConfigLayer
-
- def setUp(self):
- self.var_dir = tempfile.mkdtemp()
- self.addCleanup(shutil.rmtree, self.var_dir)
- config.push('template config', """\
- [paths.testing]
- var_dir: {0}
- """.format(self.var_dir))
- self.addCleanup(config.pop, 'template config')
- # Put a demo template in the site directory.
- path = os.path.join(self.var_dir, 'templates', 'site', 'en')
- os.makedirs(path)
- with open(os.path.join(path, 'demo.txt'), 'w') as fp:
- print('Test content', end='', file=fp)
- self._loader = getUtility(ITemplateLoader)
- self._mlist = create_list('test@example.com')
-
- def test_mailman_internal_uris(self):
- # mailman://demo.txt
- content = self._loader.get('mailman:///demo.txt')
- self.assertEqual(content, 'Test content')
-
- def test_mailman_internal_uris_twice(self):
- # mailman:///demo.txt
- content = self._loader.get('mailman:///demo.txt')
- self.assertEqual(content, 'Test content')
- content = self._loader.get('mailman:///demo.txt')
- self.assertEqual(content, 'Test content')
-
- def test_mailman_uri_with_language(self):
- content = self._loader.get('mailman:///en/demo.txt')
- self.assertEqual(content, 'Test content')
-
- def test_mailman_uri_with_english_fallback(self):
- content = self._loader.get('mailman:///it/demo.txt')
- self.assertEqual(content, 'Test content')
-
- def test_mailman_uri_with_list_name(self):
- content = self._loader.get('mailman:///test@example.com/demo.txt')
- self.assertEqual(content, 'Test content')
-
- def test_mailman_full_uri(self):
- content = self._loader.get('mailman:///test@example.com/en/demo.txt')
- self.assertEqual(content, 'Test content')
-
- def test_mailman_full_uri_with_english_fallback(self):
- content = self._loader.get('mailman:///test@example.com/it/demo.txt')
- self.assertEqual(content, 'Test content')
-
- def test_uri_not_found(self):
- with self.assertRaises(URLError) as cm:
- self._loader.get('mailman:///missing.txt')
- self.assertEqual(cm.exception.reason, 'No such file')
-
- def test_shorter_url_error(self):
- with self.assertRaises(URLError) as cm:
- self._loader.get('mailman:///')
- self.assertEqual(cm.exception.reason, 'No template specified')
-
- def test_short_url_error(self):
- with self.assertRaises(URLError) as cm:
- self._loader.get('mailman://')
- self.assertEqual(cm.exception.reason, 'No template specified')
-
- def test_bad_language(self):
- with self.assertRaises(URLError) as cm:
- self._loader.get('mailman:///xx/demo.txt')
- self.assertEqual(cm.exception.reason, 'Bad language or list name')
-
- def test_bad_mailing_list(self):
- with self.assertRaises(URLError) as cm:
- self._loader.get('mailman:///missing@example.com/demo.txt')
- self.assertEqual(cm.exception.reason, 'Bad language or list name')
-
- def test_too_many_path_components(self):
- with self.assertRaises(URLError) as cm:
- self._loader.get('mailman:///missing@example.com/en/foo/demo.txt')
- self.assertEqual(cm.exception.reason, 'No such file')
-
- def test_non_ascii(self):
- # mailman://demo.txt with non-ascii content.
- test_text = b'\xe4\xb8\xad'
- path = os.path.join(self.var_dir, 'templates', 'site', 'it')
- os.makedirs(path)
- with open(os.path.join(path, 'demo.txt'), 'wb') as fp:
- fp.write(test_text)
- content = self._loader.get('mailman:///it/demo.txt')
- self.assertIsInstance(content, str)
- self.assertEqual(content, test_text.decode('utf-8'))