summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2007-09-16 07:15:53 -0400
committerBarry Warsaw2007-09-16 07:15:53 -0400
commit0a3e802568a0bfaf9ceade4614a52db9a90b905c (patch)
tree844fdd4288578727c9ba85030b899fdb3c68c685
parent7fefa8a8f477a88c73fee417143afcf809b530b4 (diff)
downloadmailman-0a3e802568a0bfaf9ceade4614a52db9a90b905c.tar.gz
mailman-0a3e802568a0bfaf9ceade4614a52db9a90b905c.tar.zst
mailman-0a3e802568a0bfaf9ceade4614a52db9a90b905c.zip
-rw-r--r--Mailman/MailList.py81
-rw-r--r--Mailman/Message.py8
-rw-r--r--Mailman/app/membership.py153
-rw-r--r--Mailman/app/moderator.py144
-rw-r--r--Mailman/docs/requests.txt254
-rw-r--r--Mailman/templates/en/adminsubscribeack.txt2
-rw-r--r--Mailman/templates/en/subauth.txt4
-rw-r--r--Mailman/templates/en/subscribeack.txt26
8 files changed, 493 insertions, 179 deletions
diff --git a/Mailman/MailList.py b/Mailman/MailList.py
index b828bf5af..cbcaab275 100644
--- a/Mailman/MailList.py
+++ b/Mailman/MailList.py
@@ -588,85 +588,6 @@ class MailList(object, HTMLFormatter, Deliverer,
raise Errors.MMNeedApproval, _(
'subscriptions to %(realname)s require moderator approval')
- def ApprovedAddMember(self, userdesc, ack=None, admin_notif=None, text='',
- whence=''):
- """Add a member right now.
-
- The member's subscription must be approved by what ever policy the
- list enforces.
-
- userdesc is as above in AddMember().
-
- ack is a flag that specifies whether the user should get an
- acknowledgement of their being subscribed. Default is to use the
- list's default flag value.
-
- admin_notif is a flag that specifies whether the list owner should get
- an acknowledgement of this subscription. Default is to use the list's
- default flag value.
- """
- assert self.Locked()
- # Set up default flag values
- if ack is None:
- ack = self.send_welcome_msg
- if admin_notif is None:
- admin_notif = self.admin_notify_mchanges
- # Suck values out of userdesc, and apply defaults.
- email = Utils.LCDomain(userdesc.address)
- name = getattr(userdesc, 'fullname', '')
- lang = getattr(userdesc, 'language', self.preferred_language)
- digest = getattr(userdesc, 'digest', None)
- password = getattr(userdesc, 'password', Utils.MakeRandomPassword())
- if digest is None:
- if self.nondigestable:
- digest = 0
- else:
- digest = 1
- # Let's be extra cautious
- Utils.ValidateEmail(email)
- if self.isMember(email):
- raise Errors.MMAlreadyAMember, email
- # Check for banned address here too for admin mass subscribes
- # and confirmations.
- pattern = self.GetBannedPattern(email)
- if pattern:
- raise Errors.MembershipIsBanned, pattern
- # Do the actual addition
- self.addNewMember(email, realname=name, digest=digest,
- password=password, language=lang)
- self.setMemberOption(email, config.DisableMime,
- 1 - self.mime_is_default_digest)
- self.setMemberOption(email, config.Moderate,
- self.default_member_moderation)
- # Now send and log results
- if digest:
- kind = ' (digest)'
- else:
- kind = ''
- slog.info('%s: new%s %s, %s', self.internal_name(),
- kind, formataddr((name, email)), whence)
- if ack:
- self.SendSubscribeAck(email, self.getMemberPassword(email),
- digest, text)
- if admin_notif:
- lang = self.preferred_language
- otrans = i18n.get_translation()
- i18n.set_language(lang)
- try:
- realname = self.real_name
- subject = _('%(realname)s subscription notification')
- finally:
- i18n.set_translation(otrans)
- if isinstance(name, unicode):
- name = name.encode(Utils.GetCharSet(lang), 'replace')
- text = Utils.maketext(
- "adminsubscribeack.txt",
- {"listname" : realname,
- "member" : formataddr((name, email)),
- }, mlist=self)
- msg = Message.OwnerNotification(self, subject, text)
- msg.send(self)
-
def DeleteMember(self, name, whence=None, admin_notif=None, userack=True):
realname, email = parseaddr(name)
if self.unsubscribe_policy == 0:
@@ -1173,7 +1094,7 @@ bad regexp in bounce_matching_header line: %s
"""Returns matched entry in ban_list if email matches.
Otherwise returns None.
"""
- return self.GetPattern(email, self.ban_list)
+ return self.ban_list and self.GetPattern(email, self.ban_list)
def HasAutoApprovedSender(self, sender):
"""Returns True and logs if sender matches address or pattern
diff --git a/Mailman/Message.py b/Mailman/Message.py
index 5214d3caa..47262d8a3 100644
--- a/Mailman/Message.py
+++ b/Mailman/Message.py
@@ -263,15 +263,17 @@ class OwnerNotification(UserNotification):
"""Like user notifications, but this message goes to the list owners."""
def __init__(self, mlist, subject=None, text=None, tomoderators=True):
- recips = mlist.owner[:]
if tomoderators:
- recips.extend(mlist.moderator)
+ roster = mlist.moderators
+ else:
+ roster = mlist.owners
+ recips = [address.address for address in roster.addresses]
sender = config.SITE_OWNER_ADDRESS
lang = mlist.preferred_language
UserNotification.__init__(self, recips, sender, subject, text, lang)
# Hack the To header to look like it's going to the -owner address
del self['to']
- self['To'] = mlist.GetOwnerEmail()
+ self['To'] = mlist.owner_address
self._sender = sender
def _enqueue(self, mlist, **_kws):
diff --git a/Mailman/app/membership.py b/Mailman/app/membership.py
new file mode 100644
index 000000000..abd2778f6
--- /dev/null
+++ b/Mailman/app/membership.py
@@ -0,0 +1,153 @@
+# Copyright (C) 2007 by the Free Software Foundation, Inc.
+#
+# This program 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 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+"""Application support for membership management."""
+
+from email.utils import formataddr
+
+from Mailman import Message
+from Mailman import Utils
+from Mailman import i18n
+from Mailman.configuration import config
+from Mailman.constants import DeliveryMode, MemberRole
+
+_ = i18n._
+__i18n_templates__ = True
+
+
+
+def add_member(mlist, address, realname, password, delivery_mode, language,
+ ack=None, admin_notif=None, text=''):
+ """Add a member right now.
+
+ The member's subscription must be approved by what ever policy the
+ list enforces.
+
+ ack is a flag that specifies whether the user should get an
+ acknowledgement of their being subscribed. Default is to use the
+ list's default flag value.
+
+ admin_notif is a flag that specifies whether the list owner should get
+ an acknowledgement of this subscription. Default is to use the list's
+ default flag value.
+ """
+ # Set up default flag values
+ if ack is None:
+ ack = mlist.send_welcome_msg
+ if admin_notif is None:
+ admin_notif = mlist.admin_notify_mchanges
+ # Let's be extra cautious.
+ Utils.ValidateEmail(address)
+ if mlist.members.get_member(address) is not None:
+ raise Errors.AlreadySubscribedError(address)
+ # Check for banned address here too for admin mass subscribes and
+ # confirmations.
+ pattern = mlist.GetBannedPattern(address)
+ if pattern:
+ raise Errors.MembershipIsBanned(pattern)
+ # Do the actual addition. First, see if there's already a user linked
+ # with the given address.
+ user = config.db.user_manager.get_user(address)
+ 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_obj = config.db.user_manager.get_address(address)
+ if address_obj is None:
+ # Nope, we don't even know about this address, so create both the
+ # user and address now.
+ user = config.db.user_manager.create_user(address, realname)
+ # Do it this way so we don't have to flush the previous change.
+ address_obj = 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 = config.db.user_manager.create_user()
+ user.real_name = (realname if realname else address_obj.real_name)
+ user,link(address_obj)
+ # Since created the user, then the member, and set preferences on the
+ # appropriate object.
+ user.password = password
+ user.preferences.preferred_language = language
+ member = address_obj.subscribe(mlist, MemberRole.member)
+ member.preferences.delivery_mode = delivery_mode
+ else:
+ # The user exists and is linked to the address.
+ for address_obj in user.addresses:
+ if address_obj.address == address:
+ break
+ else:
+ raise AssertionError(
+ 'User should have had linked address: %s', address)
+ # Create the member and set the appropriate preferences.
+ member = address_obj.subscribe(mlist, MemberRole.member)
+ member.preferences.preferred_language = language
+ member.preferences.delivery_mode = delivery_mode
+## mlist.setMemberOption(email, config.Moderate,
+## mlist.default_member_moderation)
+ # Send notifications.
+ if ack:
+ send_welcome_message(mlist, address, language, delivery_mode, text)
+ if admin_notif:
+ otrans = i18n.get_translation()
+ i18n.set_language(mlist.preferred_language)
+ try:
+ subject = _('$mlist.real_name subscription notification')
+ finally:
+ i18n.set_translation(otrans)
+ if isinstance(realname, unicode):
+ realname = name.encode(Utils.GetCharSet(language), 'replace')
+ text = Utils.maketext(
+ 'adminsubscribeack.txt',
+ {'listname' : mlist.real_name,
+ 'member' : formataddr((realname, address)),
+ }, mlist=mlist)
+ msg = Message.OwnerNotification(mlist, subject, text)
+ msg.send(mlist)
+
+
+
+def send_welcome_message(mlist, address, language, delivery_mode, text=''):
+ if mlist.welcome_msg:
+ welcome = Utils.wrap(mlist.welcome_msg) + '\n'
+ else:
+ welcome = ''
+ # Find the IMember object which is subscribed to the mailing list, because
+ # from there, we can get the member's options url. XXX we have to flush
+ # the database here otherwise the get_member() call returns None.
+ from Mailman.database import flush; flush()
+ member = mlist.members.get_member(address)
+ options_url = member.options_url
+ # Get the text from the template.
+ text += Utils.maketext(
+ 'subscribeack.txt', {
+ 'real_name' : mlist.real_name,
+ 'posting_address' : mlist.fqdn_listname,
+ 'listinfo_url' : mlist.script_url('listinfo'),
+ 'optionsurl' : options_url,
+ 'request_address' : mlist.request_address,
+ 'welcome' : welcome,
+ }, lang=language, mlist=mlist)
+ if delivery_mode is not DeliveryMode.regular:
+ digmode = _(' (Digest mode)')
+ else:
+ digmode = ''
+ msg = Message.UserNotification(
+ address, mlist.request_address,
+ _('Welcome to the "$mlist.real_name" mailing list${digmode}'),
+ text, language)
+ msg['X-No-Archive'] = 'yes'
+ msg.send(mlist, verp=config.VERP_PERSONALIZED_DELIVERIES)
diff --git a/Mailman/app/moderator.py b/Mailman/app/moderator.py
index aa1b75b09..54e8956f0 100644
--- a/Mailman/app/moderator.py
+++ b/Mailman/app/moderator.py
@@ -26,20 +26,23 @@ __all__ = [
import logging
from datetime import datetime
-from email.utils import formatdate, getaddresses, make_msgid
+from email.utils import formataddr, formatdate, getaddresses, make_msgid
+from Mailman import Errors
from Mailman import Message
from Mailman import Utils
from Mailman import i18n
from Mailman.Queue.sbcache import get_switchboard
+from Mailman.app.membership import add_member
from Mailman.configuration import config
-from Mailman.constants import Action
+from Mailman.constants import Action, DeliveryMode
from Mailman.interfaces import RequestType
_ = i18n._
__i18n_templates__ = True
-log = logging.getLogger('mailman.vette')
+vlog = logging.getLogger('mailman.vette')
+slog = logging.getLogger('mailman.subscribe')
@@ -82,6 +85,8 @@ def handle_message(mlist, id, action,
# Handle the action.
rejection = None
global_id = msgdata['_mod_global_id']
+ sender = msgdata['_mod_sender']
+ subject = msgdata['_mod_subject']
if action is Action.defer:
# Nothing to do, but preserve the message for later.
preserve = True
@@ -89,8 +94,6 @@ def handle_message(mlist, id, action,
rejection = 'Discarded'
elif action is Action.reject:
rejection = 'Refused'
- sender = msgdata['_mod_sender']
- subject = msgdata['_mod_subject']
member = mlist.members.get_member(sender)
if member:
language = member.preferred_language
@@ -118,8 +121,8 @@ def handle_message(mlist, id, action,
# message directly here can lead to a huge delay in web turnaround.
# Log the moderation and add a header.
msg['X-Mailman-Approved-At'] = formatdate(localtime=True)
- log.info('held message approved, message-id: %s',
- msg.get('message-id', 'n/a'))
+ vlog.info('held message approved, message-id: %s',
+ msg.get('message-id', 'n/a'))
# Stick the message back in the incoming queue for further
# processing.
inq = get_switchboard(config.INQUEUE_DIR)
@@ -161,78 +164,87 @@ def handle_message(mlist, id, action,
requestdb.delete_request(id)
# Log the rejection
if rejection:
- note = """$listname: $rejection posting:
-\tFrom: $sender
-\tSubject: $subject"""
+ note = """%s: %s posting:
+\tFrom: %s
+\tSubject: %s"""
if comment:
note += '\n\tReason: ' + comment
- log.info(note)
+ vlog.info(note, mlist.fqdn_listname, rejection, sender, subject)
-def HoldSubscription(self, addr, fullname, password, digest, lang):
- # Assure that the database is open for writing
- self._opendb()
- # Get the next unique id
- id = self._next_id
- # Save the information to the request database. for held subscription
- # entries, each record in the database will be one of the following
- # format:
- #
- # the time the subscription request was received
- # the subscriber's address
- # the subscriber's selected password (TBD: is this safe???)
- # the digest flag
- # the user's preferred language
- data = time.time(), addr, fullname, password, digest, lang
- self._db[id] = (SUBSCRIPTION, data)
- #
- # TBD: this really shouldn't go here but I'm not sure where else is
- # appropriate.
- log.info('%s: held subscription request from %s',
- self.internal_name(), addr)
+
+def hold_subscription(mlist, address, realname, password, mode, language):
+ data = dict(when=datetime.now().isoformat(),
+ address=address,
+ realname=realname,
+ password=password,
+ delivery_mode=str(mode),
+ language=language)
+ # Now hold this request. We'll use the address as the key.
+ requestsdb = config.db.requests.get_list_requests(mlist)
+ request_id = requestsdb.hold_request(
+ RequestType.subscription, address, data)
+ vlog.info('%s: held subscription request from %s',
+ mlist.fqdn_listname, address)
# Possibly notify the administrator in default list language
- if self.admin_immed_notify:
- realname = self.real_name
+ if mlist.admin_immed_notify:
+ realname = mlist.real_name
subject = _(
- 'New subscription request to list %(realname)s from %(addr)s')
+ 'New subscription request to list $realname from $address')
text = Utils.maketext(
'subauth.txt',
- {'username' : addr,
- 'listname' : self.internal_name(),
- 'hostname' : self.host_name,
- 'admindb_url': self.GetScriptURL('admindb', absolute=1),
- }, mlist=self)
+ {'username' : address,
+ 'listname' : mlist.fqdn_listname,
+ 'admindb_url': mlist.script_url('admindb'),
+ }, mlist=mlist)
# This message should appear to come from the <list>-owner so as
# to avoid any useless bounce processing.
- owneraddr = self.GetOwnerEmail()
- msg = Message.UserNotification(owneraddr, owneraddr, subject, text,
- self.preferred_language)
- msg.send(self, **{'tomoderators': 1})
+ msg = Message.UserNotification(
+ mlist.owner_address, mlist.owner_address,
+ subject, text, mlist.preferred_language)
+ msg.send(mlist, tomoderators=True)
+ return request_id
-def __handlesubscription(self, record, value, comment):
- stime, addr, fullname, password, digest, lang = record
- if value == config.DEFER:
- return DEFER
- elif value == config.DISCARD:
+
+
+def handle_subscription(mlist, id, action, comment=None):
+ requestdb = config.db.requests.get_list_requests(mlist)
+ if action is Action.defer:
+ # Nothing to do.
+ return
+ elif action is Action.discard:
+ # Nothing to do except delete the request from the database.
pass
- elif value == config.REJECT:
- self._refuse(_('Subscription request'), addr,
- comment or _('[No reason given]'),
- lang=lang)
- else:
- # subscribe
- assert value == config.SUBSCRIBE
+ elif action is Action.reject:
+ key, data = requestdb.get_request(id)
+ _refuse(mlist, _('Subscription request'),
+ data['address'],
+ comment or _('[No reason given]'),
+ lang=data['language'])
+ elif action is Action.accept:
+ key, data = requestdb.get_request(id)
+ enum_value = data['delivery_mode'].split('.')[-1]
+ delivery_mode = DeliveryMode(enum_value)
+ address = data['address']
+ realname = data['realname']
try:
- userdesc = UserDesc(addr, fullname, password, digest, lang)
- self.ApprovedAddMember(userdesc, whence='via admin approval')
- except Errors.MMAlreadyAMember:
- # User has already been subscribed, after sending the request
+ add_member(mlist, address, realname, data['password'],
+ delivery_mode, data['language'])
+ except Errors.AlreadySubscribedError:
+ # The address got subscribed in some other way after the original
+ # request was made and accepted.
pass
- # TBD: disgusting hack: ApprovedAddMember() can end up closing
- # the request database.
- self._opendb()
- return REMOVE
+ slog.info('%s: new %s, %s %s', mlist.fqdn_listname,
+ delivery_mode, formataddr((realname, address)),
+ 'via admin approval')
+ else:
+ raise AssertionError('Unexpected action: %s' % action)
+ # Delete the request from the database.
+ requestdb.delete_request(id)
+ return
+
+
def HoldUnsubscription(self, addr):
# Assure the database is open for writing
self._opendb()
@@ -240,8 +252,8 @@ def HoldUnsubscription(self, addr):
id = self._next_id
# All we need to do is save the unsubscribing address
self._db[id] = (UNSUBSCRIPTION, addr)
- log.info('%s: held unsubscription request from %s',
- self.internal_name(), addr)
+ vlog.info('%s: held unsubscription request from %s',
+ self.internal_name(), addr)
# Possibly notify the administrator of the hold
if self.admin_immed_notify:
realname = self.real_name
diff --git a/Mailman/docs/requests.txt b/Mailman/docs/requests.txt
index f66a87ddd..05be06aac 100644
--- a/Mailman/docs/requests.txt
+++ b/Mailman/docs/requests.txt
@@ -21,10 +21,10 @@ And another helper for displaying messages in the virgin queue.
>>> from Mailman.Queue.sbcache import get_switchboard
>>> virginq = get_switchboard(config.VIRGINQUEUE_DIR)
- >>> def dequeue(whichq=None):
+ >>> def dequeue(whichq=None, expected_count=1):
... if whichq is None:
... whichq = virginq
- ... assert len(whichq.files) == 1, (
+ ... assert len(whichq.files) == expected_count, (
... 'Unexpected file count: %d' % len(whichq.files))
... filebase = whichq.files[0]
... qmsg, qdata = whichq.dequeue(filebase)
@@ -449,18 +449,256 @@ message is held for approval, there will be a message placed in the virgin
queue when the message is held.
>>> mlist.admin_immed_notify = True
+ >>> # XXX This will almost certainly change once we've worked out the web
+ >>> # space layout for mailing lists now.
+ >>> mlist._data.web_page_url = 'http://www.example.com/'
>>> flush()
>>> id_4 = moderator.hold_subscription(mlist,
... 'cperson@example.org', 'Claire Person',
- ... '{NONE}zyxcba, DeliveryMode.regular, 'en')
+ ... '{NONE}zyxcba', DeliveryMode.regular, 'en')
>>> flush()
>>> requests.get_request(id_4) is not None
True
>>> qmsg, qdata = dequeue()
>>> print qmsg.as_string()
- XXX
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ Content-Transfer-Encoding: 7bit
+ Subject: New subscription request to list A Test List from
+ cperson@example.org
+ From: alist-owner@example.com
+ To: alist-owner@example.com
+ Message-ID: ...
+ Date: ...
+ Precedence: bulk
+ <BLANKLINE>
+ Your authorization is required for a mailing list subscription request
+ approval:
+ <BLANKLINE>
+ For: cperson@example.org
+ List: alist@example.com
+ <BLANKLINE>
+ At your convenience, visit:
+ <BLANKLINE>
+ http://www.example.com/admindb/alist@example.com
+ <BLANKLINE>
+ to process the request.
+ <BLANKLINE>
>>> sorted(qdata.items())
- XXX
+ [('_parsemsg', False),
+ ('listname', 'alist@example.com'),
+ ('nodecorate', True),
+ ('received_time', ...),
+ ('recips', ['alist-owner@example.com']),
+ ('reduced_list_headers', True), ('tomoderators', True), ('version', 3)]
+
+Once held, the moderator can select one of several dispositions. The most
+trivial is to simply defer a decision for now.
+
+ >>> moderator.handle_subscription(mlist, id_3, Action.defer)
+ >>> flush()
+ >>> requests.get_request(id_3) is not None
+ True
+
+The held subscription can also be discarded.
+
+ >>> moderator.handle_subscription(mlist, id_3, Action.discard)
+ >>> flush()
+ >>> print requests.get_request(id_3)
+ None
+
+The request can be rejected, in which case a message is sent to the
+subscriber.
+
+ >>> moderator.handle_subscription(mlist, id_4, Action.reject,
+ ... 'This is a closed list')
+ >>> flush()
+ >>> print requests.get_request(id_4)
+ None
+ >>> qmsg, qdata = dequeue()
+ >>> print qmsg.as_string()
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ Content-Transfer-Encoding: 7bit
+ Subject: Request to mailing list "A Test List" rejected
+ From: alist-bounces@example.com
+ To: cperson@example.org
+ Message-ID: ...
+ Date: ...
+ Precedence: bulk
+ <BLANKLINE>
+ Your request to the alist@example.com mailing list
+ <BLANKLINE>
+ Subscription request
+ <BLANKLINE>
+ has been rejected by the list moderator. The moderator gave the
+ following reason for rejecting your request:
+ <BLANKLINE>
+ "This is a closed list"
+ <BLANKLINE>
+ Any questions or comments should be directed to the list administrator
+ at:
+ <BLANKLINE>
+ alist-owner@example.com
+ <BLANKLINE>
+ >>> sorted(qdata.items())
+ [('_parsemsg', False), ('listname', 'alist@example.com'),
+ ('nodecorate', True),
+ ('received_time', ...),
+ ('recips', ['cperson@example.org']),
+ ('reduced_list_headers', True), ('version', 3)]
+
+Or the subscription can be approved/accepted. This subscribes the member to
+the mailing list.
+
+ >>> mlist.send_welcome_msg = True
+ >>> id_4 = moderator.hold_subscription(mlist,
+ ... 'fperson@example.org', 'Frank Person',
+ ... '{NONE}abcxyz', DeliveryMode.regular, 'en')
+ >>> flush()
+
+A message will be sent to the moderators telling them about the held
+subscription and the fact that they may need to approve it.
+
+ >>> qmsg, qdata = dequeue()
+ >>> print qmsg.as_string()
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ Content-Transfer-Encoding: 7bit
+ Subject: New subscription request to list A Test List from
+ fperson@example.org
+ From: alist-owner@example.com
+ To: alist-owner@example.com
+ Message-ID: ...
+ Date: ...
+ Precedence: bulk
+ <BLANKLINE>
+ Your authorization is required for a mailing list subscription request
+ approval:
+ <BLANKLINE>
+ For: fperson@example.org
+ List: alist@example.com
+ <BLANKLINE>
+ At your convenience, visit:
+ <BLANKLINE>
+ http://www.example.com/admindb/alist@example.com
+ <BLANKLINE>
+ to process the request.
+ <BLANKLINE>
+ >>> sorted(qdata.items())
+ [('_parsemsg', False), ('listname', 'alist@example.com'),
+ ('nodecorate', True), ('received_time', ...),
+ ('recips', ['alist-owner@example.com']),
+ ('reduced_list_headers', True), ('tomoderators', True), ('version', 3)]
+
+Accept the subscription request.
+
+ >>> mlist.admin_notify_mchanges = True
+ >>> moderator.handle_subscription(mlist, id_4, Action.accept)
+ >>> flush()
+
+There are now two messages in the virgin queue. One is a welcome message
+being sent to the user and the other is a subscription notification that is
+sent to the moderators. The only good way to tell which is which is to look
+at the recipient list.
+
+ >>> qmsg_1, qdata_1 = dequeue(expected_count=2)
+ >>> qmsg_2, qdata_2 = dequeue()
+ >>> if 'fperson@example.org' in qdata_1['recips']:
+ ... # The first message is the welcome message
+ ... welcome_qmsg = qmsg_1
+ ... welcome_qdata = qdata_1
+ ... admin_qmsg = qmsg_2
+ ... admin_qdata = qdata_2
+ ... else:
+ ... welcome_qmsg = qmsg_2
+ ... welcome_qdata = qdata_2
+ ... admin_qmsg = qmsg_1
+ ... admin_qdata = qdata_1
+
+The welcome message is sent to the person who just subscribed.
+
+ >>> print welcome_qmsg.as_string()
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ Content-Transfer-Encoding: 7bit
+ Subject: Welcome to the "A Test List" mailing list
+ From: alist-request@example.com
+ To: fperson@example.org
+ X-No-Archive: yes
+ Message-ID: ...
+ Date: ...
+ Precedence: bulk
+ <BLANKLINE>
+ Welcome to the "A Test List" mailing list!
+ <BLANKLINE>
+ To post to this list, send your email to:
+ <BLANKLINE>
+ alist@example.com
+ <BLANKLINE>
+ General information about the mailing list is at:
+ <BLANKLINE>
+ http://www.example.com/listinfo/alist@example.com
+ <BLANKLINE>
+ If you ever want to unsubscribe or change your options (eg, switch to
+ or from digest mode, change your password, etc.), visit your
+ subscription page at:
+ <BLANKLINE>
+ http://example.com/fperson@example.org
+ <BLANKLINE>
+ You can also make such adjustments via email by sending a message to:
+ <BLANKLINE>
+ alist-request@example.com
+ <BLANKLINE>
+ with the word 'help' in the subject or body (don't include the
+ quotes), and you will get back a message with instructions. You will
+ need your password to change your options, but for security purposes,
+ this email is not included here. There is also a button on your
+ options page that will send your current password to you.
+ <BLANKLINE>
+ >>> sorted(welcome_qdata.items())
+ [('_parsemsg', False), ('listname', 'alist@example.com'),
+ ('nodecorate', True), ('received_time', ...),
+ ('recips', ['fperson@example.org']),
+ ('reduced_list_headers', True), ('verp', False), ('version', 3)]
+
+The admin message is sent to the moderators.
+
+ >>> print admin_qmsg.as_string()
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ Content-Transfer-Encoding: 7bit
+ Subject: A Test List subscription notification
+ From: changeme@example.com
+ To: alist-owner@example.com
+ Message-ID: ...
+ Date: ...
+ Precedence: bulk
+ <BLANKLINE>
+ Frank Person <fperson@example.org> has been successfully subscribed to
+ A Test List.
+ <BLANKLINE>
+ >>> sorted(admin_qdata.items())
+ [('_parsemsg', False), ('envsender', 'changeme@example.com'),
+ ('listname', 'alist@example.com'),
+ ('nodecorate', True), ('received_time', ...),
+ ('recips', []), ('reduced_list_headers', True), ('version', 3)]
+
+Frank Person is now a member of the mailing list.
+
+ >>> member = mlist.members.get_member('fperson@example.org')
+ >>> member
+ <Member: Frank Person <fperson@example.org>
+ on alist@example.com as MemberRole.member>
+ >>> member.preferred_language
+ 'en'
+ >>> print member.delivery_mode
+ DeliveryMode.regular
+ >>> user = config.db.user_manager.get_user(member.address.address)
+ >>> user.real_name
+ 'Frank Person'
+ >>> user.password
+ '{NONE}abcxyz'
Holding unsubscription requests
@@ -477,14 +715,12 @@ unsubscription holds can send the list's moderators an immediate notification.
>>> flush()
>>> address_1 = list(user_1.addresses)[0]
>>> address_1.subscribe(mlist, MemberRole.member)
- <Member: <dperson@example.com> on
- _xtest@example.com as MemberRole.member>
+ <Member: dperson@example.com on alist@example.com as MemberRole.member>
>>> user_2 = config.db.user_manager.create_user('eperson@example.com')
>>> flush()
>>> address_2 = list(user_2.addresses)[0]
>>> address_2.subscribe(mlist, MemberRole.member)
- <Member: <eperson@example.com> on
- _xtest@example.com as MemberRole.member>
+ <Member: eperson@example.com on alist@example.com as MemberRole.member>
>>> flush()
>>> id_5 = moderator.hold_unsubscription(mlist, 'dperson@example.com')
>>> flush()
diff --git a/Mailman/templates/en/adminsubscribeack.txt b/Mailman/templates/en/adminsubscribeack.txt
index 388a3a240..5e8ee6cf8 100644
--- a/Mailman/templates/en/adminsubscribeack.txt
+++ b/Mailman/templates/en/adminsubscribeack.txt
@@ -1,3 +1 @@
%(member)s has been successfully subscribed to %(listname)s.
-
-
diff --git a/Mailman/templates/en/subauth.txt b/Mailman/templates/en/subauth.txt
index 9c20c3dac..869be71c7 100644
--- a/Mailman/templates/en/subauth.txt
+++ b/Mailman/templates/en/subauth.txt
@@ -2,10 +2,10 @@ Your authorization is required for a mailing list subscription request
approval:
For: %(username)s
- List: %(listname)s@%(hostname)s
+ List: %(listname)s
At your convenience, visit:
%(admindb_url)s
-
+
to process the request.
diff --git a/Mailman/templates/en/subscribeack.txt b/Mailman/templates/en/subscribeack.txt
index fad433f28..03f56c022 100644
--- a/Mailman/templates/en/subscribeack.txt
+++ b/Mailman/templates/en/subscribeack.txt
@@ -1,8 +1,8 @@
-Welcome to the %(real_name)s@%(host_name)s mailing list!
+Welcome to the "%(real_name)s" mailing list!
%(welcome)s
To post to this list, send your email to:
- %(emailaddr)s
+ %(posting_address)s
General information about the mailing list is at:
@@ -13,21 +13,13 @@ from digest mode, change your password, etc.), visit your subscription
page at:
%(optionsurl)s
-%(umbrella)s
-You can also make such adjustments via email by sending a message to:
-
- %(real_name)s-request@%(host_name)s
-with the word `help' in the subject or body (don't include the
-quotes), and you will get back a message with instructions.
-
-You must know your password to change your options (including changing
-the password, itself) or to unsubscribe. It is:
+You can also make such adjustments via email by sending a message to:
- %(password)s
+ %(request_address)s
-Normally, Mailman will remind you of your %(host_name)s mailing list
-passwords once every month, although you can disable this if you
-prefer. This reminder will also include instructions on how to
-unsubscribe or change your account options. There is also a button on
-your options page that will email your current password to you.
+with the word 'help' in the subject or body (don't include the quotes), and
+you will get back a message with instructions. You will need your password to
+change your options, but for security purposes, this email is not included
+here. There is also a button on your options page that will send your current
+password to you.