summaryrefslogtreecommitdiff
path: root/Mailman
diff options
context:
space:
mode:
Diffstat (limited to 'Mailman')
-rw-r--r--Mailman/Cgi/create.py2
-rw-r--r--Mailman/Errors.py1
-rw-r--r--Mailman/app/create.py16
-rw-r--r--Mailman/app/styles.py6
-rw-r--r--Mailman/bin/newlist.py20
-rw-r--r--Mailman/docs/create.txt123
-rw-r--r--Mailman/docs/styles.txt30
-rw-r--r--Mailman/interfaces/styles.py7
-rw-r--r--Mailman/tests/test_documentation.py6
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)