------------------------------------------------------------
revno: 6543
committer: Barry Warsaw <[EMAIL PROTECTED]>
branch nick: 3.0
timestamp: Sun 2007-08-05 22:49:04 -0500
message:
  Fixed a problem where members of a deleted mailing list were hanging around.
  This would cause duplicate members (e.g. owners) if you created, deleted and
  then recreated the mailing list.
  
  Mailman.app.create -> Mailman.app.lifecycle; Mailman/doc/create.txt ->
  Mailman/doc/lifecycle.txt; also added a remove_list() function.
  
  Added SubscriptionError base class, made HostileSubscriptionError inherit from
  that, and added a new AlreadySubscribedError.
  
  Rewrote bin/rmlist to use the new lifecycle.remove_list() function.
  
  IAddress.subscribe() must now throw an AlreadySubscribedError if the address
  is already subscribed to the mailing list with the given role.
  
  Added a Subscribers roster, attached to the IMailingList which gives access to
  all subscribers of a mailing list, regardless of their role.  Added a new test
  for this roster.
renamed:
  Mailman/app/create.py => Mailman/app/lifecycle.py
  Mailman/docs/create.txt => Mailman/docs/lifecycle.txt
modified:
  Mailman/Errors.py
  Mailman/app/__init__.py*
  Mailman/bin/newlist.py
  Mailman/bin/rmlist.py
  Mailman/database/model/address.py
  Mailman/database/model/mailinglist.py
  Mailman/database/model/roster.py
  Mailman/docs/membership.txt
  Mailman/interfaces/address.py
  Mailman/interfaces/mlistrosters.py
  Mailman/app/lifecycle.py
  Mailman/docs/lifecycle.txt

=== renamed file 'Mailman/app/create.py' => 'Mailman/app/lifecycle.py'
--- a/Mailman/app/create.py     2007-08-05 12:42:16 +0000
+++ b/Mailman/app/lifecycle.py  2007-08-06 03:49:04 +0000
@@ -17,6 +17,10 @@
 
 """Application level list creation."""
 
+import os
+import shutil
+import logging
+
 from Mailman import Errors
 from Mailman import Utils
 from Mailman.Utils import ValidateEmail
@@ -25,6 +29,14 @@
 from Mailman.configuration import config
 from Mailman.constants import MemberRole
 
+__all__ = [
+    'create_list',
+    'remove_list',
+    ]
+
+
+log = logging.getLogger('mailman.error')
+
 
 
 def create_list(fqdn_listname, owners=None):
@@ -58,3 +70,47 @@
             addr = list(user.addresses)[0]
         addr.subscribe(mlist, MemberRole.owner)
     return mlist
+
+
+
+def remove_list(fqdn_listname, mailing_list=None, archives=True):
+    """Remove the list and all associated artifacts and subscriptions."""
+    removeables = []
+    # mailing_list will be None when only residual archives are being removed.
+    if mailing_list:
+        # Remove all subscriptions, regardless of role.
+        for member in mailing_list.subscribers.members:
+            member.unsubscribe()
+        # Delete the mailing list from the database.
+        config.db.list_manager.delete(mailing_list)
+        # Do the MTA-specific list deletion tasks
+        if config.MTA:
+            modname = 'Mailman.MTA.' + config.MTA
+            __import__(modname)
+            sys.modules[modname].remove(mailing_list)
+        # Remove the list directory.
+        removeables.append(os.path.join(config.LIST_DATA_DIR, fqdn_listname))
+    # Remove any stale locks associated with the list.
+    for filename in os.listdir(config.LOCK_DIR):
+        fn_listname = filename.split('.')[0]
+        if fn_listname == fqdn_listname:
+            removeables.append(os.path.join(config.LOCK_DIR, filename))
+    if archives:
+        private_dir = config.PRIVATE_ARCHIVE_FILE_DIR
+        public_dir  = config.PUBLIC_ARCHIVE_FILE_DIR
+        removeables.extend([
+            os.path.join(private_dir, fqdn_listname),
+            os.path.join(private_dir, fqdn_listname + '.mbox'),
+            os.path.join(public_dir, fqdn_listname),
+            os.path.join(public_dir, fqdn_listname + '.mbox'),
+            ])
+    # Now that we know what files and directories to delete, delete them.
+    for target in removeables:
+        if os.path.islink(target):
+            os.unlink(target)
+        elif os.path.isdir(target):
+            shutil.rmtree(target)
+        elif os.path.isfile(target):
+            os.unlink(target)
+        else:
+            log.error('Could not delete list artifact: $target')

=== renamed file 'Mailman/docs/create.txt' => 'Mailman/docs/lifecycle.txt'
--- a/Mailman/docs/create.txt   2007-08-05 12:42:16 +0000
+++ b/Mailman/docs/lifecycle.txt        2007-08-06 03:49:04 +0000
@@ -1,12 +1,12 @@
-Application level list creation
--------------------------------
-
-The low-level way to create a new mailing list is to use the IListManager
-interface.  This interface simply adds the appropriate database entries to
-record the list's creation.
-
-There is a higher level interface for creating mailing lists which performs a
-few additional tasks such as:
+Application level list lifecycle
+--------------------------------
+
+The low-level way to create and delete a mailing list is to use the
+IListManager interface.  This interface simply adds or removes the appropriate
+database entries to record the list's creation.
+
+There is a higher level interface for creating and deleting mailing lists
+which performs additional tasks such as:
 
  * validating the list's posting address (which also serves as the list's
    fully qualified name);
@@ -16,7 +16,7 @@
  * notifying watchers of list creation;
  * creating ancillary artifacts (such as the list's on-disk directory)
 
-    >>> from Mailman.app.create import create_list
+    >>> from Mailman.app.lifecycle import create_list
 
 
 Posting address validation
@@ -121,3 +121,25 @@
     >>> flush()
     >>> sorted(user.real_name for user in mlist_3.owners.users)
     ['Anne Person', 'Bart Person', 'Caty Person', 'Dirk Person']
+
+
+Removing a list
+---------------
+
+Removing a mailing list deletes the list, all its subscribers, and any related
+artifacts.
+
+    >>> from Mailman import Utils
+    >>> from Mailman.app.lifecycle import remove_list
+    >>> remove_list(mlist_2.fqdn_listname, mlist_2, True)
+    >>> flush()
+    >>> Utils.list_exists('[EMAIL PROTECTED]')
+    False
+
+We should now be able to completely recreate the mailing list.
+
+    >>> mlist_2a = create_list('[EMAIL PROTECTED]', owners)
+    >>> flush()
+    >>> sorted(addr.address for addr in mlist_2a.owners.addresses)
+    ['[EMAIL PROTECTED]', '[EMAIL PROTECTED]',
+     '[EMAIL PROTECTED]', '[EMAIL PROTECTED]']

=== modified file 'Mailman/Errors.py'
--- a/Mailman/Errors.py 2007-08-05 12:42:16 +0000
+++ b/Mailman/Errors.py 2007-08-06 03:49:04 +0000
@@ -162,11 +162,30 @@
 
 
 
-# Additional exceptions
-class HostileSubscriptionError(MailmanError):
-    """A cross-subscription attempt was made."""
-    # This exception gets raised when an invitee attempts to use the
-    # invitation to cross-subscribe to some other mailing list.
+# Subscription exceptions
+class SubscriptionError(MailmanError):
+    """Subscription errors base class."""
+
+
+class HostileSubscriptionError(SubscriptionError):
+    """A cross-subscription attempt was made.
+    
+    This exception gets raised when an invitee attempts to use the
+    invitation to cross-subscribe to some other mailing list.
+    """
+
+
+class AlreadySubscribedError(SubscriptionError):
+    """The member is already subscribed to the mailing list with this role."""
+
+    def __init__(self, fqdn_listname, address, role):
+        self._fqdn_listname = fqdn_listname
+        self._address = address
+        self._role = role
+
+    def __str__(self):
+        return '%s is already a %s of mailing list %s' % (
+            self._address, self._role, self._fqdn_listname)
 
 
 

=== modified file 'Mailman/app/__init__.py' (properties changed)
=== modified file 'Mailman/bin/newlist.py'
--- a/Mailman/bin/newlist.py    2007-08-05 12:42:16 +0000
+++ b/Mailman/bin/newlist.py    2007-08-06 03:49:04 +0000
@@ -27,7 +27,7 @@
 from Mailman import Utils
 from Mailman import Version
 from Mailman import i18n
-from Mailman.app.create import create_list
+from Mailman.app.lifecycle import create_list
 from Mailman.configuration import config
 from Mailman.initialize import initialize
 

=== modified file 'Mailman/bin/rmlist.py'
--- a/Mailman/bin/rmlist.py     2007-08-05 04:32:09 +0000
+++ b/Mailman/bin/rmlist.py     2007-08-06 03:49:04 +0000
@@ -24,69 +24,16 @@
 from Mailman import Utils
 from Mailman import Version
 from Mailman.MailList import MailList
+from Mailman.app.lifecycle import remove_list
 from Mailman.configuration import config
 from Mailman.i18n import _
 from Mailman.initialize import initialize
 
+
 __i18n_templates__ = True
 
 
 
-def remove_it(listname, filename, msg, quiet=False):
-    if os.path.islink(filename):
-        if not quiet:
-            print _('Removing $msg')
-        os.unlink(filename)
-    elif os.path.isdir(filename):
-        if not quiet:
-            print _('Removing $msg')
-        shutil.rmtree(filename)
-    elif os.path.isfile(filename):
-        os.unlink(filename)
-    else:
-        if not quiet:
-            print _('$listname $msg not found as $filename')
-
-
-
-def delete_list(listname, mlist=None, archives=True, quiet=False):
-    removeables = []
-    if mlist:
-        # Remove the list from the database
-        config.db.list_manager.delete(mlist)
-        # Do the MTA-specific list deletion tasks
-        if config.MTA:
-            modname = 'Mailman.MTA.' + config.MTA
-            __import__(modname)
-            sys.modules[modname].remove(mlist)
-        # Remove the list directory
-        removeables.append((os.path.join('lists', listname), _('list info')))
-
-    # Remove any stale locks associated with the list
-    for filename in os.listdir(config.LOCK_DIR):
-        fn_listname = filename.split('.')[0]
-        if fn_listname == listname:
-            removeables.append((os.path.join(config.LOCK_DIR, filename),
-                                _('stale lock file')))
-
-    if archives:
-        removeables.extend([
-            (os.path.join('archives', 'private', listname),
-             _('private archives')),
-            (os.path.join('archives', 'private', listname + '.mbox'),
-             _('private archives')),
-            (os.path.join('archives', 'public', listname),
-             _('public archives')),
-            (os.path.join('archives', 'public', listname + '.mbox'),
-             _('public archives')),
-            ])
-
-    for dirtmpl, msg in removeables:
-        path = os.path.join(config.VAR_DIR, dirtmpl)
-        remove_it(listname, path, msg, quiet)
-
-
-
 def parseargs():
     parser = optparse.OptionParser(version=Version.MAILMAN_VERSION,
                                    usage=_("""\
@@ -130,7 +77,7 @@
     if not opts.archives:
         print _('Not removing archives.  Reinvoke with -a to remove them.')
 
-    delete_list(fqdn_listname, mlist, opts.archives)
+    remove_list(fqdn_listname, mlist, opts.archives)
     config.db.flush()
 
 

=== modified file 'Mailman/database/model/address.py'
--- a/Mailman/database/model/address.py 2007-08-01 20:11:08 +0000
+++ b/Mailman/database/model/address.py 2007-08-06 03:49:04 +0000
@@ -19,8 +19,10 @@
 from email.utils import formataddr
 from zope.interface import implements
 
+from Mailman import Errors
 from Mailman.interfaces import IAddress
 
+
 MEMBER_KIND     = 'Mailman.database.model.member.Member'
 PREFERENCE_KIND = 'Mailman.database.model.preferences.Preferences'
 USER_KIND       = 'Mailman.database.model.user.User'
@@ -62,12 +64,18 @@
             return '<Address: %s [%s] key: %s at %#x>' % (
                 address_str, verified, self.address, id(self))
 
-    def subscribe(self, mlist, role):
+    def subscribe(self, mailing_list, role):
         from Mailman.database.model import Member
         from Mailman.database.model import Preferences
         # This member has no preferences by default.
+        member = Member.get_by(role=role,
+                               mailing_list=mailing_list.fqdn_listname,
+                               address=self)
+        if member:
+            raise Errors.AlreadySubscribedError(
+                mailing_list.fqdn_listname, self.address, role)
         member = Member(role=role,
-                        mailing_list=mlist.fqdn_listname,
+                        mailing_list=mailing_list.fqdn_listname,
                         address=self)
         member.preferences = Preferences()
         return member

=== modified file 'Mailman/database/model/mailinglist.py'
--- a/Mailman/database/model/mailinglist.py     2007-08-05 04:32:09 +0000
+++ b/Mailman/database/model/mailinglist.py     2007-08-06 03:49:04 +0000
@@ -185,6 +185,7 @@
         self.members = roster.MemberRoster(self)
         self.regular_members = roster.RegularMemberRoster(self)
         self.digest_members = roster.DigestMemberRoster(self)
+        self.subscribers = roster.Subscribers(self)
 
     @property
     def fqdn_listname(self):

=== modified file 'Mailman/database/model/roster.py'
--- a/Mailman/database/model/roster.py  2007-06-18 14:50:23 +0000
+++ b/Mailman/database/model/roster.py  2007-08-06 03:49:04 +0000
@@ -184,3 +184,15 @@
                                        role=MemberRole.member):
             if member.delivery_mode in _digest_modes:
                 yield member
+
+
+
+class Subscribers(AbstractRoster):
+    """Return all subscribed members regardless of their role."""
+
+    name = 'subscribers'
+
+    @property
+    def members(self):
+        for member in Member.select_by(mailing_list=self._mlist.fqdn_listname):
+            yield member

=== modified file 'Mailman/docs/membership.txt'
--- a/Mailman/docs/membership.txt       2007-08-02 14:47:56 +0000
+++ b/Mailman/docs/membership.txt       2007-08-06 03:49:04 +0000
@@ -209,3 +209,32 @@
     None
     >>> print mlist.members.get_member('[EMAIL PROTECTED]')
     None
+
+
+All subscribers
+---------------
+
+There is also a roster containing all the subscribers of a mailing list,
+regardless of their role.
+
+    >>> def sortkey(member):
+    ...     return (member.address.address, int(member.role))
+    >>> [(member.address.address, str(member.role))
+    ...  for member in sorted(mlist.subscribers.members, key=sortkey)]
+    [('[EMAIL PROTECTED]', 'MemberRole.member'),
+     ('[EMAIL PROTECTED]', 'MemberRole.owner'),
+     ('[EMAIL PROTECTED]', 'MemberRole.member'),
+     ('[EMAIL PROTECTED]', 'MemberRole.moderator'),
+     ('[EMAIL PROTECTED]', 'MemberRole.member')]
+
+
+Double subscriptions
+--------------------
+
+It is an error to subscribe someone to a list with the same role twice.
+
+    >>> address_1.subscribe(mlist, MemberRole.owner)
+    Traceback (most recent call last):
+    ...
+    AlreadySubscribedError: [EMAIL PROTECTED] is already a MemberRole.owner
+    of mailing list [EMAIL PROTECTED]

=== modified file 'Mailman/interfaces/address.py'
--- a/Mailman/interfaces/address.py     2007-08-01 20:11:08 +0000
+++ b/Mailman/interfaces/address.py     2007-08-06 03:49:04 +0000
@@ -56,10 +56,14 @@
         None if the email address has not yet been validated.  The specific
         method of validation is not defined here.""")
 
-    def subscribe(mlist, role):
+    def subscribe(mailing_list, role):
         """Subscribe the address to the given mailing list with the given role.
 
-        role is a Mailman.constants.MemberRole enum.
+        :param mailing_list: The IMailingList being subscribed to.
+        :param role: A MemberRole enum value.
+        :return: The IMember representing this subscription.
+        :raises AlreadySubscribedError: If the address is already subscribed
+            to the mailing list with the given role.
         """
 
     preferences = Attribute(

=== modified file 'Mailman/interfaces/mlistrosters.py'
--- a/Mailman/interfaces/mlistrosters.py        2007-06-09 19:20:32 +0000
+++ b/Mailman/interfaces/mlistrosters.py        2007-08-06 03:49:04 +0000
@@ -61,3 +61,8 @@
         postings to this mailing list, regardless of whether they have their
         deliver disabled or not, or of the type of digest they are to
         receive.""")
+
+    subscribers = Attribute(
+        """An iterator over all IMembers subscribed to this list, with any
+        role.
+        """)



--
(no title)
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
[email protected]
Unsubscribe: 
http://mail.python.org/mailman/options/mailman-checkins/archive%40jab.org

Reply via email to