summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2008-09-20 00:33:07 -0400
committerBarry Warsaw2008-09-20 00:33:07 -0400
commit211a82ddb463ac044ba20d51208e5f5a169dcb6c (patch)
tree77989d93988a32af4c92b5b1b9b7adf0c8fe15b1
parentb42f3204f7223f3ce9ae306dcb2cec10853eca8d (diff)
downloadmailman-211a82ddb463ac044ba20d51208e5f5a169dcb6c.tar.gz
mailman-211a82ddb463ac044ba20d51208e5f5a169dcb6c.tar.zst
mailman-211a82ddb463ac044ba20d51208e5f5a169dcb6c.zip
-rw-r--r--mailman/Cgi/create.py8
-rw-r--r--mailman/Defaults.py34
-rw-r--r--mailman/archiving/mhonarc.py2
-rw-r--r--mailman/archiving/pipermail.py12
-rw-r--r--mailman/archiving/prototype.py3
-rw-r--r--mailman/configuration.py39
-rw-r--r--mailman/docs/domains.txt74
-rw-r--r--mailman/domain.py67
-rw-r--r--mailman/interfaces/domain.py44
-rw-r--r--mailman/testing/testing.cfg.in2
-rw-r--r--mailman/tests/test_documentation.py14
11 files changed, 220 insertions, 79 deletions
diff --git a/mailman/Cgi/create.py b/mailman/Cgi/create.py
index c2b8b710d..a5be1be15 100644
--- a/mailman/Cgi/create.py
+++ b/mailman/Cgi/create.py
@@ -151,8 +151,12 @@ def process_request(doc, cgidata):
# Make sure the url host name matches one of our virtual domains. Then
# calculate the list's posting address.
url_host = Utils.get_request_domain()
- email_host = config.get_email_host(url_host)
- if not email_host:
+ # Find the IDomain matching this url_host if there is one.
+ for email_host, domain in config.domains:
+ if domain.url_host == url_host:
+ email_host = domain.email_host
+ break
+ else:
safehostname = Utils.websafe(url_host)
request_creation(doc, cgidata,
_('Unknown virtual host: $safehostname'))
diff --git a/mailman/Defaults.py b/mailman/Defaults.py
index 6fa457425..5807eb0e8 100644
--- a/mailman/Defaults.py
+++ b/mailman/Defaults.py
@@ -124,40 +124,6 @@ DEFAULT_DATABASE_URL = 'sqlite:///$DATA_DIR/mailman.db'
#####
-# Virtual domains
-#####
-
-# Mailman needs to know about at least one domain, called the 'site default
-# domain'. If you run only one domain with Mailman, this will generally be
-# calculated automatically when you configured Mailman. You can always change
-# this in your mailman.cfg file. You can also add additional virtual domains
-# in your mailman.cfg file to enable multiple virtual domains. Every mailing
-# list will be situated in exactly one virtual domain.
-#
-# For Mailman's purposes, a virtual domain associates an email host name with
-# a web host name. These may be the same, but often they are different, and
-# the list is always referred to by its fully-qualified posting address. For
-# example, if you created 'mylist' in the example.com domain, people can post
-# to your list via 'mylist@example.com'. They may refer to the web pages via
-# 'www.example.com'. So your email host name is 'example.com' and your web
-# host name is 'www.example.com'.
-#
-# To add a virtual domain, put a call to add_domain(email_host, url_host) in
-# your mailman.cfg file. If no add_domain() calls are found, Mailman will
-# automatically add a virtual domain for the following defaults. However if
-# you explicit add domains, you will need to add these defaults as well.
-#
-# These defaults will be filled in by configure.
-DEFAULT_EMAIL_HOST = '@MAILHOST@'
-DEFAULT_URL_HOST = '@URLHOST@'
-
-# Note that you will want to run bin/fix_url.py to change the domain of an
-# existing list. bin/fix_url.py must be run within the bin/withlist script,
-# like so: bin/withlist -l -r bin/fix_url.py <listname>
-
-
-
-#####
# Spam avoidance defaults
#####
diff --git a/mailman/archiving/mhonarc.py b/mailman/archiving/mhonarc.py
index cc549dee8..fcebbb987 100644
--- a/mailman/archiving/mhonarc.py
+++ b/mailman/archiving/mhonarc.py
@@ -52,7 +52,7 @@ class MHonArc:
def list_url(mlist):
"""See `IArchiver`."""
# XXX What about private MHonArc archives?
- web_host = config.domains.get(mlist.host_name, mlist.host_name)
+ web_host = config.domains[mlist.host_name].url_host
return Template(config.PUBLIC_ARCHIVE_URL).safe_substitute(
listname=mlist.fqdn_listname,
hostname=web_host,
diff --git a/mailman/archiving/pipermail.py b/mailman/archiving/pipermail.py
index 1e8f4f28e..5c5fb17ea 100644
--- a/mailman/archiving/pipermail.py
+++ b/mailman/archiving/pipermail.py
@@ -59,7 +59,15 @@ class PipermailMailingListAdapter:
def adapt_mailing_list_for_pipermail(iface, obj):
- """Adapt IMailingLists to IPipermailMailingList."""
+ """Adapt `IMailingLists` to `IPipermailMailingList`.
+
+ :param iface: The interface to adapt to.
+ :type iface: `zope.interface.Interface`
+ :param obj: The object being adapted.
+ :type obj: any object
+ :return: An `IPipermailMailingList` instance if adaptation succeeded or
+ None if it didn't.
+ """
if IMailingList.providedBy(obj) and iface is IPipermailMailingList:
return PipermailMailingListAdapter(obj)
return None
@@ -82,7 +90,7 @@ class Pipermail:
if mlist.archive_private:
url = mlist.script_url('private') + '/index.html'
else:
- web_host = config.domains.get(mlist.host_name, mlist.host_name)
+ web_host = config.domains[mlist.host_name].url_host
url = Template(config.PUBLIC_ARCHIVE_URL).safe_substitute(
listname=mlist.fqdn_listname,
hostname=web_host,
diff --git a/mailman/archiving/prototype.py b/mailman/archiving/prototype.py
index deeaaa624..9e552aac9 100644
--- a/mailman/archiving/prototype.py
+++ b/mailman/archiving/prototype.py
@@ -49,8 +49,7 @@ class Prototype:
@staticmethod
def list_url(mlist):
"""See `IArchiver`."""
- web_host = config.domains.get(mlist.host_name, mlist.host_name)
- return 'http://' + web_host
+ return config.domains[mlist.host_name].base_url
@staticmethod
def permalink(mlist, msg):
diff --git a/mailman/configuration.py b/mailman/configuration.py
index 8a702c457..75e588953 100644
--- a/mailman/configuration.py
+++ b/mailman/configuration.py
@@ -24,6 +24,7 @@ import errno
from mailman import Defaults
from mailman import Errors
from mailman import version
+from mailman.domain import Domain
from mailman.languages import LanguageManager
SPACE = ' '
@@ -45,8 +46,7 @@ DEFAULT_QRUNNERS = (
class Configuration(object):
def __init__(self):
- self.domains = {} # email host -> web host
- self._reverse = None
+ self.domains = {} # email host -> IDomain
self.qrunners = {}
self.qrunner_shortcuts = {}
self.QFILE_SCHEMA_VERSION = version.QFILE_SCHEMA_VERSION
@@ -150,10 +150,6 @@ class Configuration(object):
del ns['add_qrunner']
del ns['del_qrunner']
self.__dict__.update(ns)
- # Add the default domain if there are no virtual domains currently
- # defined.
- if not self.domains:
- self.add_domain(self.DEFAULT_EMAIL_HOST, self.DEFAULT_URL_HOST)
# Enable all specified languages, and promote the language manager to
# a public attribute.
self.languages = self._languages
@@ -181,36 +177,25 @@ class Configuration(object):
self.pipelines = {}
self.commands = {}
- def add_domain(self, email_host, url_host=None):
+ def add_domain(self, *args, **kws):
"""Add a virtual domain.
- :param email_host: The host name for the email interface.
- :param url_host: Optional host name for the web interface. If not
- given, the email host will be used.
+ See `Domain`.
"""
- if url_host is None:
- url_host = email_host
- if email_host in self.domains:
+ domain = Domain(*args, **kws)
+ if domain.email_host in self.domains:
raise Errors.BadDomainSpecificationError(
- 'Duplicate email host: %s' % email_host)
+ 'Duplicate email host: %s' % domain.email_host)
# Make sure there's only one mapping for the url_host
- if url_host in self.domains.values():
+ if domain.url_host in self.domains.values():
raise Errors.BadDomainSpecificationError(
- 'Duplicate url host: %s' % url_host)
+ '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[email_host] = url_host
- # Invalidate the reverse mapping cache
- self._reverse = None
+ self.domains[domain.email_host] = domain
- # Given an email host name, the url host name can be looked up directly.
- # This does the reverse mapping.
- def get_email_host(self, url_host, default=None):
- if self._reverse is None:
- # XXX Can't use a generator comprehension until Python 2.4 is
- # minimum requirement.
- self._reverse = dict([(v, k) for k, v in self.domains.items()])
- return self._reverse.get(url_host, default)
+ # Proxy the docstring for the above method.
+ add_domain.__doc__ = Domain.__init__.__doc__
def add_qrunner(self, name, count=1):
"""Convenient interface for adding additional qrunners.
diff --git a/mailman/docs/domains.txt b/mailman/docs/domains.txt
new file mode 100644
index 000000000..6ae53931c
--- /dev/null
+++ b/mailman/docs/domains.txt
@@ -0,0 +1,74 @@
+Domains
+=======
+
+Domains are how Mailman interacts with email host names and web host names.
+Generally, new domains are registered in the mailman.cfg configuration file by
+calling the add_domain() method.
+
+At a minimum, the email host name must be specified.
+
+ >>> from mailman.configuration import config
+ >>> config.add_domain('example.net')
+
+The domain object can be looked up by email host name.
+
+ >>> domain = config.domains['example.net']
+ >>> print domain.email_host
+ example.net
+
+The base url is calculated by default if not given.
+
+ >>> print domain.base_url
+ http://example.net
+
+There is no description by default.
+
+ >>> print domain.description
+ None
+
+By default, the contact address is the domain's postmaster.
+
+ >>> print domain.contact_address
+ postmaster@example.net
+
+And the url_host is by default the same as the email host.
+
+ >>> print domain.url_host
+ example.net
+
+
+Full specification
+------------------
+
+The domain can also be much more fully defined.
+
+ >>> config.add_domain(
+ ... 'example.org', 'https://mail.example.org',
+ ... 'The example domain',
+ ... 'postmaster@mail.example.org')
+
+ >>> 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
+ postmaster@mail.example.org
+ >>> print domain.url_host
+ mail.example.org
+
+
+Confirmation tokens
+-------------------
+
+Confirmation tokens can be added to either the email confirmation address...
+
+ >>> print domain.confirm_address('xyz')
+ confirm-xyz@example.org
+
+...or the confirmation url.
+
+ >>> print domain.confirm_url('abc')
+ https://mail.example.org/confirm/abc
diff --git a/mailman/domain.py b/mailman/domain.py
new file mode 100644
index 000000000..c93e13ab0
--- /dev/null
+++ b/mailman/domain.py
@@ -0,0 +1,67 @@
+# Copyright (C) 2008 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+"""Domains."""
+
+__metaclass__ = type
+__all__ = [
+ 'Domain',
+ ]
+
+from urlparse import urljoin, urlparse
+from zope.interface import implements
+
+from mailman.interfaces.domain import IDomain
+
+
+
+class Domain:
+ """Domains."""
+
+ implements(IDomain)
+
+ def __init__(self, email_host, base_url=None, description=None,
+ contact_address=None):
+ """Create and register a domain.
+
+ :param email_host: The host name for the email interface.
+ :type email_host: 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
+ domain. If not given, postmaster@`email_host` will be used.
+ """
+ self.email_host = email_host
+ self.base_url = (base_url if base_url is not None else
+ 'http://' + email_host)
+ self.description = description
+ self.contact_address = (contact_address
+ if contact_address is not None
+ else 'postmaster@' + email_host)
+ self.url_host = urlparse(self.base_url).netloc
+
+ def confirm_address(self, token=''):
+ """See `IDomain`."""
+ return 'confirm-%s@%s' % (token, self.email_host)
+
+ def confirm_url(self, token=''):
+ """See `IDomain`."""
+ return urljoin(self.base_url, 'confirm/' + token)
diff --git a/mailman/interfaces/domain.py b/mailman/interfaces/domain.py
index 4cfec42cb..99a2b9c91 100644
--- a/mailman/interfaces/domain.py
+++ b/mailman/interfaces/domain.py
@@ -24,30 +24,54 @@ from zope.interface import Interface, Attribute
class IDomain(Interface):
"""Interface representing domains."""
- domain_name = Attribute(
- """The domain's name, e.g. python.org.""")
+ email_host = Attribute(
+ """The host name for email for this domain.
+
+ :type: string
+ """)
+
+ url_host = Attribute(
+ """The host name for the web interface for this domain.
+
+ :type: string
+ """)
+
+ base_url = Attribute(
+ """The base url for the Mailman server at this domain.
+
+ The base url includes the scheme and host name.
+
+ :type: string
+ """)
description = Attribute(
"""The human readable description of the domain name.
- E.g. Python Dot Org or mail.python.org.
+ :type: string
""")
contact_address = Attribute(
"""The contact address for the human at this domain.
E.g. postmaster@python.org.
- """)
- base_url = Attribute(
- """The base url for the Mailman server at this domain.
-
- E.g. https://mail.python.org
+ :type: string
""")
def confirm_address(token=''):
- """The address used for various forms of email confirmation."""
+ """The address used for various forms of email confirmation.
+
+ :param token: The confirmation token to use in the email address.
+ :type token: string
+ :return: The email confirmation address.
+ :rtype: string
+ """
def confirm_url(token=''):
- """The url used for various forms of confirmation."""
+ """The url used for various forms of confirmation.
+ :param token: The confirmation token to use in the url.
+ :type token: string
+ :return: The confirmation url.
+ :rtype: string
+ """
diff --git a/mailman/testing/testing.cfg.in b/mailman/testing/testing.cfg.in
index c8e121079..e544242d0 100644
--- a/mailman/testing/testing.cfg.in
+++ b/mailman/testing/testing.cfg.in
@@ -12,6 +12,6 @@ USE_LMTP = Yes
MAIL_ARCHIVE_BASEURL = 'http://go.mail-archive.dev/'
MAIL_ARCHIVE_RECIPIENT = 'archive@mail-archive.dev'
-add_domain('example.com', 'www.example.com')
+add_domain('example.com', 'http://www.example.com')
# bin/testall will add additional runtime configuration variables here.
diff --git a/mailman/tests/test_documentation.py b/mailman/tests/test_documentation.py
index 2d42f989d..13ffd8cbf 100644
--- a/mailman/tests/test_documentation.py
+++ b/mailman/tests/test_documentation.py
@@ -53,6 +53,14 @@ def specialized_message_from_string(text):
return message
+def stop():
+ """Call into pdb.set_trace()"""
+ # Do the import here so that you get the wacky special hacked pdb instead
+ # of Python's normal pdb.
+ import pdb
+ pdb.set_trace()
+
+
def setup(testobj):
"""Test setup."""
smtpd = SMTPServer()
@@ -64,6 +72,10 @@ def setup(testobj):
testobj.globs['message_from_string'] = specialized_message_from_string
testobj.globs['commit'] = config.db.commit
testobj.globs['smtpd'] = smtpd
+ testobj.globs['stop'] = stop
+ # Stash the current state of the global domains away for restoration in
+ # the teardown.
+ testobj._domains = config.domains.copy()
@@ -71,6 +83,8 @@ def cleaning_teardown(testobj):
"""Clear all persistent data at the end of a doctest."""
# Clear the database of all rows.
config.db._reset()
+ # Reset the global domains.
+ config.domains = testobj._domains
# Remove all but the default style.
for style in style_manager.styles:
if style.name <> 'default':