diff options
| -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. |
