------------------------------------------------------------
revno: 6748
committer: Barry Warsaw <[email protected]>
branch nick: 3.0
timestamp: Thu 2009-07-16 22:36:06 -0400
message:
  Wow.  Put domains into the database.
  
  Add an IDomainManager and a global domain manager which can be gotten by
  adapting the global config object.
  
  Add an IDomainCollection interface for exposing the domain manager onto the
  API.
renamed:
  src/mailman/domain.py => src/mailman/database/domain.py
modified:
  src/mailman/Archiver/Archiver.py
  src/mailman/app/lifecycle.py
  src/mailman/app/moderator.py
  src/mailman/archiving/mhonarc.py
  src/mailman/archiving/pipermail.py
  src/mailman/archiving/prototype.py
  src/mailman/commands/docs/join.txt
  src/mailman/commands/join.py
  src/mailman/config/config.py
  src/mailman/config/configure.zcml
  src/mailman/config/schema.cfg
  src/mailman/constants.py
  src/mailman/database/mailinglist.py
  src/mailman/database/mailman.sql
  src/mailman/database/member.py
  src/mailman/docs/addresses.txt
  src/mailman/docs/domains.txt
  src/mailman/docs/registration.txt
  src/mailman/interfaces/domain.py
  src/mailman/pipeline/docs/cook-headers.txt
  src/mailman/rest/adapters.py
  src/mailman/rest/configure.zcml
  src/mailman/rest/docs/domains.txt
  src/mailman/rest/webservice.py
  src/mailman/testing/layers.py
  src/mailman/testing/testing.cfg
  src/mailman/tests/test_documentation.py
  src/mailman/database/domain.py

=== modified file 'src/mailman/Archiver/Archiver.py'
--- src/mailman/Archiver/Archiver.py    2009-02-08 04:10:55 +0000
+++ src/mailman/Archiver/Archiver.py    2009-07-17 02:36:06 +0000
@@ -33,6 +33,7 @@
 
 from mailman import Utils
 from mailman.config import config
+from mailman.interfaces.domain import IDomainManager
 
 log = logging.getLogger('mailman.error')
 
@@ -128,7 +129,8 @@
         if self.archive_private:
             url = self.GetScriptURL('private') + '/index.html'
         else:
