Commit 211a82dd authored by Barry Warsaw's avatar Barry Warsaw

Support for proper domains by interface. Add a test and update modules as

necessary.  Finally delete DEFAULT_EMAIL_HOST and DEFAULT_URL_HOST.  Now
add_domain() must be called in the confirmation file for every domain.

add_domain() itself is now pretty much just a proxy for the Domain
constructor.

Add stop() as an alias for pdb.set_trace() to the documentation doctest globs.
parent b42f3204
......@@ -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'))
......
......@@ -122,40 +122,6 @@ DEFAULT_VAR_DIRECTORY = '/var/mailman'
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
......
......@@ -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,
......
......@@ -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,
......
......@@ -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):
......
......@@ -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.
......
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
# 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)
......@@ -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
"""
......@@ -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.
......@@ -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':
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment