diff options
| author | Barry Warsaw | 2007-08-05 22:49:04 -0500 |
|---|---|---|
| committer | Barry Warsaw | 2007-08-05 22:49:04 -0500 |
| commit | 89bdaec5c735ffb2b27cc29620cb01b451b72550 (patch) | |
| tree | 61c6fa40eb1d3e267475430005b50ecf44b46512 | |
| parent | e0abca9fbdde530f7517396677c87f19c86bc0c6 (diff) | |
| download | mailman-89bdaec5c735ffb2b27cc29620cb01b451b72550.tar.gz mailman-89bdaec5c735ffb2b27cc29620cb01b451b72550.tar.zst mailman-89bdaec5c735ffb2b27cc29620cb01b451b72550.zip | |
| -rw-r--r-- | Mailman/Errors.py | 29 | ||||
| -rw-r--r--[-rwxr-xr-x] | Mailman/app/__init__.py | 0 | ||||
| -rw-r--r-- | Mailman/app/lifecycle.py (renamed from Mailman/app/create.py) | 56 | ||||
| -rw-r--r-- | Mailman/bin/newlist.py | 2 | ||||
| -rw-r--r-- | Mailman/bin/rmlist.py | 59 | ||||
| -rw-r--r-- | Mailman/database/model/address.py | 12 | ||||
| -rw-r--r-- | Mailman/database/model/mailinglist.py | 1 | ||||
| -rw-r--r-- | Mailman/database/model/roster.py | 12 | ||||
| -rw-r--r-- | Mailman/docs/lifecycle.txt (renamed from Mailman/docs/create.txt) | 38 | ||||
| -rw-r--r-- | Mailman/docs/membership.txt | 29 | ||||
| -rw-r--r-- | Mailman/interfaces/address.py | 8 | ||||
| -rw-r--r-- | Mailman/interfaces/mlistrosters.py | 5 |
12 files changed, 177 insertions, 74 deletions
diff --git a/Mailman/Errors.py b/Mailman/Errors.py index f3f7671e8..99065ddbe 100644 --- a/Mailman/Errors.py +++ b/Mailman/Errors.py @@ -162,11 +162,30 @@ class RejectMessage(HandlerError): -# 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) diff --git a/Mailman/app/__init__.py b/Mailman/app/__init__.py index e69de29bb..e69de29bb 100755..100644 --- a/Mailman/app/__init__.py +++ b/Mailman/app/__init__.py diff --git a/Mailman/app/create.py b/Mailman/app/lifecycle.py index d2f85d90d..1c40feaeb 100644 --- a/Mailman/app/create.py +++ b/Mailman/app/lifecycle.py @@ -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.app.styles import style_manager 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 @@ def create_list(fqdn_listname, owners=None): 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') diff --git a/Mailman/bin/newlist.py b/Mailman/bin/newlist.py index 4396e6556..6847cc16f 100644 --- a/Mailman/bin/newlist.py +++ b/Mailman/bin/newlist.py @@ -27,7 +27,7 @@ from Mailman import Message 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 diff --git a/Mailman/bin/rmlist.py b/Mailman/bin/rmlist.py index c51ab7fdf..e0be8f6f5 100644 --- a/Mailman/bin/rmlist.py +++ b/Mailman/bin/rmlist.py @@ -24,66 +24,13 @@ from Mailman import Errors 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) +__i18n_templates__ = True @@ -130,7 +77,7 @@ No such list: ${fqdn_listname}. Removing its residual archives.""") 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() diff --git a/Mailman/database/model/address.py b/Mailman/database/model/address.py index 9c36d2472..391004413 100644 --- a/Mailman/database/model/address.py +++ b/Mailman/database/model/address.py @@ -19,8 +19,10 @@ from elixir import * 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 @@ class Address(Entity): 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 diff --git a/Mailman/database/model/mailinglist.py b/Mailman/database/model/mailinglist.py index 11deb28c6..0cb968574 100644 --- a/Mailman/database/model/mailinglist.py +++ b/Mailman/database/model/mailinglist.py @@ -185,6 +185,7 @@ class MailingList(Entity): 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): diff --git a/Mailman/database/model/roster.py b/Mailman/database/model/roster.py index 0730d2b4b..8440e0ffc 100644 --- a/Mailman/database/model/roster.py +++ b/Mailman/database/model/roster.py @@ -184,3 +184,15 @@ class DigestMemberRoster(AbstractRoster): 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 diff --git a/Mailman/docs/create.txt b/Mailman/docs/lifecycle.txt index 9154a5c63..4a6354381 100644 --- a/Mailman/docs/create.txt +++ b/Mailman/docs/lifecycle.txt @@ -1,12 +1,12 @@ -Application level list creation -------------------------------- +Application level list lifecycle +-------------------------------- -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. +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 mailing lists which performs a -few additional tasks such as: +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 @@ few additional tasks such as: * 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 @@ the system, they won't be created again. >>> 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('test_2@example.com') + False + +We should now be able to completely recreate the mailing list. + + >>> mlist_2a = create_list('test_2@example.com', owners) + >>> flush() + >>> sorted(addr.address for addr in mlist_2a.owners.addresses) + ['aperson@example.com', 'bperson@example.com', + 'cperson@example.com', 'dperson@example.com'] diff --git a/Mailman/docs/membership.txt b/Mailman/docs/membership.txt index ee322780c..515ac7623 100644 --- a/Mailman/docs/membership.txt +++ b/Mailman/docs/membership.txt @@ -209,3 +209,32 @@ is returned. None >>> print mlist.members.get_member('zperson@example.com') 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)] + [('aperson@example.com', 'MemberRole.member'), + ('aperson@example.com', 'MemberRole.owner'), + ('bperson@example.com', 'MemberRole.member'), + ('bperson@example.com', 'MemberRole.moderator'), + ('cperson@example.com', '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: aperson@example.com is already a MemberRole.owner + of mailing list _xtest@example.com diff --git a/Mailman/interfaces/address.py b/Mailman/interfaces/address.py index 6b00d7915..1e654a2fc 100644 --- a/Mailman/interfaces/address.py +++ b/Mailman/interfaces/address.py @@ -56,10 +56,14 @@ class IAddress(Interface): 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( diff --git a/Mailman/interfaces/mlistrosters.py b/Mailman/interfaces/mlistrosters.py index 9cd20e3ef..86cd4ec91 100644 --- a/Mailman/interfaces/mlistrosters.py +++ b/Mailman/interfaces/mlistrosters.py @@ -61,3 +61,8 @@ class IMailingListRosters(Interface): 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. + """) |
