summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBarry Warsaw2014-01-06 22:43:59 -0500
committerBarry Warsaw2014-01-06 22:43:59 -0500
commitd5aac006b6eed59999029605b037c6202fcf395e (patch)
tree799a41026ea1e93c91b3130b734b72c9c8890d0c /src
parent2fa21e92d57f05488bad732a4da3fb5131ee1ca1 (diff)
downloadmailman-d5aac006b6eed59999029605b037c6202fcf395e.tar.gz
mailman-d5aac006b6eed59999029605b037c6202fcf395e.tar.zst
mailman-d5aac006b6eed59999029605b037c6202fcf395e.zip
Diffstat (limited to 'src')
-rw-r--r--src/mailman/app/bounces.py2
-rw-r--r--src/mailman/app/events.py11
-rw-r--r--src/mailman/app/membership.py26
-rw-r--r--src/mailman/app/moderator.py5
-rw-r--r--src/mailman/app/notifications.py32
-rw-r--r--src/mailman/app/registrar.py77
-rw-r--r--src/mailman/app/tests/test_bounces.py3
-rw-r--r--src/mailman/app/tests/test_notifications.py42
-rw-r--r--src/mailman/app/tests/test_registration.py132
-rw-r--r--src/mailman/bin/runner.py2
-rw-r--r--src/mailman/commands/tests/test_confirm.py5
-rw-r--r--src/mailman/core/initialize.py2
-rw-r--r--src/mailman/handlers/docs/acknowledge.rst1
-rw-r--r--src/mailman/interfaces/registrar.py24
-rw-r--r--src/mailman/model/docs/registration.rst2
-rw-r--r--src/mailman/rest/wsgiapp.py2
-rw-r--r--src/mailman/runners/docs/digester.rst1
-rw-r--r--src/mailman/runners/tests/test_bounce.py1
-rw-r--r--src/mailman/runners/tests/test_join.py1
-rw-r--r--src/mailman/testing/helpers.py2
20 files changed, 290 insertions, 83 deletions
diff --git a/src/mailman/app/bounces.py b/src/mailman/app/bounces.py
index 186c16467..101d96f2a 100644
--- a/src/mailman/app/bounces.py
+++ b/src/mailman/app/bounces.py
@@ -196,7 +196,7 @@ def send_probe(member, msg):
member.mailing_list.list_id)
text = make('probe.txt', mlist, member.preferred_language.code,
listname=mlist.fqdn_listname,
- address= member.address.email,
+ address=member.address.email,
optionsurl=member.options_url,
owneraddr=mlist.owner_address,
)
diff --git a/src/mailman/app/events.py b/src/mailman/app/events.py
index 814d3df03..3730d5aad 100644
--- a/src/mailman/app/events.py
+++ b/src/mailman/app/events.py
@@ -27,7 +27,8 @@ __all__ = [
from zope import event
-from mailman.app import domain, moderator, subscriptions
+from mailman.app import (
+ domain, membership, moderator, registrar, subscriptions)
from mailman.core import i18n, switchboard
from mailman.languages import manager as language_manager
from mailman.styles import manager as style_manager
@@ -39,11 +40,13 @@ def initialize():
"""Initialize global event subscribers."""
event.subscribers.extend([
domain.handle_DomainDeletingEvent,
+ i18n.handle_ConfigurationUpdatedEvent,
+ language_manager.handle_ConfigurationUpdatedEvent,
+ membership.handle_SubscriptionEvent,
moderator.handle_ListDeletingEvent,
passwords.handle_ConfigurationUpdatedEvent,
+ registrar.handle_ConfirmationNeededEvent,
+ style_manager.handle_ConfigurationUpdatedEvent,
subscriptions.handle_ListDeletingEvent,
switchboard.handle_ConfigurationUpdatedEvent,
- i18n.handle_ConfigurationUpdatedEvent,
- style_manager.handle_ConfigurationUpdatedEvent,
- language_manager.handle_ConfigurationUpdatedEvent,
])
diff --git a/src/mailman/app/membership.py b/src/mailman/app/membership.py
index bbb921812..83c1b7f8b 100644
--- a/src/mailman/app/membership.py
+++ b/src/mailman/app/membership.py
@@ -23,20 +23,22 @@ __metaclass__ = type
__all__ = [
'add_member',
'delete_member',
+ 'handle_SubscriptionEvent',
]
from email.utils import formataddr
from zope.component import getUtility
-from mailman.app.notifications import send_goodbye_message
+from mailman.app.notifications import (
+ send_goodbye_message, send_welcome_message)
from mailman.config import config
from mailman.core.i18n import _
from mailman.email.message import OwnerNotification
from mailman.interfaces.address import IEmailValidator
from mailman.interfaces.bans import IBanManager
from mailman.interfaces.member import (
- MemberRole, MembershipIsBannedError, NotAMemberError)
+ MemberRole, MembershipIsBannedError, NotAMemberError, SubscriptionEvent)
from mailman.interfaces.usermanager import IUserManager
from mailman.utilities.i18n import make
@@ -156,3 +158,23 @@ def delete_member(mlist, email, admin_notif=None, userack=None):
msg = OwnerNotification(mlist, subject, text,
roster=mlist.administrators)
msg.send(mlist)
+
+
+
+def handle_SubscriptionEvent(event):
+ if not isinstance(event, SubscriptionEvent):
+ return
+ # Only send a notification message if the mailing list is configured to do
+ # so, and the member being added is a list member (as opposed to a
+ # moderator, non-member, or owner).
+ member = event.member
+ if member.role is not MemberRole.member:
+ return
+ mlist = member.mailing_list
+ if not mlist.send_welcome_message:
+ return
+ # What language should the welcome message be sent in?
+ language = member.preferred_language
+ if language is None:
+ language = mlist.preferred_language
+ send_welcome_message(mlist, member, language)
diff --git a/src/mailman/app/moderator.py b/src/mailman/app/moderator.py
index dabff068e..046450305 100644
--- a/src/mailman/app/moderator.py
+++ b/src/mailman/app/moderator.py
@@ -38,8 +38,7 @@ from email.utils import formataddr, formatdate, getaddresses, make_msgid
from zope.component import getUtility
from mailman.app.membership import add_member, delete_member
-from mailman.app.notifications import (
- send_admin_subscription_notice, send_welcome_message)
+from mailman.app.notifications import send_admin_subscription_notice
from mailman.config import config
from mailman.core.i18n import _
from mailman.email.message import UserNotification
@@ -259,8 +258,6 @@ def handle_subscription(mlist, id, action, comment=None):
# request was made and accepted.
pass
else:
- if mlist.send_welcome_message:
- send_welcome_message(mlist, address, language, delivery_mode)
if mlist.admin_notify_mchanges:
send_admin_subscription_notice(
mlist, address, display_name, language)
diff --git a/src/mailman/app/notifications.py b/src/mailman/app/notifications.py
index 2a803d5dc..1fa1fe01e 100644
--- a/src/mailman/app/notifications.py
+++ b/src/mailman/app/notifications.py
@@ -65,44 +65,36 @@ def _get_message(uri_template, mlist, language):
-def send_welcome_message(mlist, address, language, delivery_mode, text=''):
+def send_welcome_message(mlist, member, language, text=''):
"""Send a welcome message to a subscriber.
Prepending to the standard welcome message template is the mailing list's
welcome message, if there is one.
- :param mlist: the mailing list
+ :param mlist: The mailing list.
:type mlist: IMailingList
- :param address: The address to respond to
- :type address: string
- :param language: the language of the response
+ :param member: The member to send the welcome message to.
+ :param address: IMember
+ :param language: The language of the response.
:type language: ILanguage
- :param delivery_mode: the type of delivery the subscriber is getting
- :type delivery_mode: DeliveryMode
"""
- welcome_message = _get_message(mlist.welcome_message_uri,
- mlist, language)
- # Find the IMember object which is subscribed to the mailing list, because
- # from there, we can get the member's options url.
- member = mlist.members.get_member(address)
- user_name = member.user.display_name
+ welcome_message = _get_message(mlist.welcome_message_uri, mlist, language)
options_url = member.options_url
# Get the text from the template.
+ display_name = ('' if member.user is None else member.user.display_name)
text = expand(welcome_message, dict(
fqdn_listname=mlist.fqdn_listname,
list_name=mlist.display_name,
listinfo_uri=mlist.script_url('listinfo'),
list_requests=mlist.request_address,
- user_name=user_name,
- user_address=address,
+ user_name=display_name,
+ user_address=member.address.email,
user_options_uri=options_url,
))
- if delivery_mode is not DeliveryMode.regular:
- digmode = _(' (Digest mode)')
- else:
- digmode = ''
+ digmode = ('' if member.delivery_mode is DeliveryMode.regular
+ else _(' (Digest mode)'))
msg = UserNotification(
- formataddr((user_name, address)),
+ formataddr((display_name, member.address.email)),
mlist.request_address,
_('Welcome to the "$mlist.display_name" mailing list${digmode}'),
text, language)
diff --git a/src/mailman/app/registrar.py b/src/mailman/app/registrar.py
index 252c2cf2a..aa4e35483 100644
--- a/src/mailman/app/registrar.py
+++ b/src/mailman/app/registrar.py
@@ -22,22 +22,23 @@ from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
'Registrar',
+ 'handle_ConfirmationNeededEvent',
]
import logging
from zope.component import getUtility
+from zope.event import notify
from zope.interface import implementer
-from mailman.app.notifications import send_welcome_message
from mailman.core.i18n import _
from mailman.email.message import UserNotification
from mailman.interfaces.address import IEmailValidator
from mailman.interfaces.listmanager import IListManager
from mailman.interfaces.member import DeliveryMode, MemberRole
from mailman.interfaces.pending import IPendable, IPendings
-from mailman.interfaces.registrar import IRegistrar
+from mailman.interfaces.registrar import ConfirmationNeededEvent, IRegistrar
from mailman.interfaces.templates import ITemplateLoader
from mailman.interfaces.usermanager import IUserManager
from mailman.utilities.datetime import now
@@ -69,29 +70,13 @@ class Registrar:
type=PendableRegistration.PEND_KEY,
email=email,
display_name=display_name,
- delivery_mode=delivery_mode.name)
- pendable['list_name'] = mlist.fqdn_listname
+ delivery_mode=delivery_mode.name,
+ list_id=mlist.list_id)
token = getUtility(IPendings).add(pendable)
- # There are three ways for a user to confirm their subscription. They
- # can reply to the original message and let the VERP'd return address
- # 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 ' + token
- confirm_address = mlist.confirm_address(token)
- # For i18n interpolation.
- confirm_url = mlist.domain.confirm_url(token)
- email_address = email
- domain_name = mlist.domain.mail_host
- contact_address = mlist.domain.contact_address
- # Send a verification email to the address.
- template = getUtility(ITemplateLoader).get(
- 'mailman:///{0}/{1}/confirm.txt'.format(
- mlist.fqdn_listname,
- mlist.preferred_language.code))
- text = _(template)
- msg = UserNotification(email, confirm_address, subject, text)
- msg.send(mlist)
+ # We now have everything we need to begin the confirmation dance.
+ # Trigger the event to start the ball rolling, and return the
+ # generated token.
+ notify(ConfirmationNeededEvent(mlist, pendable, token))
return token
def confirm(self, token):
@@ -103,7 +88,6 @@ class Registrar:
missing = object()
email = pendable.get('email', missing)
display_name = pendable.get('display_name', missing)
- list_name = pendable.get('list_name', missing)
pended_delivery_mode = pendable.get('delivery_mode', 'regular')
try:
delivery_mode = DeliveryMode[pended_delivery_mode]
@@ -151,20 +135,43 @@ class Registrar:
pass
address.verified_on = now()
# If this registration is tied to a mailing list, subscribe the person
- # to the list right now, and possibly send a welcome message.
- list_name = pendable.get('list_name')
- if list_name is not None:
- mlist = getUtility(IListManager).get(list_name)
- if mlist:
+ # to the list right now. That will generate a SubscriptionEvent,
+ # which can be used to send a welcome message.
+ list_id = pendable.get('list_id')
+ if list_id is not None:
+ mlist = getUtility(IListManager).get_by_list_id(list_id)
+ if mlist is not None:
member = mlist.subscribe(address, MemberRole.member)
member.preferences.delivery_mode = delivery_mode
- if mlist.send_welcome_message:
- send_welcome_message(mlist,
- address.email,
- mlist.preferred_language,
- delivery_mode)
return True
def discard(self, token):
# Throw the record away.
getUtility(IPendings).confirm(token)
+
+
+
+def handle_ConfirmationNeededEvent(event):
+ if not isinstance(event, ConfirmationNeededEvent):
+ return
+ # There are three ways for a user to confirm their subscription. They
+ # can reply to the original message and let the VERP'd return address
+ # 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
+ mlist = getUtility(IListManager).get_by_list_id(event.pendable['list_id'])
+ confirm_address = mlist.confirm_address(event.token)
+ # For i18n interpolation.
+ confirm_url = mlist.domain.confirm_url(event.token)
+ email_address = event.pendable['email']
+ domain_name = mlist.domain.mail_host
+ contact_address = mlist.domain.contact_address
+ # Send a verification email to the address.
+ template = getUtility(ITemplateLoader).get(
+ 'mailman:///{0}/{1}/confirm.txt'.format(
+ mlist.fqdn_listname,
+ mlist.preferred_language.code))
+ text = _(template)
+ msg = UserNotification(email_address, confirm_address, subject, text)
+ msg.send(mlist)
diff --git a/src/mailman/app/tests/test_bounces.py b/src/mailman/app/tests/test_bounces.py
index 284ade92b..5eb518786 100644
--- a/src/mailman/app/tests/test_bounces.py
+++ b/src/mailman/app/tests/test_bounces.py
@@ -198,6 +198,7 @@ class TestSendProbe(unittest.TestCase):
def setUp(self):
self._mlist = create_list('test@example.com')
+ self._mlist.send_welcome_message = False
self._member = add_member(self._mlist, 'anne@example.com',
'Anne Person', 'xxx',
DeliveryMode.regular, 'en')
@@ -355,6 +356,7 @@ class TestProbe(unittest.TestCase):
def setUp(self):
self._mlist = create_list('test@example.com')
+ self._mlist.send_welcome_message = False
self._member = add_member(self._mlist, 'anne@example.com',
'Anne Person', 'xxx',
DeliveryMode.regular, 'en')
@@ -395,6 +397,7 @@ class TestMaybeForward(unittest.TestCase):
site_owner: postmaster@example.com
""")
self._mlist = create_list('test@example.com')
+ self._mlist.send_welcome_message = False
self._msg = mfs("""\
From: bouncer@example.com
To: test-bounces@example.com
diff --git a/src/mailman/app/tests/test_notifications.py b/src/mailman/app/tests/test_notifications.py
index d37bd2906..4cdc1c01c 100644
--- a/src/mailman/app/tests/test_notifications.py
+++ b/src/mailman/app/tests/test_notifications.py
@@ -33,10 +33,9 @@ from zope.component import getUtility
from mailman.app.lifecycle import create_list
from mailman.app.membership import add_member
-from mailman.app.notifications import send_welcome_message
from mailman.config import config
from mailman.interfaces.languages import ILanguageManager
-from mailman.interfaces.member import DeliveryMode
+from mailman.interfaces.member import DeliveryMode, MemberRole
from mailman.testing.helpers import get_queue_messages
from mailman.testing.layers import ConfigLayer
@@ -82,11 +81,8 @@ Welcome to the $list_name mailing list.
shutil.rmtree(self.var_dir)
def test_welcome_message(self):
- en = getUtility(ILanguageManager).get('en')
add_member(self._mlist, 'anne@example.com', 'Anne Person',
'password', DeliveryMode.regular, 'en')
- send_welcome_message(self._mlist, 'anne@example.com', en,
- DeliveryMode.regular)
# Now there's one message in the virgin queue.
messages = get_queue_messages('virgin')
self.assertEqual(len(messages), 1)
@@ -110,16 +106,42 @@ Welcome to the Test List mailing list.
'mailman:///$listname/$language/welcome.txt')
# Add the xx language and subscribe Anne using it.
manager = getUtility(ILanguageManager)
- xx = manager.add('xx', 'us-ascii', 'Xlandia')
+ manager.add('xx', 'us-ascii', 'Xlandia')
add_member(self._mlist, 'anne@example.com', 'Anne Person',
'password', DeliveryMode.regular, 'xx')
- send_welcome_message(self._mlist, 'anne@example.com', xx,
- DeliveryMode.regular)
# Now there's one message in the virgin queue.
messages = get_queue_messages('virgin')
self.assertEqual(len(messages), 1)
message = messages[0].msg
self.assertEqual(str(message['subject']),
'Welcome to the "Test List" mailing list')
- self.assertEqual(message.get_payload(),
- 'You just joined the Test List mailing list!')
+ self.assertMultiLineEqual(
+ message.get_payload(),
+ 'You just joined the Test List mailing list!')
+
+ def test_no_welcome_message_to_owners(self):
+ # Welcome messages go only to mailing list members, not to owners.
+ add_member(self._mlist, 'anne@example.com', 'Anne Person',
+ 'password', DeliveryMode.regular, 'xx',
+ MemberRole.owner)
+ # There is no welcome message in the virgin queue.
+ messages = get_queue_messages('virgin')
+ self.assertEqual(len(messages), 0)
+
+ def test_no_welcome_message_to_nonmembers(self):
+ # Welcome messages go only to mailing list members, not to nonmembers.
+ add_member(self._mlist, 'anne@example.com', 'Anne Person',
+ 'password', DeliveryMode.regular, 'xx',
+ MemberRole.nonmember)
+ # There is no welcome message in the virgin queue.
+ messages = get_queue_messages('virgin')
+ self.assertEqual(len(messages), 0)
+
+ def test_no_welcome_message_to_moderators(self):
+ # Welcome messages go only to mailing list members, not to moderators.
+ add_member(self._mlist, 'anne@example.com', 'Anne Person',
+ 'password', DeliveryMode.regular, 'xx',
+ MemberRole.moderator)
+ # There is no welcome message in the virgin queue.
+ messages = get_queue_messages('virgin')
+ self.assertEqual(len(messages), 0)
diff --git a/src/mailman/app/tests/test_registration.py b/src/mailman/app/tests/test_registration.py
new file mode 100644
index 000000000..ff128ae6f
--- /dev/null
+++ b/src/mailman/app/tests/test_registration.py
@@ -0,0 +1,132 @@
+# Copyright (C) 2012 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 email address registration."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'TestEmailValidation',
+ 'TestRegistration',
+ ]
+
+
+import unittest
+
+from zope.component import getUtility
+
+from mailman.app.lifecycle import create_list
+from mailman.interfaces.address import InvalidEmailAddressError
+from mailman.interfaces.pending import IPendings
+from mailman.interfaces.registrar import ConfirmationNeededEvent, IRegistrar
+from mailman.testing.helpers import event_subscribers
+from mailman.testing.layers import ConfigLayer
+
+
+
+class TestEmailValidation(unittest.TestCase):
+ """Test basic email validation."""
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ self.registrar = getUtility(IRegistrar)
+ self.mlist = create_list('alpha@example.com')
+
+ def test_empty_string_is_invalid(self):
+ self.assertRaises(InvalidEmailAddressError,
+ self.registrar.register, self.mlist,
+ '')
+
+ def test_no_spaces_allowed(self):
+ self.assertRaises(InvalidEmailAddressError,
+ self.registrar.register, self.mlist,
+ 'some name@example.com')
+
+ def test_no_angle_brackets(self):
+ self.assertRaises(InvalidEmailAddressError,
+ self.registrar.register, self.mlist,
+ '<script>@example.com')
+
+ def test_ascii_only(self):
+ self.assertRaises(InvalidEmailAddressError,
+ self.registrar.register, self.mlist,
+ '\xa0@example.com')
+
+ def test_domain_required(self):
+ self.assertRaises(InvalidEmailAddressError,
+ self.registrar.register, self.mlist,
+ 'noatsign')
+
+ def test_full_domain_required(self):
+ self.assertRaises(InvalidEmailAddressError,
+ self.registrar.register, self.mlist,
+ 'nodom@ain')
+
+
+
+class TestRegistration(unittest.TestCase):
+ """Test registration."""
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ self.registrar = getUtility(IRegistrar)
+ self.mlist = create_list('alpha@example.com')
+
+ def test_confirmation_event_received(self):
+ # Registering an email address generates an event.
+ def capture_event(event):
+ self.assertIsInstance(event, ConfirmationNeededEvent)
+ with event_subscribers(capture_event):
+ self.registrar.register(self.mlist, 'anne@example.com')
+
+ def test_event_mlist(self):
+ # The event has a reference to the mailing list being subscribed to.
+ def capture_event(event):
+ self.assertIs(event.mlist, self.mlist)
+ with event_subscribers(capture_event):
+ self.registrar.register(self.mlist, 'anne@example.com')
+
+ def test_event_pendable(self):
+ # The event has an IPendable which contains additional information.
+ def capture_event(event):
+ pendable = event.pendable
+ self.assertEqual(pendable['type'], 'registration')
+ self.assertEqual(pendable['email'], 'anne@example.com')
+ # The key is present, but the value is None.
+ self.assertIsNone(pendable['display_name'])
+ # The default is regular delivery.
+ self.assertEqual(pendable['delivery_mode'], 'regular')
+ self.assertEqual(pendable['list_id'], 'alpha.example.com')
+ with event_subscribers(capture_event):
+ self.registrar.register(self.mlist, 'anne@example.com')
+
+ def test_token(self):
+ # Registering the email address returns a token, and this token links
+ # back to the pendable.
+ captured_events = []
+ def capture_event(event):
+ captured_events.append(event)
+ with event_subscribers(capture_event):
+ token = self.registrar.register(self.mlist, 'anne@example.com')
+ self.assertEqual(len(captured_events), 1)
+ event = captured_events[0]
+ self.assertEqual(event.token, token)
+ pending = getUtility(IPendings).confirm(token)
+ self.assertEqual(pending, event.pendable)
diff --git a/src/mailman/bin/runner.py b/src/mailman/bin/runner.py
index 6e8922687..b8d1fc66a 100644
--- a/src/mailman/bin/runner.py
+++ b/src/mailman/bin/runner.py
@@ -156,7 +156,7 @@ def main():
cannot be run once."""))
parser.add_argument(
'-l', '--list',
- default=False, action='store_true',
+ default=None, action='store_true',
help=_('List the available runner names and exit.'))
parser.add_argument(
'-v', '--verbose',
diff --git a/src/mailman/commands/tests/test_confirm.py b/src/mailman/commands/tests/test_confirm.py
index 513a541b0..19a9068bc 100644
--- a/src/mailman/commands/tests/test_confirm.py
+++ b/src/mailman/commands/tests/test_confirm.py
@@ -55,11 +55,10 @@ class TestConfirm(unittest.TestCase):
def tearDown(self):
reset_the_world()
-
+
def test_welcome_message(self):
# A confirmation causes a welcome message to be sent to the member, if
# enabled by the mailing list.
- #
status = self._command.process(
self._mlist, Message(), {}, (self._token,), Results())
self.assertEqual(status, ContinueProcessing.yes)
@@ -68,7 +67,7 @@ class TestConfirm(unittest.TestCase):
self.assertEqual(len(messages), 1)
# Grab the welcome message.
welcome = messages[0].msg
- self.assertEqual(welcome['subject'],
+ self.assertEqual(welcome['subject'],
'Welcome to the "Test" mailing list')
self.assertEqual(welcome['to'], 'Anne Person <anne@example.com>')
diff --git a/src/mailman/core/initialize.py b/src/mailman/core/initialize.py
index 0d0b829fe..6f1e10068 100644
--- a/src/mailman/core/initialize.py
+++ b/src/mailman/core/initialize.py
@@ -115,7 +115,7 @@ def initialize_1(config_path=None):
# write our files. Specifically we must have g+rw and we probably want
# o-rwx although I think in most cases it doesn't hurt if other can read
# or write the files.
- os.umask(007)
+ os.umask(0o007)
# Initialize configuration event subscribers. This must be done before
# setting up the configuration system.
from mailman.app.events import initialize as initialize_events
diff --git a/src/mailman/handlers/docs/acknowledge.rst b/src/mailman/handlers/docs/acknowledge.rst
index 479aa4ea6..2235985ad 100644
--- a/src/mailman/handlers/docs/acknowledge.rst
+++ b/src/mailman/handlers/docs/acknowledge.rst
@@ -10,6 +10,7 @@ acknowledgment.
>>> mlist = create_list('test@example.com')
>>> mlist.display_name = 'Test'
>>> mlist.preferred_language = 'en'
+ >>> mlist.send_welcome_message = False
>>> # XXX This will almost certainly change once we've worked out the web
>>> # space layout for mailing lists now.
diff --git a/src/mailman/interfaces/registrar.py b/src/mailman/interfaces/registrar.py
index 28a245d37..8a77ca8f6 100644
--- a/src/mailman/interfaces/registrar.py
+++ b/src/mailman/interfaces/registrar.py
@@ -26,6 +26,7 @@ from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
+ 'ConfirmationNeededEvent',
'IRegistrar',
]
@@ -34,6 +35,29 @@ from zope.interface import Interface
+class ConfirmationNeededEvent:
+ """Triggered when an address needs confirmation.
+
+ Addresses must be verified before they can receive messages or post to
+ mailing list. When an address is registered with Mailman, via the
+ `IRegistrar` interface, an `IPendable` is created which represents the
+ pending registration. This pending registration is stored in the
+ database, keyed by a token. Then this event is triggered.
+
+ There may be several ways to confirm an email address. On some sites,
+ registration may immediately produce a verification, e.g. because it is on
+ a known intranet. Or verification may occur via external database lookup
+ (e.g. LDAP). On most public mailing lists, a mail-back confirmation is
+ sent to the address, and only if they reply to the mail-back, or click on
+ an embedded link, is the registered address confirmed.
+ """
+ def __init__(self, mlist, pendable, token):
+ self.mlist = mlist
+ self.pendable = pendable
+ self.token = token
+
+
+
class IRegistrar(Interface):
"""Interface for registering and verifying email addresses and users.
diff --git a/src/mailman/model/docs/registration.rst b/src/mailman/model/docs/registration.rst
index 58e9d7a86..77cb75890 100644
--- a/src/mailman/model/docs/registration.rst
+++ b/src/mailman/model/docs/registration.rst
@@ -104,7 +104,7 @@ But this address is waiting for confirmation.
delivery_mode: regular
display_name : Anne Person
email : aperson@example.com
- list_name : alpha@example.com
+ list_id : alpha.example.com
type : registration
diff --git a/src/mailman/rest/wsgiapp.py b/src/mailman/rest/wsgiapp.py
index e114c2ee9..b7ad3d698 100644
--- a/src/mailman/rest/wsgiapp.py
+++ b/src/mailman/rest/wsgiapp.py
@@ -17,7 +17,7 @@
"""Basic WSGI Application object for REST server."""
-from __future__ import absolute_import, unicode_literals
+from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
diff --git a/src/mailman/runners/docs/digester.rst b/src/mailman/runners/docs/digester.rst
index d0231a895..96e8739e3 100644
--- a/src/mailman/runners/docs/digester.rst
+++ b/src/mailman/runners/docs/digester.rst
@@ -10,6 +10,7 @@ starts by a number of messages being posted to the mailing list.
>>> mlist.digest_size_threshold = 0.6
>>> mlist.volume = 1
>>> mlist.next_digest_number = 1
+ >>> mlist.send_welcome_message = False
>>> from string import Template
>>> process = config.handlers['to-digest'].process
diff --git a/src/mailman/runners/tests/test_bounce.py b/src/mailman/runners/tests/test_bounce.py
index 27c8d6076..315a81c22 100644
--- a/src/mailman/runners/tests/test_bounce.py
+++ b/src/mailman/runners/tests/test_bounce.py
@@ -57,6 +57,7 @@ class TestBounceRunner(unittest.TestCase):
def setUp(self):
self._mlist = create_list('test@example.com')
+ self._mlist.send_welcome_message = False
self._bounceq = config.switchboards['bounces']
self._runner = make_testable_runner(BounceRunner, 'bounces')
self._anne = getUtility(IUserManager).create_address(
diff --git a/src/mailman/runners/tests/test_join.py b/src/mailman/runners/tests/test_join.py
index 205f7ceb0..fbea9e661 100644
--- a/src/mailman/runners/tests/test_join.py
+++ b/src/mailman/runners/tests/test_join.py
@@ -52,6 +52,7 @@ class TestJoin(unittest.TestCase):
def setUp(self):
self._mlist = create_list('test@example.com')
+ self._mlist.send_welcome_message = False
self._commandq = config.switchboards['command']
self._runner = make_testable_runner(CommandRunner, 'command')
diff --git a/src/mailman/testing/helpers.py b/src/mailman/testing/helpers.py
index 24a7692ae..1ef9e964e 100644
--- a/src/mailman/testing/helpers.py
+++ b/src/mailman/testing/helpers.py
@@ -327,6 +327,8 @@ def call_api(url, data=None, method=None, username=None, password=None):
else:
method = 'POST'
method = method.upper()
+ if method in ('POST', 'PUT', 'PATCH') and data is None:
+ data = urlencode({}, doseq=True)
basic_auth = '{0}:{1}'.format(
(config.webservice.admin_user if username is None else username),
(config.webservice.admin_pass if password is None else password))