------------------------------------------------------------
revno: 6550
committer: Barry Warsaw <[EMAIL PROTECTED]>
branch nick: 3.0
timestamp: Sun 2007-09-16 07:15:53 -0400
message:
  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.
added:
  Mailman/app/membership.py
modified:
  Mailman/MailList.py
  Mailman/Message.py
  Mailman/app/moderator.py
  Mailman/docs/requests.txt
  Mailman/templates/en/adminsubscribeack.txt
  Mailman/templates/en/subauth.txt
  Mailman/templates/en/subscribeack.txt

=== added file 'Mailman/app/membership.py'
--- a/Mailman/app/membership.py 1970-01-01 00:00:00 +0000
+++ b/Mailman/app/membership.py 2007-09-16 11:15:53 +0000
@@ -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)

=== modified file 'Mailman/MailList.py'
--- a/Mailman/MailList.py       2007-09-09 17:22:27 +0000
+++ b/Mailman/MailList.py       2007-09-16 11:15:53 +0000
@@ -588,85 +588,6 @@
             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 @@
         """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

=== modified file 'Mailman/Message.py'
--- a/Mailman/Message.py        2007-08-01 21:05:06 +0000
+++ b/Mailman/Message.py        2007-09-16 11:15:53 +0000
@@ -263,15 +263,17 @@
     """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):

=== modified file 'Mailman/app/moderator.py'
--- a/Mailman/app/moderator.py  2007-09-09 23:13:36 +0000
+++ b/Mailman/app/moderator.py  2007-09-16 11:15:53 +0000
@@ -26,20 +26,23 @@
 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 @@
     # 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 @@
         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 @@
         # 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 @@
         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)
-
-
-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)
+        vlog.info(note, mlist.fqdn_listname, rejection, sender, subject)
+
+
+
+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})
-
-def __handlesubscription(self, record, value, comment):
-    stime, addr, fullname, password, digest, lang = record
-    if value == config.DEFER:
-        return DEFER
-    elif value == config.DISCARD:
+        msg = Message.UserNotification(
+            mlist.owner_address, mlist.owner_address,
+            subject, text, mlist.preferred_language)
+        msg.send(mlist, tomoderators=True)
+    return request_id
+
+
+
+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 @@
     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

=== modified file 'Mailman/docs/requests.txt'
--- a/Mailman/docs/requests.txt 2007-09-09 23:13:36 +0000
+++ b/Mailman/docs/requests.txt 2007-09-16 11:15:53 +0000
@@ -21,10 +21,10 @@
 
     >>> 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 @@
 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,
     ...     '[EMAIL PROTECTED]', '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
