diff options
| author | Barry Warsaw | 2007-09-16 07:15:53 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2007-09-16 07:15:53 -0400 |
| commit | 0a3e802568a0bfaf9ceade4614a52db9a90b905c (patch) | |
| tree | 844fdd4288578727c9ba85030b899fdb3c68c685 | |
| parent | 7fefa8a8f477a88c73fee417143afcf809b530b4 (diff) | |
| download | mailman-0a3e802568a0bfaf9ceade4614a52db9a90b905c.tar.gz mailman-0a3e802568a0bfaf9ceade4614a52db9a90b905c.tar.zst mailman-0a3e802568a0bfaf9ceade4614a52db9a90b905c.zip | |
Work out subscription holding and handling. ApprovedAddMember() is
gone. Fix up OwnerNotification class. Rewrite subauth.txt and
subscribeack.txt for new implementation, especially removing the
password notification from the latter. Add lots of tests.
| -rw-r--r-- | Mailman/MailList.py | 81 | ||||
| -rw-r--r-- | Mailman/Message.py | 8 | ||||
| -rw-r--r-- | Mailman/app/membership.py | 153 | ||||
| -rw-r--r-- | Mailman/app/moderator.py | 144 | ||||
| -rw-r--r-- | Mailman/docs/requests.txt | 254 | ||||
| -rw-r--r-- | Mailman/templates/en/adminsubscribeack.txt | 2 | ||||
| -rw-r--r-- | Mailman/templates/en/subauth.txt | 4 | ||||
| -rw-r--r-- | Mailman/templates/en/subscribeack.txt | 26 |
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. |
