Commit 783163c4 authored by Barry Warsaw's avatar Barry Warsaw

* Refactor add_member() so that it uses a RequestRecord namedtuple.

* RequestRecord contains no password key so these are not part of the held
  requests database any more.
* Pending record contains `email` now instead of `address`.
parent 18b79808
......@@ -238,14 +238,16 @@ Holding subscription requests
For closed lists, subscription requests will also be held for moderator
approval. In this case, several pieces of information related to the
subscription must be provided, including the subscriber's address and real
name, their password (possibly hashed), what kind of delivery option they are
choosing and their preferred language.
name, what kind of delivery option they are choosing and their preferred
language.
>>> from mailman.app.moderator import hold_subscription
>>> from mailman.interfaces.member import DeliveryMode
>>> from mailman.interfaces.subscriptions import RequestRecord
>>> req_id = hold_subscription(
... mlist, 'fred@example.org', 'Fred Person',
... '{NONE}abcxyz', DeliveryMode.regular, 'en')
... mlist,
... RequestRecord('fred@example.org', 'Fred Person',
... DeliveryMode.regular, 'en'))
Disposing of membership change requests
......@@ -269,8 +271,9 @@ The held subscription can also be discarded.
Gwen tries to subscribe to the mailing list, but...
>>> req_id = hold_subscription(
... mlist, 'gwen@example.org', 'Gwen Person',
... '{NONE}zyxcba', DeliveryMode.regular, 'en')
... mlist,
... RequestRecord('gwen@example.org', 'Gwen Person',
... DeliveryMode.regular, 'en'))
...her request is rejected...
......@@ -305,8 +308,9 @@ mailing list.
>>> mlist.send_welcome_message = False
>>> req_id = hold_subscription(
... mlist, 'herb@example.org', 'Herb Person',
... 'abcxyz', DeliveryMode.regular, 'en')
... mlist,
... RequestRecord('herb@example.org', 'Herb Person',
... DeliveryMode.regular, 'en'))
The moderators accept the subscription request.
......@@ -399,8 +403,9 @@ list is configured to send them.
Iris tries to subscribe to the mailing list.
>>> req_id = hold_subscription(mlist, 'iris@example.org', 'Iris Person',
... 'password', DeliveryMode.regular, 'en')
>>> req_id = hold_subscription(mlist,
... RequestRecord('iris@example.org', 'Iris Person',
... DeliveryMode.regular, 'en'))
There's now a message in the virgin queue, destined for the list owner.
......@@ -491,8 +496,9 @@ can get a welcome message.
>>> mlist.admin_notify_mchanges = False
>>> mlist.send_welcome_message = True
>>> req_id = hold_subscription(mlist, 'kate@example.org', 'Kate Person',
... 'password', DeliveryMode.regular, 'en')
>>> req_id = hold_subscription(mlist,
... RequestRecord('kate@example.org', 'Kate Person',
... DeliveryMode.regular, 'en'))
>>> handle_subscription(mlist, req_id, Action.accept)
>>> messages = get_queue_messages('virgin')
>>> len(messages)
......
......@@ -27,7 +27,6 @@ __all__ = [
from email.utils import formataddr
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.bans import IBanManager
......@@ -40,8 +39,7 @@ from zope.component import getUtility
def add_member(mlist, email, display_name, password, delivery_mode, language,
role=MemberRole.member):
def add_member(mlist, record, role=MemberRole.member):
"""Add a member right now.
The member's subscription must be approved by whatever policy the list
......@@ -49,16 +47,8 @@ def add_member(mlist, email, display_name, password, delivery_mode, language,
:param mlist: The mailing list to add the member to.
:type mlist: `IMailingList`
:param email: The email address to subscribe.
:type email: str
:param display_name: The subscriber's full name.
:type display_name: str
:param password: The subscriber's plain text password.
:type password: str
:param delivery_mode: The delivery mode the subscriber has chosen.
:type delivery_mode: DeliveryMode
:param language: The language that the subscriber is going to use.
:type language: str
:param record: a subscription request record.
:type record: RequestRecord
:param role: The membership role for this subscription.
:type role: `MemberRole`
:return: The just created member.
......@@ -69,62 +59,41 @@ def add_member(mlist, email, display_name, password, delivery_mode, language,
:raises MembershipIsBannedError: if the membership is not allowed.
"""
# Check to see if the email address is banned.
if IBanManager(mlist).is_banned(email):
raise MembershipIsBannedError(mlist, email)
# See if there's already a user linked with the given address.
if IBanManager(mlist).is_banned(record.email):
raise MembershipIsBannedError(mlist, record.email)
# Make sure there is a user linked with the given address.
user_manager = getUtility(IUserManager)
user = user_manager.get_user(email)
if user is None:
# A user linked to this address does not yet exist. Is the address
# itself known but just not linked to a user?
address = user_manager.get_address(email)
if address is None:
# Nope, we don't even know about this address, so create both the
# user and address now.
user = user_manager.create_user(email, display_name)
# Do it this way so we don't have to flush the previous change.
address = list(user.addresses)[0]
else:
# The address object exists, but it's not linked to a user.
# Create the user and link it now.
user = user_manager.create_user()
user.display_name = (
display_name if display_name else address.display_name)
user.link(address)
# Encrypt the password using the currently selected hash scheme.
user.password = config.password_context.encrypt(password)
user.preferences.preferred_language = language
user = user_manager.make_user(record.email, record.display_name)
# Encrypt the password using the currently selected hash scheme.
user.preferences.preferred_language = record.language
# Subscribe the address, not the user.
# We're looking for two versions of the email address, the case
# preserved version and the case insensitive version. We'll
# subscribe the version with matching case if it exists, otherwise
# we'll use one of the matching case-insensitively ones. It's
# undefined which one we pick.
case_preserved = None
case_insensitive = None
for address in user.addresses:
if address.original_email == record.email:
case_preserved = address
if address.email == record.email.lower():
case_insensitive = address
assert case_preserved is not None or case_insensitive is not None, (
'Could not find a linked address for: {}'.format(record.email))
address = (case_preserved if case_preserved is not None
else case_insensitive)
# Create the member and set the appropriate preferences. It's
# possible we're subscribing the lower cased version of the address;
# if that's already subscribed re-issue the exception with the correct
# email address (i.e. the one passed in here).
try:
member = mlist.subscribe(address, role)
member.preferences.delivery_mode = delivery_mode
else:
# The user exists and is linked to the case-insensitive address.
# We're looking for two versions of the email address, the case
# preserved version and the case insensitive version. We'll
# subscribe the version with matching case if it exists, otherwise
# we'll use one of the matching case-insensitively ones. It's
# undefined which one we pick.
case_preserved = None
case_insensitive = None
for address in user.addresses:
if address.original_email == email:
case_preserved = address
if address.email == email.lower():
case_insensitive = address
assert case_preserved is not None or case_insensitive is not None, (
'Could not find a linked address for: {}'.format(email))
address = (case_preserved if case_preserved is not None
else case_insensitive)
# Create the member and set the appropriate preferences. It's
# possible we're subscribing the lower cased version of the address;
# if that's already subscribed re-issue the exception with the correct
# email address (i.e. the one passed in here).
try:
member = mlist.subscribe(address, role)
except AlreadySubscribedError as error:
raise AlreadySubscribedError(
error.fqdn_listname, email, error.role)
member.preferences.preferred_language = language
member.preferences.delivery_mode = delivery_mode
except AlreadySubscribedError as error:
raise AlreadySubscribedError(
error.fqdn_listname, record.email, error.role)
member.preferences.preferred_language = record.language
member.preferences.delivery_mode = record.delivery_mode
return member
......
......@@ -44,6 +44,7 @@ from mailman.interfaces.member import (
AlreadySubscribedError, DeliveryMode, NotAMemberError)
from mailman.interfaces.messages import IMessageStore
from mailman.interfaces.requests import IListRequests, RequestType
from mailman.interfaces.subscriptions import RequestRecord
from mailman.utilities.datetime import now
from mailman.utilities.i18n import make
from zope.component import getUtility
......@@ -192,26 +193,26 @@ def handle_message(mlist, id, action,
def hold_subscription(mlist, address, display_name, password, mode, language):
def hold_subscription(mlist, record):
data = dict(when=now().isoformat(),
address=address,
display_name=display_name,
password=password,
delivery_mode=mode.name,
language=language)
# Now hold this request. We'll use the address as the key.
email=record.email,
display_name=record.display_name,
delivery_mode=record.delivery_mode.name,
language=record.language)
# Now hold this request. We'll use the email address as the key.
requestsdb = IListRequests(mlist)
request_id = requestsdb.hold_request(
RequestType.subscription, address, data)
RequestType.subscription, record.email, data)
vlog.info('%s: held subscription request from %s',
mlist.fqdn_listname, address)
mlist.fqdn_listname, record.email)
# Possibly notify the administrator in default list language
if mlist.admin_immed_notify:
email = record.email # XXX: seems unnecessary
subject = _(
'New subscription request to $mlist.display_name from $address')
'New subscription request to $mlist.display_name from $email')
text = make('subauth.txt',
mailing_list=mlist,
username=address,
username=record.email,
listname=mlist.fqdn_listname,
admindb_url=mlist.script_url('admindb'),
)
......@@ -236,19 +237,19 @@ def handle_subscription(mlist, id, action, comment=None):
elif action is Action.reject:
key, data = requestdb.get_request(id)
_refuse(mlist, _('Subscription request'),
data['address'],
data['email'],
comment or _('[No reason given]'),
lang=getUtility(ILanguageManager)[data['language']])
elif action is Action.accept:
key, data = requestdb.get_request(id)
delivery_mode = DeliveryMode[data['delivery_mode']]
address = data['address']
email = data['email']
display_name = data['display_name']
language = getUtility(ILanguageManager)[data['language']]
password = data['password']
try:
add_member(mlist, address, display_name, password,
delivery_mode, language)
add_member(
mlist,
RequestRecord(email, display_name, delivery_mode, language))
except AlreadySubscribedError:
# The address got subscribed in some other way after the original
# request was made and accepted.
......@@ -256,9 +257,9 @@ def handle_subscription(mlist, id, action, comment=None):
else:
if mlist.admin_notify_mchanges:
send_admin_subscription_notice(
mlist, address, display_name, language)
mlist, email, display_name, language)
slog.info('%s: new %s, %s %s', mlist.fqdn_listname,
delivery_mode, formataddr((display_name, address)),
delivery_mode, formataddr((display_name, email)),
'via admin approval')
else:
raise AssertionError('Unexpected action: {0}'.format(action))
......@@ -267,20 +268,20 @@ def handle_subscription(mlist, id, action, comment=None):
def hold_unsubscription(mlist, address):
data = dict(address=address)
def hold_unsubscription(mlist, email):
data = dict(email=email)
requestsdb = IListRequests(mlist)
request_id = requestsdb.hold_request(
RequestType.unsubscription, address, data)
RequestType.unsubscription, email, data)
vlog.info('%s: held unsubscription request from %s',
mlist.fqdn_listname, address)
mlist.fqdn_listname, email)
# Possibly notify the administrator of the hold
if mlist.admin_immed_notify:
subject = _(
'New unsubscription request from $mlist.display_name by $address')
'New unsubscription request from $mlist.display_name by $email')
text = make('unsubauth.txt',
mailing_list=mlist,
address=address,
email=email,
listname=mlist.fqdn_listname,
admindb_url=mlist.script_url('admindb'),
)
......@@ -297,7 +298,7 @@ def hold_unsubscription(mlist, address):
def handle_unsubscription(mlist, id, action, comment=None):
requestdb = IListRequests(mlist)
key, data = requestdb.get_request(id)
address = data['address']
email = data['email']
if action is Action.defer:
# Nothing to do.
return
......@@ -306,16 +307,16 @@ def handle_unsubscription(mlist, id, action, comment=None):
pass
elif action is Action.reject:
key, data = requestdb.get_request(id)
_refuse(mlist, _('Unsubscription request'), address,
_refuse(mlist, _('Unsubscription request'), email,
comment or _('[No reason given]'))
elif action is Action.accept:
key, data = requestdb.get_request(id)
try:
delete_member(mlist, address)
delete_member(mlist, email)
except NotAMemberError:
# User has already been unsubscribed.
pass
slog.info('%s: deleted %s', mlist.fqdn_listname, address)
slog.info('%s: deleted %s', mlist.fqdn_listname, email)
else:
raise AssertionError('Unexpected action: {0}'.format(action))
# Delete the request from the database.
......
......@@ -24,21 +24,19 @@ __all__ = [
from operator import attrgetter
from passlib.utils import generate_password as generate
from sqlalchemy import and_, or_
from uuid import UUID
from zope.component import getUtility
from zope.interface import implementer
from mailman.app.membership import add_member, delete_member
from mailman.config import config
from mailman.core.constants import system_preferences
from mailman.database.transaction import dbconnection
from mailman.interfaces.listmanager import (
IListManager, ListDeletingEvent, NoSuchListError)
from mailman.interfaces.member import DeliveryMode, MemberRole
from mailman.interfaces.subscriptions import (
ISubscriptionService, MissingUserError)
ISubscriptionService, MissingUserError, RequestRecord)
from mailman.interfaces.usermanager import IUserManager
from mailman.model.member import Member
......@@ -148,16 +146,11 @@ class SubscriptionService:
if isinstance(subscriber, str):
if display_name is None:
display_name, at, domain = subscriber.partition('@')
# Because we want to keep the REST API simple, there is no
# password or language given to us. We'll use the system's
# default language for the user's default language. We'll set the
# password to a system default. This will have to get reset since
# it can't be retrieved. Note that none of these are used unless
# the address is completely new to us.
password = generate(int(config.passwords.password_length))
return add_member(mlist, subscriber, display_name, password,
delivery_mode,
system_preferences.preferred_language, role)
return add_member(
mlist,
RequestRecord(subscriber, display_name, delivery_mode,
system_preferences.preferred_language),
role)
else:
# We have to assume it's a UUID.
assert isinstance(subscriber, UUID), 'Not a UUID'
......
......@@ -42,6 +42,7 @@ from mailman.interfaces.bounce import UnrecognizedBounceDisposition
from mailman.interfaces.languages import ILanguageManager
from mailman.interfaces.member import DeliveryMode, MemberRole
from mailman.interfaces.pending import IPendings
from mailman.interfaces.subscriptions import RequestRecord
from mailman.interfaces.usermanager import IUserManager
from mailman.testing.helpers import (
LogFileMark, get_queue_messages, specialized_message_from_string as mfs)
......@@ -193,9 +194,10 @@ 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')
self._member = add_member(
self._mlist,
RequestRecord('anne@example.com', 'Anne Person',
DeliveryMode.regular, 'en'))
self._msg = mfs("""\
From: bouncer@example.com
To: anne@example.com
......@@ -285,9 +287,10 @@ class TestSendProbeNonEnglish(unittest.TestCase):
def setUp(self):
self._mlist = create_list('test@example.com')
self._member = add_member(self._mlist, 'anne@example.com',
'Anne Person', 'xxx',
DeliveryMode.regular, 'en')
self._member = add_member(
self._mlist,
RequestRecord('anne@example.com', 'Anne Person',
DeliveryMode.regular, 'en'))
self._msg = mfs("""\
From: bouncer@example.com
To: anne@example.com
......@@ -351,9 +354,10 @@ 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')
self._member = add_member(
self._mlist,
RequestRecord('anne@example.com', 'Anne Person',
DeliveryMode.regular, 'en'))
self._msg = mfs("""\
From: bouncer@example.com
To: anne@example.com
......
This diff is collapsed.
......@@ -19,16 +19,21 @@
__all__ = [
'TestModeration',
'TestUnsubscription',
]
import unittest
from mailman.app.lifecycle import create_list
from mailman.app.moderator import handle_message, hold_message
from mailman.app.moderator import (
handle_message, handle_subscription, handle_unsubscription, hold_message,
hold_subscription, hold_unsubscription)
from mailman.interfaces.action import Action
from mailman.interfaces.member import DeliveryMode
from mailman.interfaces.messages import IMessageStore
from mailman.interfaces.requests import IListRequests
from mailman.interfaces.subscriptions import RequestRecord
from mailman.runners.incoming import IncomingRunner
from mailman.runners.outgoing import OutgoingRunner
from mailman.runners.pipeline import PipelineRunner
......@@ -148,3 +153,26 @@ Message-ID: <alpha>
'Forward of moderated message')
self.assertEqual(messages[0].msgdata['recipients'],
['zack@example.com'])
class TestUnsubscription(unittest.TestCase):
"""Test unsubscription requests."""
layer = SMTPLayer
def setUp(self):
self._mlist = create_list('test@example.com')
self._request_db = IListRequests(self._mlist)
def test_unsubscribe_defer(self):
# When unsubscriptions must be approved by the moderator, but the
# moderator defers this decision.
token = hold_subscription(
self._mlist,
RequestRecord('anne@example.org', 'Anne Person',
DeliveryMode.regular, 'en'))
handle_subscription(self._mlist, token, Action.accept)
# Now hold and handle an unsubscription request.
token = hold_unsubscription(self._mlist, 'anne@example.org')
handle_unsubscription(self._mlist, token, Action.defer)
......@@ -32,6 +32,7 @@ from mailman.app.membership import add_member
from mailman.config import config
from mailman.interfaces.languages import ILanguageManager
from mailman.interfaces.member import DeliveryMode, MemberRole
from mailman.interfaces.subscriptions import RequestRecord
from mailman.testing.helpers import get_queue_messages
from mailman.testing.layers import ConfigLayer
from zope.component import getUtility
......@@ -78,8 +79,10 @@ Welcome to the $list_name mailing list.
shutil.rmtree(self.var_dir)
def test_welcome_message(self):
add_member(self._mlist, 'anne@example.com', 'Anne Person',
'password', DeliveryMode.regular, 'en')
add_member(
self._mlist,
RequestRecord('anne@example.com', 'Anne Person',
DeliveryMode.regular, 'en'))
# Now there's one message in the virgin queue.
messages = get_queue_messages('virgin')
self.assertEqual(len(messages), 1)
......@@ -104,8 +107,10 @@ Welcome to the Test List mailing list.
# Add the xx language and subscribe Anne using it.
manager = getUtility(ILanguageManager)
manager.add('xx', 'us-ascii', 'Xlandia')
add_member(self._mlist, 'anne@example.com', 'Anne Person',
'password', DeliveryMode.regular, 'xx')
add_member(
self._mlist,
RequestRecord('anne@example.com', 'Anne Person',
DeliveryMode.regular, 'xx'))
# Now there's one message in the virgin queue.
messages = get_queue_messages('virgin')
self.assertEqual(len(messages), 1)
......@@ -118,27 +123,33 @@ Welcome to 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)
add_member(
self._mlist,
RequestRecord('anne@example.com', 'Anne Person',
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)
add_member(
self._mlist,
RequestRecord('anne@example.com', 'Anne Person',
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)
add_member(
self._mlist,
RequestRecord('anne@example.com', 'Anne Person',
DeliveryMode.regular, 'xx'),
MemberRole.moderator)
# There is no welcome message in the virgin queue.
messages = get_queue_messages('virgin')
self.assertEqual(len(messages), 0)
......@@ -31,11 +31,14 @@ Posts by list members are moderated if the member's moderation action is not
deferred. The default setting for the moderation action of new members is
determined by the mailing list's settings. By default, a mailing list is not
set to moderate new member postings.
::
>>> from mailman.app.membership import add_member
>>> from mailman.interfaces.member import DeliveryMode
>>> member = add_member(mlist, 'anne@example.com', 'Anne', 'aaa',
... DeliveryMode.regular, 'en')
>>> from mailman.interfaces.subscriptions import RequestRecord
>>> member = add_member(mlist, RequestRecord('anne@example.com', 'Anne',
... DeliveryMode.regular, 'en'))
>>> member
<Member: Anne <anne@example.com> on test@example.com as MemberRole.member>
>>> print(member.moderation_action)
......
......@@ -27,15 +27,14 @@ import codecs
from email.utils import formataddr, parseaddr
from mailman.app.membership import add_member
from mailman.config import config
from mailman.core.i18n import _
from mailman.database.transaction import transactional
from mailman.interfaces.command import ICLISubCommand
from mailman.interfaces.listmanager import IListManager
from mailman.interfaces.member import (
AlreadySubscribedError, DeliveryMode, DeliveryStatus)
from mailman.interfaces.subscriptions import RequestRecord
from operator import attrgetter
from passlib.utils import generate_password as generate
from zope.component import getUtility
from zope.interface import implementer
......@@ -193,12 +192,11 @@ class Members:
continue
# Parse the line and ensure that the values are unicodes.
display_name, email = parseaddr(line)
# Give the user a default, user-friendly password.
password = generate(int(config.passwords.password_length))
try:
add_member(mlist, email, display_name, password,
DeliveryMode.regular,
mlist.preferred_language.code)
add_member(mlist,
RequestRecord(email, display_name,
DeliveryMode.regular,
mlist.preferred_language.code))
except AlreadySubscribedError:
# It's okay if the address is already subscribed, just
# print a warning and continue.
......
......@@ -36,12 +36,16 @@ Once the mailing list add some members, they will be displayed.
>>> from mailman.interfaces.member import DeliveryMode
>>> from mailman.app.membership import add_member
>>> add_member(mlist1, 'anne@example.com', 'Anne Person', 'xxx',
... DeliveryMode.regular, mlist1.preferred_language.code)
>>> from mailman.interfaces.subscriptions import RequestRecord
>>> add_member(mlist1, RequestRecord('anne@example.com', 'Anne Person',
... DeliveryMode.regular,
... mlist1.preferred_language.code))
<Member: Anne Person <anne@example.com>
on test1@example.com as MemberRole.member>
>>> add_member(mlist1, 'bart@example.com', 'Bart Person', 'xxx',
... DeliveryMode.regular, mlist1.preferred_language.code)
>>> add_member(mlist1, RequestRecord('bart@example.com', 'Bart Person',
... DeliveryMode.regular,
... mlist1.preferred_language.code))
<Member: Bart Person <bart@example.com>
on test1@example.com as MemberRole.member>
......@@ -52,8 +56,9 @@ Once the mailing list add some members, they will be displayed.
Members are displayed in alphabetical order based on their address.
::
>>> add_member(mlist1, 'anne@aaaxample.com', 'Anne Person', 'xxx',
... DeliveryMode.regular, mlist1.preferred_language.code)
>>> add_member(mlist1, RequestRecord('anne@aaaxample.com', 'Anne Person',
... DeliveryMode.regular,
... mlist1.preferred_language.code))
<Member: Anne Person <anne@aaaxample.com>
on test1@example.com as MemberRole.member>
......@@ -136,21 +141,29 @@ status is enabled...
::
>>> from mailman.interfaces.member import DeliveryStatus
>>> member = mlist1.members.get_member('anne@aaaxample.com')
>>> member.preferences.delivery_status = DeliveryStatus.by_moderator
>>> member = mlist1.members.get_member('bart@example.com')
>>> member.preferences.delivery_status = DeliveryStatus.by_user
>>> member = add_member(
... mlist1, 'cris@example.com', 'Cris Person', 'xxx',
... DeliveryMode.regular, mlist1.preferred_language.code)
... mlist1,
... RequestRecord('cris@example.com', 'Cris Person',
... DeliveryMode.regular,
... mlist1.preferred_language.code))
>>> member.preferences.delivery_status = DeliveryStatus.unknown
>>> member = add_member(
... mlist1, 'dave@example.com', 'Dave Person', 'xxx',
... DeliveryMode.regular, mlist1.preferred_language.code)
... mlist1,
... RequestRecord('dave@example.com', 'Dave Person',
... DeliveryMode.regular,
... mlist1.preferred_language.code))
>>> member.preferences.delivery_status = DeliveryStatus.enabled
>>> member = add_member(
... mlist1, 'elly@example.com', 'Elly Person', 'xxx',
... DeliveryMode.regular, mlist1.preferred_language.code)
... mlist1,
... RequestRecord('elly@example.com', 'Elly Person',
... DeliveryMode.regular,
... mlist1.preferred_language.code))
>>> member.preferences.delivery_status = DeliveryStatus.by_bounces
>>> args.nomail = 'enabled'
......
......@@ -19,9 +19,12 @@
__all__ = [
'ISubscriptionService',
'RequestRecord',
]
from collections import namedtuple
from mailman.interfaces.errors import MailmanError
from mailman.interfaces.member import DeliveryMode, MemberRole
from zope.interface import Interface
......@@ -39,6 +42,19 @@ class MissingUserError(MailmanError):
return self.user_id
_RequestRecord = namedtuple(
'RequestRecord',
'email display_name delivery_mode, language')
def RequestRecord(email, display_name='',
delivery_mode=DeliveryMode.regular,
language=None):
if language is None:
from mailman.core.constants import system_preferences
language = system_preferences.preferred_language
return _RequestRecord(email, display_name, delivery_mode, language)
class ISubscriptionService(Interface):
"""General Subscription services."""
......
......@@ -43,6 +43,22 @@ class IUserManager(Interface):
registered.
"""
def make_user(email, display_name=None):
"""Create a new user linked to an address object.
If ``email`` is already associated with an existing `IAddress`