-    >>> sorted(qdata.items())
-    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
+     [EMAIL PROTECTED]
+    From: [EMAIL PROTECTED]
+    To: [EMAIL PROTECTED]
+    Message-ID: ...
+    Date: ...
+    Precedence: bulk
+    <BLANKLINE>
+    Your authorization is required for a mailing list subscription request
+    approval:
+    <BLANKLINE>
+        For:  [EMAIL PROTECTED]
+        List: [EMAIL PROTECTED]
+    <BLANKLINE>
+    At your convenience, visit:
+    <BLANKLINE>
+        http://www.example.com/admindb/[EMAIL PROTECTED]
+    <BLANKLINE>
+    to process the request.
+    <BLANKLINE>
+    >>> sorted(qdata.items())
+    [('_parsemsg', False),
+    ('listname', '[EMAIL PROTECTED]'),
+    ('nodecorate', True),
+    ('received_time', ...),
+    ('recips', ['[EMAIL PROTECTED]']),
+    ('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: [EMAIL PROTECTED]
+    To: [EMAIL PROTECTED]
+    Message-ID: ...
+    Date: ...
+    Precedence: bulk
+    <BLANKLINE>
+    Your request to the [EMAIL PROTECTED] 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>
+        [EMAIL PROTECTED]
+    <BLANKLINE>
+    >>> sorted(qdata.items())
+    [('_parsemsg', False), ('listname', '[EMAIL PROTECTED]'),
+    ('nodecorate', True),
+    ('received_time', ...),
+    ('recips', ['[EMAIL PROTECTED]']),
+    ('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,
+    ...     '[EMAIL PROTECTED]', '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
+     [EMAIL PROTECTED]
+    From: [EMAIL PROTECTED]
+    To: [EMAIL PROTECTED]
+    Message-ID: ...
+    Date: ...
+    Precedence: bulk
+    <BLANKLINE>
+    Your authorization is required for a mailing list subscription request
+    approval:
+    <BLANKLINE>
+        For:  [EMAIL PROTECTED]
+        List: [EMAIL PROTECTED]
+    <BLANKLINE>
+    At your convenience, visit:
+    <BLANKLINE>
+        http://www.example.com/admindb/[EMAIL PROTECTED]
+    <BLANKLINE>
+    to process the request.
+    <BLANKLINE>
+    >>> sorted(qdata.items())
+    [('_parsemsg', False), ('listname', '[EMAIL PROTECTED]'),
+     ('nodecorate', True), ('received_time', ...),
+     ('recips', ['[EMAIL PROTECTED]']),
+     ('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 '[EMAIL PROTECTED]' 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: [EMAIL PROTECTED]
+    To: [EMAIL PROTECTED]
+    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>
+      [EMAIL PROTECTED]
+    <BLANKLINE>
+    General information about the mailing list is at:
+    <BLANKLINE>
+      http://www.example.com/listinfo/[EMAIL PROTECTED]
+    <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/[EMAIL PROTECTED]
+    <BLANKLINE>
+    You can also make such adjustments via email by sending a message to:
+    <BLANKLINE>
+      [EMAIL PROTECTED]
+    <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', '[EMAIL PROTECTED]'),
+     ('nodecorate', True), ('received_time', ...),
+     ('recips', ['[EMAIL PROTECTED]']),
+     ('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: [EMAIL PROTECTED]
+    To: [EMAIL PROTECTED]
+    Message-ID: ...
+    Date: ...
+    Precedence: bulk
+    <BLANKLINE>
+    Frank Person <[EMAIL PROTECTED]> has been successfully subscribed to
+    A Test List.
+    <BLANKLINE>
+    >>> sorted(admin_qdata.items())
+    [('_parsemsg', False), ('envsender', '[EMAIL PROTECTED]'),
+     ('listname', '[EMAIL PROTECTED]'),
+     ('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('[EMAIL PROTECTED]')
+    >>> member
+    <Member: Frank Person <[EMAIL PROTECTED]>
+             on [EMAIL PROTECTED] 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 @@
     >>> flush()
     >>> address_1 = list(user_1.addresses)[0]
     >>> address_1.subscribe(mlist, MemberRole.member)
-    <Member: <[EMAIL PROTECTED]> on
-             [EMAIL PROTECTED] as MemberRole.member>
+    <Member: [EMAIL PROTECTED] on [EMAIL PROTECTED] as MemberRole.member>
     >>> user_2 = config.db.user_manager.create_user('[EMAIL PROTECTED]')
     >>> flush()
     >>> address_2 = list(user_2.addresses)[0]
     >>> address_2.subscribe(mlist, MemberRole.member)
-    <Member: <[EMAIL PROTECTED]> on
-             [EMAIL PROTECTED] as MemberRole.member>
+    <Member: [EMAIL PROTECTED] on [EMAIL PROTECTED] as MemberRole.member>
     >>> flush()
     >>> id_5 = moderator.hold_unsubscription(mlist, '[EMAIL PROTECTED]')
     >>> flush()

=== modified file 'Mailman/templates/en/adminsubscribeack.txt'
--- a/Mailman/templates/en/adminsubscribeack.txt        2001-05-18 21:28:54 
+0000
+++ b/Mailman/templates/en/adminsubscribeack.txt        2007-09-16 11:15:53 
+0000
@@ -1,3 +1,1 @@
 %(member)s has been successfully subscribed to %(listname)s.
-
-

=== modified file 'Mailman/templates/en/subauth.txt'
--- a/Mailman/templates/en/subauth.txt  2001-10-21 06:41:00 +0000
+++ b/Mailman/templates/en/subauth.txt  2007-09-16 11:15:53 +0000
@@ -2,10 +2,10 @@
 approval:
 
     For:  %(username)s
-    List: %(listname)[EMAIL PROTECTED](hostname)s
+    List: %(listname)s
 
 At your convenience, visit:
 
     %(admindb_url)s
-       
+
 to process the request.

=== modified file 'Mailman/templates/en/subscribeack.txt'
--- a/Mailman/templates/en/subscribeack.txt     2001-07-26 23:14:47 +0000
+++ b/Mailman/templates/en/subscribeack.txt     2007-09-16 11:15:53 +0000
@@ -1,8 +1,8 @@
-Welcome to the %(real_name)[EMAIL PROTECTED](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 @@
 page at:
 
   %(optionsurl)s
-%(umbrella)s
+
 You can also make such adjustments via email by sending a message to:
 
-  %(real_name)[EMAIL PROTECTED](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:
-
-  %(password)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.
+  %(request_address)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 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.



--

https://code.launchpad.net/~mailman-coders/mailman/3.0

You are receiving this branch notification because you are subscribed to it.
To unsubscribe from this branch go to 
https://code.launchpad.net/~mailman-coders/mailman/3.0/+subscription/mailman-checkins.
_______________________________________________
Mailman-checkins mailing list
Mailman-checkins@python.org
Unsubscribe: 
http://mail.python.org/mailman/options/mailman-checkins/archive%40jab.org

Reply via email to