------------------------------------------------------------
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