diff options
| author | Barry Warsaw | 2008-09-20 00:33:07 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2008-09-20 00:33:07 -0400 |
| commit | 211a82ddb463ac044ba20d51208e5f5a169dcb6c (patch) | |
| tree | 77989d93988a32af4c92b5b1b9b7adf0c8fe15b1 | |
| parent | b42f3204f7223f3ce9ae306dcb2cec10853eca8d (diff) | |
| download | mailman-211a82ddb463ac044ba20d51208e5f5a169dcb6c.tar.gz mailman-211a82ddb463ac044ba20d51208e5f5a169dcb6c.tar.zst mailman-211a82ddb463ac044ba20d51208e5f5a169dcb6c.zip | |
| -rw-r--r-- | mailman/Cgi/create.py | 8 | ||||
| -rw-r--r-- | mailman/Defaults.py | 34 | ||||
| -rw-r--r-- | mailman/archiving/mhonarc.py | 2 | ||||
| -rw-r--r-- | mailman/archiving/pipermail.py | 12 | ||||
| -rw-r--r-- | mailman/archiving/prototype.py | 3 | ||||
| -rw-r--r-- | mailman/configuration.py | 39 | ||||
| -rw-r--r-- | mailman/docs/domains.txt | 74 | ||||
| -rw-r--r-- | mailman/domain.py | 67 | ||||
| -rw-r--r-- | mailman/interfaces/domain.py | 44 | ||||
| -rw-r--r-- | mailman/testing/testing.cfg.in | 2 | ||||
| -rw-r--r-- | mailman/tests/test_documentation.py | 14 |
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': |