-            web_host = config.domains.get(self.host_name, self.host_name)
+            domain = IDomainManager(config).get(self.host_name)
+            web_host = (self.host_name if domain is None else domain.url_host)
             url = Template(config.PUBLIC_ARCHIVE_URL).safe_substitute(
                 listname=self.fqdn_listname,
                 hostname=web_host,

=== modified file 'src/mailman/app/lifecycle.py'
--- src/mailman/app/lifecycle.py        2009-07-01 03:14:26 +0000
+++ src/mailman/app/lifecycle.py        2009-07-17 02:36:06 +0000
@@ -33,6 +33,7 @@
 from mailman.config import config
 from mailman.core import errors
 from mailman.email.validate import validate
+from mailman.interfaces.domain import IDomainManager
 from mailman.interfaces.member import MemberRole
 from mailman.utilities.modules import call_name
 
@@ -48,7 +49,7 @@
     validate(fqdn_listname)
     # pylint: disable-msg=W0612
     listname, domain = fqdn_listname.split('@', 1)
-    if domain not in config.domains:
+    if domain not in IDomainManager(config):
         raise errors.BadDomainSpecificationError(domain)
     mlist = config.db.list_manager.create(fqdn_listname)
     for style in config.style_manager.lookup(mlist):

=== modified file 'src/mailman/app/moderator.py'
--- src/mailman/app/moderator.py        2009-07-04 16:47:24 +0000
+++ src/mailman/app/moderator.py        2009-07-17 02:36:06 +0000
@@ -330,8 +330,9 @@
     realname = mlist.real_name
     if lang is None:
         member = mlist.members.get_member(recip)
-        lang = (member.preferred_language if member
-                else mlist.preferred_language)
+        lang = (mlist.preferred_language
+                if member is None
+                else member.preferred_language)
     text = Utils.maketext(
         'refuse.txt',
         {'listname' : mlist.fqdn_listname,

=== modified file 'src/mailman/archiving/mhonarc.py'
--- src/mailman/archiving/mhonarc.py    2009-01-17 02:04:21 +0000
+++ src/mailman/archiving/mhonarc.py    2009-07-17 02:36:06 +0000
@@ -35,6 +35,7 @@
 
 from mailman.config import config
 from mailman.interfaces.archiver import IArchiver
+from mailman.interfaces.domain import IDomainManager
 from mailman.utilities.string import expand
 
 
@@ -53,7 +54,7 @@
     def list_url(mlist):
         """See `IArchiver`."""
         # XXX What about private MHonArc archives?
-        web_host = config.domains[mlist.host_name].url_host
+        web_host = IDomainManager(config)[mlist.host_name].url_host
         return expand(config.archiver.mhonarc.base_url,
                       dict(listname=mlist.fqdn_listname,
                            hostname=web_host,

=== modified file 'src/mailman/archiving/pipermail.py'
--- src/mailman/archiving/pipermail.py  2009-02-08 04:10:55 +0000
+++ src/mailman/archiving/pipermail.py  2009-07-17 02:36:06 +0000
@@ -35,6 +35,7 @@
 
 from mailman.config import config
 from mailman.interfaces.archiver import IArchiver, IPipermailMailingList
+from mailman.interfaces.domain import IDomainManager
 from mailman.interfaces.mailinglist import IMailingList
 from mailman.utilities.filesystem import makedirs
 from mailman.utilities.string import expand
@@ -97,7 +98,7 @@
         if mlist.archive_private:
             url = mlist.script_url('private') + '/index.html'
         else:
-            web_host = config.domains[mlist.host_name].url_host
+            web_host = IDomainManager(config)[mlist.host_name].url_host
             return expand(config.archiver.pipermail.base_url,
                           dict(listname=mlist.fqdn_listname,
                                hostname=web_host,

=== modified file 'src/mailman/archiving/prototype.py'
--- src/mailman/archiving/prototype.py  2009-01-17 02:04:21 +0000
+++ src/mailman/archiving/prototype.py  2009-07-17 02:36:06 +0000
@@ -33,6 +33,7 @@
 
 from mailman.config import config
 from mailman.interfaces.archiver import IArchiver
+from mailman.interfaces.domain import IDomainManager
 
 
 
@@ -50,7 +51,7 @@
     @staticmethod
     def list_url(mlist):
         """See `IArchiver`."""
-        return config.domains[mlist.host_name].base_url
+        return IDomainManager(config)[mlist.host_name].base_url
 
     @staticmethod
     def permalink(mlist, msg):

=== modified file 'src/mailman/commands/docs/join.txt'
--- src/mailman/commands/docs/join.txt  2009-02-10 03:19:18 +0000
+++ src/mailman/commands/docs/join.txt  2009-07-17 02:36:06 +0000
@@ -119,8 +119,9 @@
 list.
 
     >>> token = str(qmsg['subject']).split()[1].strip()
+    >>> from mailman.interfaces.domain import IDomainManager
     >>> from mailman.interfaces.registrar import IRegistrar
-    >>> registrar = IRegistrar(config.domains['example.com'])
+    >>> registrar = IRegistrar(IDomainManager(config)[u'example.com'])
     >>> registrar.confirm(token)
     True
 

=== modified file 'src/mailman/commands/join.py'
--- src/mailman/commands/join.py        2009-02-10 03:19:18 +0000
+++ src/mailman/commands/join.py        2009-07-17 02:36:06 +0000
@@ -30,6 +30,7 @@
 from mailman.config import config
 from mailman.i18n import _
 from mailman.interfaces.command import ContinueProcessing, IEmailCommand
+from mailman.interfaces.domain import IDomainManager
 from mailman.interfaces.member import DeliveryMode
 from mailman.interfaces.registrar import IRegistrar
 
@@ -66,7 +67,7 @@
             print >> results, _(
                 '$self.name: No valid address found to subscribe')
             return ContinueProcessing.no
-        domain = config.domains[mlist.host_name]
+        domain = IDomainManager(config)[mlist.host_name]
         registrar = IRegistrar(domain)
         registrar.register(address, real_name, mlist)
         person = formataddr((real_name, address))

=== modified file 'src/mailman/config/config.py'
--- src/mailman/config/config.py        2009-06-30 01:30:04 +0000
+++ src/mailman/config/config.py        2009-07-17 02:36:06 +0000
@@ -36,7 +36,6 @@
 
 from mailman import version
 from mailman.core import errors
-from mailman.domain import Domain
 from mailman.languages.manager import LanguageManager
 from mailman.styles.manager import StyleManager
 from mailman.utilities.filesystem import makedirs
@@ -58,7 +57,6 @@
     implements(IConfiguration)
 
     def __init__(self):
-        self.domains = {}       # email host -> IDomain
         self.switchboards = {}
         self.languages = LanguageManager()
         self.style_manager = StyleManager()
@@ -74,7 +72,6 @@
 
     def _clear(self):
         """Clear the cached configuration variables."""
-        self.domains.clear()
         self.switchboards.clear()
         self.languages = LanguageManager()
 
@@ -118,21 +115,6 @@
 
     def _post_process(self):
         """Perform post-processing after loading the configuration files."""
-        # Set up the domains.
-        domains = self._config.getByCategory('domain', [])
-        for section in domains:
-            domain = Domain(section.email_host, section.base_url,
-                            section.description, section.contact_address)
-            if domain.email_host in self.domains:
-                raise errors.BadDomainSpecificationError(
-                    'Duplicate email host: %s' % domain.email_host)
-            # Make sure there's only one mapping for the url_host
-            if domain.url_host in self.domains.values():
-                raise errors.BadDomainSpecificationError(
-                    'Duplicate url host: %s' % domain.url_host)
-            # We'll do the reverse mappings on-demand.  There shouldn't be too
-            # many virtual hosts that it will really matter that much.
-            self.domains[domain.email_host] = domain
         # Set up directories.
         self.BIN_DIR = os.path.abspath(os.path.dirname(sys.argv[0]))
         self.VAR_DIR = var_dir = self._config.mailman.var_dir

=== modified file 'src/mailman/config/configure.zcml'
--- src/mailman/config/configure.zcml   2009-06-30 01:30:04 +0000
+++ src/mailman/config/configure.zcml   2009-07-17 02:36:06 +0000
@@ -24,4 +24,10 @@
     factory="mailman.database.mailinglist.AcceptableAliasSet"
     />
 
+  <adapter
+    for="mailman.config.config.IConfiguration"
+    provides="mailman.interfaces.domain.IDomainManager"
+    factory="mailman.database.domain.DomainManager"
+    />
+
 </configure>

=== modified file 'src/mailman/config/schema.cfg'
--- src/mailman/config/schema.cfg       2009-06-30 01:30:04 +0000
+++ src/mailman/config/schema.cfg       2009-07-17 02:36:06 +0000
@@ -233,22 +233,6 @@
 show_tracebacks: yes
 
 
-[domain.master]
-# Site-wide domain defaults.  To configure an individual
-# domain, add a [domain.example_com] section with the overrides.
-
-# This is the host name for the email interface.
-email_host: example.com
-# This is the base url for the domain's web interface.  It must include the
-# url scheme.
-base_url: http://example.com
-# The contact address for this domain.  This is advertised as the human to
-# contact when users have problems with the lists in this domain.
-contact_address: [email protected]
-# A short description of this domain.
-description: An example domain.
-
-
 [language.master]
 # Template for language definitions.  The section name must be [language.xx]
 # where xx is the 2-character ISO code for the language.

=== modified file 'src/mailman/constants.py'
--- src/mailman/constants.py    2009-07-01 03:14:26 +0000
+++ src/mailman/constants.py    2009-07-17 02:36:06 +0000
@@ -21,7 +21,7 @@
 
 __metaclass__ = type
 __all__ = [
-    'SystemDefaultPreferences',
+    'system_preferences',
     ]
 
 
@@ -44,8 +44,16 @@
 
     acknowledge_posts = False
     hide_address = True
-    preferred_language = config.languages['en']
     receive_list_copy = True
     receive_own_postings = True
     delivery_mode = DeliveryMode.regular
     delivery_status = DeliveryStatus.enabled
+
+    @property
+    def preferred_language(self):
+        """Return the system preferred language."""
+        return config.languages['en']
+
+
+
+system_preferences = SystemDefaultPreferences()

=== renamed file 'src/mailman/domain.py' => 'src/mailman/database/domain.py'
--- src/mailman/domain.py       2009-07-01 03:14:26 +0000
+++ src/mailman/database/domain.py      2009-07-17 02:36:06 +0000
@@ -22,34 +22,48 @@
 __metaclass__ = type
 __all__ = [
     'Domain',
+    'DomainManager',
     ]
 
 from urlparse import urljoin, urlparse
+from storm.locals import Int, Unicode
 from zope.interface import implements
 
-from mailman.interfaces.domain import IDomain
+from mailman.core.errors import BadDomainSpecificationError
+from mailman.database.model import Model
+from mailman.interfaces.domain import IDomain, IDomainManager
 
 
 
-class Domain:
+class Domain(Model):
     """Domains."""
 
     implements(IDomain)
 
-    def __init__(self, email_host, base_url=None, description=None,
+    id = Int(primary=True)
+
+    email_host = Unicode()
+    base_url = Unicode()
+    description = Unicode()
+    contact_address = Unicode()
+
+    def __init__(self, email_host,
+                 description=None,
+                 base_url=None,
                  contact_address=None):
         """Create and register a domain.
 
         :param email_host: The host name for the email interface.
         :type email_host: string
+        :param description: An optional description of the domain.
+        :type description: string
         :param base_url: The optional base url for the domain, including
             scheme.  If not given, it will be constructed from the
             `email_host` using the http protocol.
         :type base_url: string
-        :param description: An optional description of the domain.
-        :type description: string
-        :type contact_address: The email address to contact a human for this
+        :param contact_address: The email address to contact a human for this
             domain.  If not given, postmas...@`email_host` will be used.
+        :type contact_address: string
         """
         self.email_host = email_host
         self.base_url = (base_url
@@ -59,9 +73,12 @@
         self.contact_address = (contact_address
                                 if contact_address is not None
                                 else 'postmaster@' + email_host)
+
+    @property
+    def url_host(self):
         # pylint: disable-msg=E1101
         # no netloc member; yes it does
-        self.url_host = urlparse(self.base_url).netloc
+        return urlparse(self.base_url).netloc
 
     def confirm_address(self, token=''):
         """See `IDomain`."""
@@ -70,3 +87,77 @@
     def confirm_url(self, token=''):
         """See `IDomain`."""
         return urljoin(self.base_url, 'confirm/' + token)
+
+    def __repr__(self):
+        """repr(a_domain)"""
+        if self.description is None:
+            return ('<Domain {0.email_host}, base_url: {0.base_url}, '
+                    'contact_address: {0.contact_address}>').format(self)
+        else:
+            return ('<Domain {0.email_host}, {0.description}, '
+                    'base_url: {0.base_url}, '
+                    'contact_address: {0.contact_address}>').format(self)
+
+
+
+class DomainManager:
+    """Domain manager."""
+
+    implements(IDomainManager)
+
+    def __init__(self, config):
+        """Create a domain manager.
+
+        :param config: The configuration object.
+        :type config: `IConfiguration`
+        """
+        self.config = config
+        self.store = config.db.store
+
+    def add(self, email_host,
+            description=None,
+            base_url=None,
+            contact_address=None):
+        """See `IDomainManager`."""
+        # Be sure the email_host is not already registered.  This is probably
+        # a constraint that should (also) be maintained in the database.
+        if self.get(email_host) is not None:
+            raise BadDomainSpecificationError(
+                'Duplicate email host: %s' % email_host)
+        domain = Domain(email_host, description, base_url, contact_address)
+        self.store.add(domain)
+        return domain
+
+    def remove(self, email_host):
+        domain = self[email_host]
+        self.store.remove(domain)
+        return domain
+
+    def get(self, email_host, default=None):
+        """See `IDomainManager`."""
+        domains = self.store.find(Domain, email_host=email_host)
+        if domains.count() < 1:
+            return default
+        assert domains.count() == 1, (
+            'Too many matching domains: %s' % email_host)
+        return domains.one()
+
+    def __getitem__(self, email_host):
+        """See `IDomainManager`."""
+        missing = object()
+        domain = self.get(email_host, missing)
+        if domain is missing:
+            raise KeyError(email_host)
+        return domain
+
+    def __len__(self):
+        return self.store.find(Domain).count()
+
+    def __iter__(self):
+        """See `IDomainManager`."""
+        for domain in self.store.find(Domain):
+            yield domain
+
+    def __contains__(self, email_host):
+        """See `IDomainManager`."""
+        return self.store.find(Domain, email_host=email_host).count() > 0

=== modified file 'src/mailman/database/mailinglist.py'
--- src/mailman/database/mailinglist.py 2009-06-30 01:30:04 +0000
+++ src/mailman/database/mailinglist.py 2009-07-17 02:36:06 +0000
@@ -40,6 +40,7 @@
 from mailman.database.mime import ContentFilter
 from mailman.database.model import Model
 from mailman.database.types import Enum
+from mailman.interfaces.domain import IDomainManager
 from mailman.interfaces.mailinglist import (
     IAcceptableAlias, IAcceptableAliasSet, IMailingList, Personalization)
 from mailman.interfaces.mime import FilterType
@@ -210,12 +211,12 @@
     @property
     def web_host(self):
         """See `IMailingList`."""
-        return config.domains[self.host_name]
+        return IDomainManager(config)[self.host_name]
 
     def script_url(self, target, context=None):
         """See `IMailingList`."""
         # Find the domain for this mailing list.
-        domain = config.domains[self.host_name]
+        domain = IDomainManager(config)[self.host_name]
         # XXX Handle the case for when context is not None; those would be
         # relative URLs.
         return urljoin(domain.base_url, target + '/' + self.fqdn_listname)

=== modified file 'src/mailman/database/mailman.sql'
--- src/mailman/database/mailman.sql    2009-03-03 15:15:42 +0000
+++ src/mailman/database/mailman.sql    2009-07-17 02:36:06 +0000
@@ -67,11 +67,21 @@
 CREATE INDEX ix_contentfilter_mailing_list_id
     ON contentfilter (mailing_list_id);
 
+CREATE TABLE domain (
+    id INTEGER NOT NULL,
+    email_host TEXT,
+    base_url TEXT,
+    description TEXT,
+    contact_address TEXT,
+    PRIMARY KEY (id)
+    );
+
 CREATE TABLE language (
-        id INTEGER NOT NULL,
-        code TEXT,
-        PRIMARY KEY (id)
-);
+    id INTEGER NOT NULL,
+    code TEXT,
+    PRIMARY KEY (id)
+    );
+
 CREATE TABLE mailinglist (
         id INTEGER NOT NULL,
         -- List identity
@@ -80,7 +90,7 @@
         list_id TEXT,
         include_list_post_header BOOLEAN,
         include_rfc2369_headers BOOLEAN,
-        -- Attributes not directly modifiable via the web u/i        
+        -- Attributes not directly modifiable via the web u/i
         created_at TIMESTAMP,
         admin_member_chunksize INTEGER,
         next_request_id INTEGER,

=== modified file 'src/mailman/database/member.py'
--- src/mailman/database/member.py      2009-01-17 02:04:21 +0000
+++ src/mailman/database/member.py      2009-07-17 02:36:06 +0000
@@ -28,7 +28,7 @@
 from zope.interface import implements
 
 from mailman.config import config
-from mailman.constants import SystemDefaultPreferences
+from mailman.constants import system_preferences
 from mailman.database.model import Model
 from mailman.database.types import Enum
 from mailman.interfaces.member import IMember
@@ -69,7 +69,7 @@
             pref = getattr(self.address.user.preferences, preference)
             if pref is not None:
                 return pref
-        return getattr(SystemDefaultPreferences, preference)
+        return getattr(system_preferences, preference)
 
     @property
     def acknowledge_posts(self):

=== modified file 'src/mailman/docs/addresses.txt'
--- src/mailman/docs/addresses.txt      2009-02-23 02:33:17 +0000
+++ src/mailman/docs/addresses.txt      2009-07-17 02:36:06 +0000
@@ -1,3 +1,4 @@
+===============
 Email addresses
 ===============
 
@@ -10,7 +11,7 @@
 
 
 Creating addresses
-------------------
+==================
 
 Addresses are created directly through the user manager, which starts out with
 no addresses.
@@ -82,7 +83,7 @@
 
 
 Deleting addresses
-------------------
+==================
 
 You can remove an unlinked address from the user manager.
 
@@ -110,7 +111,7 @@
 
 
 Registration and validation
----------------------------
+===========================
 
 Addresses have two dates, the date the address was registered on and the date
 the address was validated on.  Neither date is set by default.
@@ -141,7 +142,7 @@
 
 
 Subscriptions
--------------
+=============
 
 Addresses get subscribed to mailing lists, not users.  When the address is
 subscribed, a role is specified.
@@ -179,7 +180,7 @@
 
 
 Case-preserved addresses
-------------------------
+========================
 
 Technically speaking, email addresses are case sensitive in the local part.
 Mailman preserves the case of addresses and uses the case preserved version

=== modified file 'src/mailman/docs/domains.txt'
--- src/mailman/docs/domains.txt        2008-12-29 00:14:04 +0000
+++ src/mailman/docs/domains.txt        2009-07-17 02:36:06 +0000
@@ -1,46 +1,122 @@
+=======
 Domains
 =======
 
+    # The test framework starts out with an example domain, so let's delete
+    # that first.
+    >>> from mailman.interfaces.domain import IDomainManager
+    >>> manager = IDomainManager(config)
+    >>> manager.remove(u'example.com')
+    <Domain example.com...>
+
 Domains are how Mailman interacts with email host names and web host names.
-Generally, new domains are registered in the mailman.cfg configuration file.
-We simulate that here by pushing new configurations.
-
-    >>> config.push('example.org', """
-    ... [domain.example_dot_org]
-    ... email_host: example.org
-    ... base_url: https://mail.example.org
-    ... description: The example domain
-    ... contact_address: [email protected]
-    ... """)
-
-    >>> domain = config.domains['example.org']
-    >>> print domain.email_host
-    example.org
-    >>> print domain.base_url
-    https://mail.example.org
-    >>> print domain.description
-    The example domain
-    >>> print domain.contact_address
-    [email protected]
-    >>> print domain.url_host
-    mail.example.org
+
+    >>> from operator import attrgetter
+    >>> def show_domains():
+    ...     if len(manager) == 0:
+    ...         print 'no domains'
+    ...         return
+    ...     for domain in sorted(manager, key=attrgetter('email_host')):
+    ...         print domain
+
+    >>> show_domains()
+    no domains
+
+Adding a domain requires some basic information, of which the email host name
+is the only required piece.  The other parts are inferred from that.
+
+    >>> manager.add(u'example.org')
+    <Domain example.org, base_url: http://example.org,
+            contact_address: [email protected]>
+    >>> show_domains()
+    <Domain example.org, base_url: http://example.org,
+            contact_address: [email protected]>
+
+We can remove domains too.
+
+    >>> manager.remove(u'example.org')
+    <Domain example.org, base_url: http://example.org,
+            contact_address: [email protected]>
+    >>> show_domains()
+    no domains
+
+Sometimes the email host name is different than the base url for hitting the
+web interface for the domain.
+
+    >>> manager.add(u'example.com', base_url=u'https://mail.example.com')
+    <Domain example.com, base_url: https://mail.example.com,
+            contact_address: [email protected]>
+    >>> show_domains()
+    <Domain example.com, base_url: https://mail.example.com,
+            contact_address: [email protected]>
+
+Domains can have explicit descriptions and contact addresses.
+
+    >>> manager.add(
+    ...     u'example.net',
+    ...     base_url=u'http://lists.example.net',
+    ...     contact_address=u'[email protected]',
+    ...     description=u'The example domain')
+    <Domain example.net, The example domain,
+            base_url: http://lists.example.net,
+            contact_address: [email protected]>
+
+    >>> show_domains()
+    <Domain example.com, base_url: https://mail.example.com,
+            contact_address: [email protected]>
+    <Domain example.net, The example domain,
+            base_url: http://lists.example.net,
+            contact_address: [email protected]>
+
+In the global domain manager, domains are indexed by their email host name.
+
+    >>> for domain in sorted(manager, key=attrgetter('email_host')):
+    ...     print domain.email_host
+    example.com
+    example.net
+
+    >>> print manager[u'example.net']
+    <Domain example.net, The example domain,
+            base_url: http://lists.example.net,
+            contact_address: [email protected]>
+
+    >>> print manager[u'doesnotexist.com']
+    Traceback (most recent call last):
+    ...
+    KeyError: u'doesnotexist.com'
+
+As with a dictionary, you can also get the domain.  If the domain does not
+exist, None or a default is returned.
+
+    >>> print manager.get(u'example.net')
+    <Domain example.net, The example domain,
+            base_url: http://lists.example.net,
+            contact_address: [email protected]>
+
+    >>> print manager.get(u'doesnotexist.com')
+    None
+
+    >>> print manager.get(u'doesnotexist.com', u'blahdeblah')
+    blahdeblah
+
+Non-existent domains cannot be removed.
+
+    >>> manager.remove(u'doesnotexist.com')
+    Traceback (most recent call last):
+    ...
+    KeyError: u'doesnotexist.com'
 
 
 Confirmation tokens
--------------------
+===================
 
 Confirmation tokens can be added to either the email confirmation address...
 
-    >>> print domain.confirm_address('xyz')
-    [email protected]
+    >>> domain = manager[u'example.net']
+    >>> print domain.confirm_address(u'xyz')
+    [email protected]
 
 ...or the confirmation url.
 
-    >>> print domain.confirm_url('abc')
-    https://mail.example.org/confirm/abc
-
-
-Clean up
---------
-
-    >>> config.pop('example.org')
+    >>> print domain.confirm_url(u'abc')
+    http://lists.example.net/confirm/abc

=== modified file 'src/mailman/docs/registration.txt'
--- src/mailman/docs/registration.txt   2009-03-06 00:25:34 +0000
+++ src/mailman/docs/registration.txt   2009-07-17 02:36:06 +0000
@@ -1,11 +1,11 @@
+====================
 Address registration
 ====================
 
-When a user wants to join a mailing list -- any mailing list -- in the running
-instance, he or she must first register with Mailman.  The only thing they
-must supply is an email address, although there is additional information they
-may supply.  All registered email addresses must be verified before Mailman
-will send them any list traffic.
+Before users can join a mailing list, they must first register with Mailman.
+The only thing they must supply is an email address, although there is
+additional information they may supply.  All registered email addresses must
+be verified before Mailman will send them any list traffic.
 
     >>> from mailman.app.registrar import Registrar
     >>> from mailman.interfaces.registrar import IRegistrar
@@ -15,19 +15,11 @@
 checks, etc.  The IRegistrar is the interface to the object handling all this
 stuff.
 
-Add a domain, which will provide the context for the verification email
-message.
-
-    >>> config.push('mail', """
-    ... [domain.mail_example_dot_com]
-    ... email_host: mail.example.com
-    ... base_url: http://mail.example.com
-    ... contact_address: [email protected]
-    ... """)
-
-    >>> domain = config.domains['mail.example.com']
-
-Get a registrar by adapting a context to the interface.
+    >>> from mailman.interfaces.domain import IDomainManager
+    >>> manager = IDomainManager(config)
+    >>> domain = manager[u'example.com']
+
+Get a registrar by adapting a domain.
 
     >>> from zope.interface.verify import verifyObject
     >>> registrar = IRegistrar(domain)
@@ -45,14 +37,14 @@
 Here is a helper function to extract tokens from confirmation messages.
 
     >>> import re
-    >>> cre = re.compile('http://mail.example.com/confirm/(.*)')
+    >>> cre = re.compile('http://lists.example.com/confirm/(.*)')
     >>> def extract_token(msg):
     ...     mo = cre.search(qmsg.get_payload())
     ...     return mo.group(1)
 
 
 Invalid email addresses
------------------------
+=======================
 
 The only piece of information you need to register is the email address.
 Some amount of sanity checks are performed on the email address, although
@@ -86,7 +78,7 @@
 
 
 Register an email address
--------------------------
+=========================
 
 Registration of an unknown address creates nothing until the confirmation step
 is complete.  No IUser or IAddress is created at registration time, but a
@@ -115,7 +107,7 @@
 
 
 Verification by email
----------------------
+=====================
 
 There is also a verification email sitting in the virgin queue now.  This
 message is sent to the user in order to verify the registered address.
@@ -131,7 +123,7 @@
     Content-Type: text/plain; charset="us-ascii"
     Content-Transfer-Encoding: 7bit
     Subject: confirm ...
-    From: [email protected]
+    From: [email protected]
     To: [email protected]
     Message-ID: <...>
     Date: ...
@@ -139,7 +131,7 @@
     <BLANKLINE>
     Email Address Registration Confirmation
     <BLANKLINE>
-    Hello, this is the GNU Mailman server at mail.example.com.
+    Hello, this is the GNU Mailman server at example.com.
     <BLANKLINE>
     We have received a registration request for the email address
     <BLANKLINE>
@@ -150,13 +142,13 @@
     this message, keeping the Subject header intact.  Or you can visit this
     web page
     <BLANKLINE>
-        http://mail.example.com/confirm/...
+        http://lists.example.com/confirm/...
     <BLANKLINE>
     If you do not wish to register this email address simply disregard this
     message.  If you think you are being maliciously subscribed to the list,
     or have any other questions, you may contact
     <BLANKLINE>
-        [email protected]
+        [email protected]
     <BLANKLINE>
     >>> dump_msgdata(qdata)
     _parsemsg           : False
@@ -175,7 +167,7 @@
 
 The same token will appear in the From header.
 
-    >>> qmsg['from'] == 'confirm-' + token + '@mail.example.com'
+    >>> qmsg['from'] == 'confirm-' + token + '@example.com'
     True
 
 It will also appear in the Subject header.
@@ -207,7 +199,7 @@
 
 
 Non-standard registrations
---------------------------
+==========================
 
 If you try to confirm a registration token twice, of course only the first one
 will work.  The second one is ignored.
@@ -254,7 +246,7 @@
 
 
 Discarding
-----------
+==========
 
 A confirmation token can also be discarded, say if the user changes his or her
 mind about registering.  When discarded, no IAddress or IUser is created.
@@ -272,7 +264,7 @@
 
 
 Registering a new address for an existing user
-----------------------------------------------
+==============================================
 
 When a new address for an existing user is registered, there isn't too much
 different except that the new address will still need to be verified before it
@@ -306,7 +298,7 @@
 
 
 Corner cases
-------------
+============
 
 If you try to confirm a token that doesn't exist in the pending database, the
 confirm method will just return None.
@@ -332,7 +324,7 @@
 
 
 Registration and subscription
------------------------------
+=============================
 
 Fred registers with Mailman at the same time that he subscribes to a mailing
 list.
@@ -353,9 +345,3 @@
     >>> print mlist.members.get_member(u'[email protected]')
     <Member: Fred Person <[email protected]>
              on [email protected] as MemberRole.member>
-
-
-Clean up
---------
-
-    >>> config.pop('mail')

=== modified file 'src/mailman/interfaces/domain.py'
--- src/mailman/interfaces/domain.py    2009-07-11 01:55:26 +0000
+++ src/mailman/interfaces/domain.py    2009-07-17 02:36:06 +0000
@@ -22,7 +22,8 @@
 __metaclass__ = type
 __all__ = [
     'IDomain',
-    'IDomainSet',
+    'IDomainCollection',
+    'IDomainManager',
     ]
 
 
@@ -91,8 +92,78 @@
 
 
 
-class IDomainSet(Interface):
-    """The set of all known domains."""
+class IDomainManager(Interface):
+    """The manager of domains."""
+
+    def add(email_host, description=None, base_url=None, contact_address=None):
+        """Add a new domain.
+
+        :param email_host: The email host name for the domain.
+        :type email_host: string
+        :param description: The description of the domain.
+        :type description: string
+        :param base_url: The base url, including the scheme for the web
+            interface of the domain.  If not given, it defaults to
+            http://`email_host`/
+        :type base_url: string
+        :param contact_address: The email contact address for the human
+            managing the domain.  If not given, defaults to
+            postmas...@`email_host`
+        :type contact_address: string
+        :return: The new domain object
+        :rtype: `IDomain`
+        :raises `BadDomainSpecificationError`: when the `email_host` is
+            already registered.
+        """
+
+    def remove(email_host):
+        """Remove the domain.
+
+        :param email_host: The email host name of the domain to remove.
+        :type email_host: string
+        :raises KeyError: if the named domain does not exist.
+        """
+
+    def __getitem__(email_host):
+        """Return the named domain.
+
+        :param email_host: The email host name of the domain to remove.
+        :type email_host: string
+        :return: The domain object.
+        :rtype: `IDomain`
+        :raises KeyError: if the named domain does not exist.
+        """
+
+    def get(email_host, default=None):
+        """Return the named domain.
+
+        :param email_host: The email host name of the domain to remove.
+        :type email_host: string
+        :param default: What to return if the named domain does not exist.
+        :type default: object
+        :return: The domain object or None if the named domain does not exist.
+        :rtype: `IDomain`
+        """
+
+    def __iter__():
+        """An iterator over all the domains.
+
+        :return: iterator over `IDomain`.
+        """
+
+    def __contains__(email_host):
+        """Is this a known domain?
+
+        :param email_host: An email host name.
+        :type email_host: string
+        :return: True if this domain is known.
+        :rtype: bool
+        """
+
+
+
+class IDomainCollection(Interface):
+    """The set of domains available via the REST API."""
 
     export_as_webservice_collection(IDomain)
 

=== modified file 'src/mailman/pipeline/docs/cook-headers.txt'
--- src/mailman/pipeline/docs/cook-headers.txt  2009-03-03 15:15:42 +0000
+++ src/mailman/pipeline/docs/cook-headers.txt  2009-07-17 02:36:06 +0000
@@ -221,11 +221,11 @@
     ---end---
 
 There are some circumstances when the list administrator wants to explicitly
-set the List-ID header.
+set the List-ID header.  Start by creating a new domain.
 
-    >>> from mailman.domain import Domain
-    >>> domain = Domain(u'mail.example.net')
-    >>> config.domains[domain.email_host] = domain
+    >>> from mailman.interfaces.domain import IDomainManager
+    >>> manager = IDomainManager(config)
+    >>> domain = manager.add(u'mail.example.net')
     >>> mlist.host_name = u'mail.example.net'
 
     >>> process(mlist, msg, {})

=== modified file 'src/mailman/rest/adapters.py'
--- src/mailman/rest/adapters.py        2009-07-11 01:55:26 +0000
+++ src/mailman/rest/adapters.py        2009-07-17 02:36:06 +0000
@@ -21,37 +21,43 @@
 
 __metaclass__ = type
 __all__ = [
-    'DomainSet',
+    'DomainCollection',
     ]
 
 
+from operator import attrgetter
+
 from zope.interface import implements
 from zope.publisher.interfaces import NotFound
 
-from mailman.interfaces.domain import IDomainSet
+from mailman.interfaces.domain import IDomainCollection
 from mailman.interfaces.rest import IResolvePathNames
 
 
 
-class DomainSet:
+class DomainCollection:
     """Sets of known domains."""
 
-    implements(IDomainSet, IResolvePathNames)
+    implements(IDomainCollection, IResolvePathNames)
 
     __name__ = 'domains'
 
-    def __init__(self, config):
-        self._config = config
+    def __init__(self, manager):
+        """Initialize the adapter from an `IDomainManager`.
+
+        :param manager: The domain manager.
+        :type manager: `IDomainManager`.
+        """
+        self._manager = manager
 
     def get_domains(self):
-        """See `IDomainSet`."""
-        # lazr.restful will not allow this to be a generator.
-        domains = self._config.domains
-        return [domains[domain] for domain in sorted(domains)]
+        """See `IDomainCollection`."""
+        # lazr.restful requires the return value to be a concrete list.
+        return sorted(self._manager, key=attrgetter('email_host'))
 
     def get(self, name):
         """See `IResolvePathNames`."""
-        domain = self._config.domains.get(name)
+        domain = self._manager.get(name)
         if domain is None:
             raise NotFound(self, name)
         return domain

=== modified file 'src/mailman/rest/configure.zcml'
--- src/mailman/rest/configure.zcml     2009-07-11 01:55:26 +0000
+++ src/mailman/rest/configure.zcml     2009-07-17 02:36:06 +0000
@@ -13,9 +13,9 @@
   <webservice:register module="mailman.interfaces.system" />
 
   <adapter
-    for="mailman.config.config.IConfiguration"
-    provides="mailman.interfaces.domain.IDomainSet"
-    factory="mailman.rest.adapters.DomainSet"
+    for="mailman.interfaces.domain.IDomainManager"
+    provides="mailman.interfaces.domain.IDomainCollection"
+    factory="mailman.rest.adapters.DomainCollection"
     />
 
   <adapter

=== modified file 'src/mailman/rest/docs/domains.txt'
--- src/mailman/rest/docs/domains.txt   2009-06-30 03:31:51 +0000
+++ src/mailman/rest/docs/domains.txt   2009-07-17 02:36:06 +0000
@@ -2,15 +2,38 @@
 Domains
 =======
 
-The REST API can be queried for the set of known domains.
+    # The test framework starts out with an example domain, so let's delete
+    # that first.
+    >>> from mailman.interfaces.domain import IDomainManager
+    >>> manager = IDomainManager(config)
+    >>> manager.remove(u'example.com')
+    <Domain example.com...>
+    >>> commit()
+
+The REST API can be queried for the set of known domains, of which there are
+initially none.
+
+    >>> dump_json('http://localhost:8001/3.0/domains')
+    resource_type_link: https://localhost:8001/3.0/#domains
+    start: None
+    total_size: 0
+
+Once a domain is added though, it is accessible through the API.
+
+    >>> manager.add(u'example.com', u'An example domain',
+    ...             u'http://lists.example.com')
+    <Domain example.com, An example domain,
+            base_url: http://lists.example.com,
+            contact_address: [email protected]>
+    >>> commit()
 
     >>> dump_json('http://localhost:8001/3.0/domains')
     entry 0:
         base_url: http://lists.example.com
         contact_address: [email protected]
-        description: An example domain.
+        description: An example domain
         email_host: example.com
-        http_etag: "546791f38192b347db544481f1386d33607ccf3d"
+        http_etag: "..."
         resource_type_link: https://localhost:8001/3.0/#domain
         self_link: https://localhost:8001/3.0/domains/example.com
         url_host: lists.example.com
@@ -18,46 +41,47 @@
     start: 0
     total_size: 1
 
-All domains are returned.
+At the top level, all domains are returned as separate entries.
 
-    >>> from mailman.config import config
-    >>> config.push('test domains', """\
-    ... [domain.example_dot_org]
-    ... email_host: example.org
-    ... base_url: http://mail.example.org
-    ... contact_address: [email protected]
-    ...
-    ... [domain.example_dot_net]
-    ... email_host: lists.example.net
-    ... base_url: http://example.net
-    ... contact_address: [email protected]
-    ... """)
+    >>> manager.add(u'example.org',
+    ...             base_url=u'http://mail.example.org',
+    ...             contact_address=u'[email protected]')
+    <Domain example.org, base_url: http://mail.example.org,
+            contact_address: [email protected]>
+    >>> manager.add(u'lists.example.net',
+    ...             u'Porkmasters',
+    ...             u'http://example.net',
+    ...             u'[email protected]')
+    <Domain lists.example.net, Porkmasters,
+            base_url: http://example.net,
+            contact_address: [email protected]>
+    >>> commit()
 
     >>> dump_json('http://localhost:8001/3.0/domains')
     entry 0:
         base_url: http://lists.example.com
         contact_address: [email protected]
-        description: An example domain.
+        description: An example domain
         email_host: example.com
-        http_etag: "546791f38192b347db544481f1386d33607ccf3d"
+        http_etag: "..."
         resource_type_link: https://localhost:8001/3.0/#domain
         self_link: https://localhost:8001/3.0/domains/example.com
         url_host: lists.example.com
     entry 1:
         base_url: http://mail.example.org
         contact_address: [email protected]
-        description: An example domain.
+        description: None
         email_host: example.org
-        http_etag: "4ff00fefca81b99ce2c7e6c50223107daf0649ff"
+        http_etag: "..."
         resource_type_link: https://localhost:8001/3.0/#domain
         self_link: https://localhost:8001/3.0/domains/example.org
         url_host: mail.example.org
     entry 2:
         base_url: http://example.net
         contact_address: [email protected]
-        description: An example domain.
+        description: Porkmasters
         email_host: lists.example.net
-        http_etag: "aa5a388197948f21b8a3eb940b6c9725c5f41fac"
+        http_etag: "..."
         resource_type_link: https://localhost:8001/3.0/#domain
         self_link: https://localhost:8001/3.0/domains/lists.example.net
         url_host: example.net
@@ -75,9 +99,9 @@
     >>> dump_json('http://localhost:8001/3.0/domains/lists.example.net')
     base_url: http://example.net
     contact_address: [email protected]
-    description: An example domain.
+    description: Porkmasters
     email_host: lists.example.net
-    http_etag: "aa5a388197948f21b8a3eb940b6c9725c5f41fac"
+    http_etag: "..."
     resource_type_link: https://localhost:8001/3.0/#domain
     self_link: https://localhost:8001/3.0/domains/lists.example.net
     url_host: example.net
@@ -88,9 +112,3 @@
     Traceback (most recent call last):
     ...
     HTTPError: HTTP Error 404: Not Found
-
-
-Clean up
-========
-
-    >>> config.pop('test domains')

=== modified file 'src/mailman/rest/webservice.py'
--- src/mailman/rest/webservice.py      2009-07-11 01:55:26 +0000
+++ src/mailman/rest/webservice.py      2009-07-17 02:36:06 +0000
@@ -41,7 +41,7 @@
 
 from mailman.config import config
 from mailman.core.system import system
-from mailman.interfaces.domain import IDomainSet
+from mailman.interfaces.domain import IDomainCollection, IDomainManager
 from mailman.interfaces.rest import IResolvePathNames
 from mailman.rest.publication import AdminWebServicePublication
 
@@ -82,13 +82,14 @@
 
     def get(self, name):
         """Maps root names to resources."""
-        log.debug('Getting top level name: %s', name)
         top_level = dict(
             system=system,
-            domains=IDomainSet(config),
+            domains=IDomainCollection(IDomainManager(config)),
             lists=config.db.list_manager,
             )
-        return top_level.get(name)
+        next_step = top_level.get(name)
+        log.debug('Top level name: %s -> %s', name, next_step)
+        return next_step
 
 
 

=== modified file 'src/mailman/testing/layers.py'
--- src/mailman/testing/layers.py       2009-07-11 01:55:26 +0000
+++ src/mailman/testing/layers.py       2009-07-17 02:36:06 +0000
@@ -43,6 +43,7 @@
 from mailman.core import initialize
 from mailman.core.logging import get_handler
 from mailman.i18n import _
+from mailman.interfaces.domain import IDomainManager
 from mailman.testing.helpers import SMTPServer, TestableMaster
 from mailman.utilities.datetime import factory
 from mailman.utilities.string import expand
@@ -161,7 +162,11 @@
 
     @classmethod
     def testSetUp(cls):
-        pass
+        # Add an example domain.
+        IDomainManager(config).add(
+            'example.com', 'An example domain.',
+            'http://lists.example.com', '[email protected]')
+        config.db.commit()
 
     @classmethod
     def testTearDown(cls):

=== modified file 'src/mailman/testing/testing.cfg'
--- src/mailman/testing/testing.cfg     2009-01-03 10:13:41 +0000
+++ src/mailman/testing/testing.cfg     2009-07-17 02:36:06 +0000
@@ -73,11 +73,6 @@
 enable: yes
 command: /bin/echo "/usr/bin/mhonarc -add -dbfile 
$PRIVATE_ARCHIVE_FILE_DIR/${listname}.mbox/mhonarc.db -outdir 
$VAR_DIR/mhonarc/${listname} -stderr $LOG_DIR/mhonarc -stdout $LOG_DIR/mhonarc 
-spammode -umask 022"
 
-[domain.example_dot_com]
-email_host: example.com
-base_url: http://lists.example.com
-contact_address: [email protected]
-
 [language.ja]
 description: Japanese
 charset: euc-jp

=== modified file 'src/mailman/tests/test_documentation.py'
--- src/mailman/tests/test_documentation.py     2009-06-30 03:15:11 +0000
+++ src/mailman/tests/test_documentation.py     2009-07-17 02:36:06 +0000
@@ -174,11 +174,13 @@
             else:
                 layer = getattr(sys.modules[package_path], 'layer', SMTPLayer)
             for filename in os.listdir(docsdir):
+                base, extension = os.path.splitext(filename)
                 if os.path.splitext(filename)[1] == '.txt':
-                    doctest_files[filename] = (
+                    module_path = package_path + '.' + base
+                    doctest_files[module_path] = (
                         os.path.join(docsdir, filename), layer)
-    for filename in sorted(doctest_files):
-        path, layer = doctest_files[filename]
+    for module_path in sorted(doctest_files):
+        path, layer = doctest_files[module_path]
         test = doctest.DocFileSuite(
             path,
             package='mailman',



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

Your team Mailman Checkins is subscribed to branch lp:mailman.
To unsubscribe from this branch go to 
https://code.launchpad.net/~mailman-coders/mailman/3.0/+edit-subscription.
_______________________________________________
Mailman-checkins mailing list
[email protected]
Unsubscribe: 
http://mail.python.org/mailman/options/mailman-checkins/archive%40jab.org

Reply via email to