diff options
Diffstat (limited to 'Mailman')
| -rw-r--r-- | Mailman/Cgi/create.py | 2 | ||||
| -rw-r--r-- | Mailman/Errors.py | 1 | ||||
| -rw-r--r-- | Mailman/app/create.py | 16 | ||||
| -rw-r--r-- | Mailman/app/styles.py | 6 | ||||
| -rw-r--r-- | Mailman/bin/newlist.py | 20 | ||||
| -rw-r--r-- | Mailman/docs/create.txt | 123 | ||||
| -rw-r--r-- | Mailman/docs/styles.txt | 30 | ||||
| -rw-r--r-- | Mailman/interfaces/styles.py | 7 | ||||
| -rw-r--r-- | Mailman/tests/test_documentation.py | 6 |
9 files changed, 188 insertions, 23 deletions
diff --git a/Mailman/Cgi/create.py b/Mailman/Cgi/create.py index a25f240ed..f98a13aba 100644 --- a/Mailman/Cgi/create.py +++ b/Mailman/Cgi/create.py @@ -173,7 +173,7 @@ def process_request(doc, cgidata): request_creation(doc, cgidata, _('List already exists: $safelistname')) return - except Errors.BadListNameError, s: + except Errors.InvalidEmailAddress, s: request_creation(doc, cgidata, _('Illegal list name: $s')) return except Errors.MMListError: diff --git a/Mailman/Errors.py b/Mailman/Errors.py index 594293caa..f3f7671e8 100644 --- a/Mailman/Errors.py +++ b/Mailman/Errors.py @@ -39,7 +39,6 @@ class MMUnknownListError(MMListError): class MMCorruptListDatabaseError(MMListError): pass class MMListNotReadyError(MMListError): pass class MMListAlreadyExistsError(MMListError): pass -class BadListNameError(MMListError): pass # Membership exceptions class MMMemberError(MailmanException): pass diff --git a/Mailman/app/create.py b/Mailman/app/create.py index 4e94a3e44..d2f85d90d 100644 --- a/Mailman/app/create.py +++ b/Mailman/app/create.py @@ -23,11 +23,14 @@ from Mailman.Utils import ValidateEmail from Mailman.app.plugins import get_plugin from Mailman.app.styles import style_manager from Mailman.configuration import config +from Mailman.constants import MemberRole -def create_list(fqdn_listname): +def create_list(fqdn_listname, owners=None): """Create the named list and apply styles.""" + if owners is None: + owners = [] ValidateEmail(fqdn_listname) listname, domain = Utils.split_listname(fqdn_listname) if domain not in config.domains: @@ -43,4 +46,15 @@ def create_list(fqdn_listname): # XXX FIXME ## mta_plugin = get_plugin('mailman.mta') ## mta_plugin().create(mlist) + # Create any owners that don't yet exist, and subscribe all addresses as + # owners of the mailing list. + usermgr = config.db.user_manager + for owner_address in owners: + addr = usermgr.get_address(owner_address) + if addr is None: + # XXX Make this use an IRegistrar instead, but that requires + # sussing out the IDomain stuff. For now, fake it. + user = usermgr.create_user(owner_address) + addr = list(user.addresses)[0] + addr.subscribe(mlist, MemberRole.owner) return mlist diff --git a/Mailman/app/styles.py b/Mailman/app/styles.py index e0bf58cde..606b388ed 100644 --- a/Mailman/app/styles.py +++ b/Mailman/app/styles.py @@ -21,6 +21,7 @@ import datetime from operator import attrgetter from zope.interface import implements +from zope.interface.verify import verifyObject from Mailman import Utils from Mailman.Errors import DuplicateStyleError @@ -261,10 +262,15 @@ class StyleManager: yield style def register(self, style): + verifyObject(IStyle, style) if style.name in self._styles: raise DuplicateStyleError(style.name) self._styles[style.name] = style + def unregister(self, style): + # Let KeyErrors percolate up. + del self._styles[style.name] + style_manager = StyleManager() diff --git a/Mailman/bin/newlist.py b/Mailman/bin/newlist.py index 9692b4fd0..4396e6556 100644 --- a/Mailman/bin/newlist.py +++ b/Mailman/bin/newlist.py @@ -29,7 +29,6 @@ from Mailman import Version from Mailman import i18n from Mailman.app.create import create_list from Mailman.configuration import config -from Mailman.constants import MemberRole from Mailman.initialize import initialize _ = i18n._ @@ -104,29 +103,14 @@ def main(): # Create the mailing list, applying styles as appropriate. try: - mlist = create_list(fqdn_listname) + mlist = create_list(fqdn_listname, opts.owners) mlist.preferred_language = opts.language - except Errors.BadListNameError: + except Errors.InvalidEmailAddress: parser.error(_('Illegal list name: $fqdn_listname')) except Errors.MMListAlreadyExistsError: parser.error(_('List already exists: $fqdn_listname')) except Errors.BadDomainSpecificationError, domain: parser.error(_('Undefined domain: $domain')) - except Errors.MMListAlreadyExistsError: - parser.error(_('List already exists: $fqdn_listname')) - - # Create any owners that don't yet exist, and subscribe all addresses as - # owners of the mailing list. - usermgr = config.db.user_manager - for owner_address in opts.owners: - addr = usermgr.get_address(owner_address) - if addr is None: - # XXX Make this use an IRegistrar instead, but that requires - # sussing out the IDomain stuff. For now, fake it. - user = usermgr.create_user(owner_address) - addr = list(user.addresses)[0] - addr.verified_on = datetime.datetime.now() - addr.subscribe(mlist, MemberRole.owner) config.db.flush() diff --git a/Mailman/docs/create.txt b/Mailman/docs/create.txt new file mode 100644 index 000000000..9154a5c63 --- /dev/null +++ b/Mailman/docs/create.txt @@ -0,0 +1,123 @@ +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: + + * validating the list's posting address (which also serves as the list's + fully qualified name); + * ensuring that the list's domain is registered; + * applying all matching styles to the new list; + * creating and assigning list owners; + * notifying watchers of list creation; + * creating ancillary artifacts (such as the list's on-disk directory) + + >>> from Mailman.app.create import create_list + + +Posting address validation +-------------------------- + +If you try to use the higher-level interface to create a mailing list with a +bogus posting address, you get an exception. + + >>> create_list('not a valid address') + Traceback (most recent call last): + ... + InvalidEmailAddress: 'not a valid address' + +If the posting address is valid, but the domain has not been registered with +Mailman yet, you get an exception. + + >>> create_list('test@example.org') + Traceback (most recent call last): + ... + BadDomainSpecificationError: example.org + + +Creating a list applies its styles +---------------------------------- + +Start by registering a test style. + + >>> from zope.interface import implements + >>> from Mailman.interfaces import IStyle + >>> class TestStyle(object): + ... implements(IStyle) + ... name = 'test' + ... priority = 10 + ... def apply(self, mailing_list): + ... # Just does something very simple. + ... mailing_list.msg_footer = u'test footer' + ... def match(self, mailing_list, styles): + ... # Applies to any test list + ... if 'test' in mailing_list.fqdn_listname: + ... styles.append(self) + >>> from Mailman.app.styles import style_manager + >>> style_manager.register(TestStyle()) + +Using the higher level interface for creating a list, applies all matching +list styles. + + >>> mlist_1 = create_list('test_1@example.com') + >>> from Mailman.database import flush + >>> flush() + >>> mlist_1.fqdn_listname + 'test_1@example.com' + >>> mlist_1.msg_footer + u'test footer' + + +Creating a list with owners +--------------------------- + +You can also specify a list of owner email addresses. If these addresses are +not yet known, they will be registered, and new users will be linked to them. +However the addresses are not verified. + + >>> owners = ['aperson@example.com', 'bperson@example.com', + ... 'cperson@example.com', 'dperson@example.com'] + >>> mlist_2 = create_list('test_2@example.com', owners) + >>> flush() + >>> mlist_2.fqdn_listname + 'test_2@example.com' + >>> mlist_2.msg_footer + u'test footer' + >>> sorted(addr.address for addr in mlist_2.owners.addresses) + ['aperson@example.com', 'bperson@example.com', + 'cperson@example.com', 'dperson@example.com'] + +None of the owner addresses are verified. + + >>> any(addr.verified_on is not None for addr in mlist_2.owners.addresses) + False + +However, all addresses are linked to users. + + >>> # The owners have no names yet + >>> len(list(mlist_2.owners.users)) + 4 + +If you create a mailing list with owner addresses that are already known to +the system, they won't be created again. + + >>> from Mailman.configuration import config + >>> usermgr = config.db.user_manager + >>> user_a = usermgr.get_user('aperson@example.com') + >>> user_b = usermgr.get_user('bperson@example.com') + >>> user_c = usermgr.get_user('cperson@example.com') + >>> user_d = usermgr.get_user('dperson@example.com') + >>> user_a.real_name = 'Anne Person' + >>> user_b.real_name = 'Bart Person' + >>> user_c.real_name = 'Caty Person' + >>> user_d.real_name = 'Dirk Person' + >>> flush() + + >>> mlist_3 = create_list('test_3@example.com', owners) + >>> flush() + >>> sorted(user.real_name for user in mlist_3.owners.users) + ['Anne Person', 'Bart Person', 'Caty Person', 'Dirk Person'] diff --git a/Mailman/docs/styles.txt b/Mailman/docs/styles.txt index f5d9a807b..12e2744b6 100644 --- a/Mailman/docs/styles.txt +++ b/Mailman/docs/styles.txt @@ -64,6 +64,7 @@ New styles must implement the IStyle interface. >>> from zope.interface import implements >>> from Mailman.interfaces import IStyle >>> class TestStyle(object): + ... implements(IStyle) ... name = 'test' ... priority = 10 ... def apply(self, mailing_list): @@ -130,13 +131,38 @@ will take effect in the new priority order. u'test footer' +Unregistering styles +-------------------- + +You can unregister a style, making it unavailable in the future. + + >>> style_manager.unregister(style_2) + >>> sorted(style.name for style in style_manager.lookup(mlist)) + ['test'] + + Corner cases ------------ If you register a style with the same name as an already registered style, you get an exception. - >>> style_manager.register(AnotherTestStyle()) + >>> style_manager.register(TestStyle()) + Traceback (most recent call last): + ... + DuplicateStyleError: test + +If you try to register an object that isn't a style, you get an exception. + + >>> style_manager.register(object()) + Traceback (most recent call last): + ... + DoesNotImplement: An object does not implement interface + <InterfaceClass Mailman.interfaces.styles.IStyle> + +If you try to unregister a style that isn't registered, you get an exception. + + >>> style_manager.unregister(style_2) Traceback (most recent call last): ... - DuplicateStyleError: another + KeyError: 'another' diff --git a/Mailman/interfaces/styles.py b/Mailman/interfaces/styles.py index 7b4207bae..aa139fde8 100644 --- a/Mailman/interfaces/styles.py +++ b/Mailman/interfaces/styles.py @@ -84,3 +84,10 @@ class IStyleManager(Interface): :raises DuplicateStyleError: if a style with the same name was already registered. """ + + def unregister(style): + """Unregister the style. + + :param style: an IStyle. + :raises KeyError: If the style's name is not currently registered. + """ diff --git a/Mailman/tests/test_documentation.py b/Mailman/tests/test_documentation.py index bbceedcc0..0dab2c2dd 100644 --- a/Mailman/tests/test_documentation.py +++ b/Mailman/tests/test_documentation.py @@ -22,6 +22,8 @@ import doctest import unittest import Mailman + +from Mailman.app.styles import style_manager from Mailman.configuration import config from Mailman.database import flush @@ -56,6 +58,10 @@ def cleaning_teardown(testobj): for dirpath, dirnames, filenames in os.walk(config.QUEUE_DIR): for filename in filenames: os.remove(os.path.join(dirpath, filename)) + # Remove all but the default style. + for style in style_manager.styles: + if style.name <> 'default': + style_manager.unregister(style) |
