summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman/Archiver/HyperArch.py2
-rw-r--r--src/mailman/app/tests/test_bounces.py24
-rw-r--r--src/mailman/config/config.py6
-rw-r--r--src/mailman/config/schema.cfg9
-rw-r--r--src/mailman/docs/NEWS.rst4
-rw-r--r--src/mailman/rest/root.py24
-rw-r--r--src/mailman/utilities/i18n.py138
-rw-r--r--src/mailman/utilities/tests/test_templates.py245
8 files changed, 248 insertions, 204 deletions
diff --git a/src/mailman/Archiver/HyperArch.py b/src/mailman/Archiver/HyperArch.py
index cfb7813bf..017c14342 100644
--- a/src/mailman/Archiver/HyperArch.py
+++ b/src/mailman/Archiver/HyperArch.py
@@ -183,7 +183,7 @@ def quick_maketext(templatefile, dict=None, lang=None, mlist=None):
template = _templatecache.get(filepath)
if filepath is None or template is None:
# Use the basic maketext, with defaults to get the raw template
- template, filepath = find(templatefile, mailing_list=mlist,
+ template, filepath = find(templatefile, mlist=mlist,
language=lang.code)
_templatefilepathcache[cachekey] = filepath
_templatecache[filepath] = template
diff --git a/src/mailman/app/tests/test_bounces.py b/src/mailman/app/tests/test_bounces.py
index 3e2571e55..3c4c80faf 100644
--- a/src/mailman/app/tests/test_bounces.py
+++ b/src/mailman/app/tests/test_bounces.py
@@ -17,7 +17,7 @@
"""Testing app.bounces functions."""
-from __future__ import absolute_import, unicode_literals
+from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
@@ -289,30 +289,29 @@ Message-ID: <first>
""")
# Set up the translation context.
- self._template_dir = tempfile.mkdtemp()
+ self._var_dir = tempfile.mkdtemp()
xx_template_path = os.path.join(
- self._template_dir, 't', 'xx', 'probe.txt')
+ self._var_dir, 'templates', 'site', 'xx', 'probe.txt')
os.makedirs(os.path.dirname(xx_template_path))
config.push('xx template dir', """\
[paths.testing]
- template_dir: {0}/t
- var_dir: {0}/v
- """.format(self._template_dir))
+ var_dir: {0}
+ """.format(self._var_dir))
language_manager = getUtility(ILanguageManager)
language_manager.add('xx', 'utf-8', 'Freedonia')
self._member.preferences.preferred_language = 'xx'
with open(xx_template_path, 'w') as fp:
- print >> fp, """\
+ print("""\
blah blah blah
$listname
$address
$optionsurl
$owneraddr
-"""
+""", file=fp)
def tearDown(self):
config.pop('xx template dir')
- shutil.rmtree(self._template_dir)
+ shutil.rmtree(self._var_dir)
def test_subject_with_member_nonenglish(self):
# Test that members with non-English preferred language get a Subject
@@ -329,7 +328,12 @@ $owneraddr
send_probe(self._member, self._msg)
message = get_queue_messages('virgin')[0].msg
notice = message.get_payload(0).get_payload()
- self.assertEqual(notice, """\
+ try:
+ # Python 2.7
+ eq = self.assertMultiLineEqual
+ except AttributeError:
+ eq = self.assertEqual
+ eq(notice, """\
blah blah blah test@example.com anne@example.com
http://example.com/anne@example.com test-owner@example.com""")
diff --git a/src/mailman/config/config.py b/src/mailman/config/config.py
index 89d9e39ad..034b76b4f 100644
--- a/src/mailman/config/config.py
+++ b/src/mailman/config/config.py
@@ -17,7 +17,7 @@
"""Configuration file loading and management."""
-from __future__ import absolute_import, unicode_literals
+from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
@@ -152,7 +152,7 @@ class Configuration:
if category.name == layout:
break
else:
- print >> sys.stderr, 'No path configuration found:', layout
+ print('No path configuration found:', layout, file=sys.stderr)
sys.exit(1)
# First, collect all variables in a substitution dictionary. $VAR_DIR
# is taken from the environment or from the configuration file if the
@@ -202,7 +202,7 @@ class Configuration:
if dollar_count == 0:
break
if dollar_count == last_dollar_count:
- print >> sys.stderr, 'Path expansion infloop detected'
+ print('Path expansion infloop detected', file=sys.stderr)
sys.exit(1)
last_dollar_count = dollar_count
# Ensure that all paths are normalized and made absolute. Handle the
diff --git a/src/mailman/config/schema.cfg b/src/mailman/config/schema.cfg
index 5a64db268..cde01cbd7 100644
--- a/src/mailman/config/schema.cfg
+++ b/src/mailman/config/schema.cfg
@@ -102,13 +102,8 @@ messages_dir: $var_dir/messages
pipermail_public_dir: $var_dir/archives/public
# Directory for private Pipermail archiver artifacts.
pipermail_private_dir: $var_dir/archives/private
-#
-# Where Mailman looks for its templates. This can either be a file system
-# path or the special symbol ':source:' to locate them within the source tree
-# (specifically, inside the mailman.templates package directory).
-#
-template_dir: :source:
-#
+# Root directory for site-specific template override files.
+template_dir: $var_dir/templates
# There are also a number of paths to specific file locations that can be
# defined. For these, the directory containing the file must already exist,
# or be one of the directories created by Mailman as per above.
diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst
index 368483c05..2b4deb9c2 100644
--- a/src/mailman/docs/NEWS.rst
+++ b/src/mailman/docs/NEWS.rst
@@ -21,6 +21,10 @@ Architecture
* Separate out the RFC 2369 header adding handler.
* Dynamically calculate the `List-Id` header instead of storing it in the
database. This means it cannot be changed.
+ * Major redesign of the template search system, fixing LP: #788309. $var_dir
+ is now used when search for all template overrides, site, domain, or
+ mailing list. The in-tree English templates are used only as a last
+ fallback.
REST
----
diff --git a/src/mailman/rest/root.py b/src/mailman/rest/root.py
index 794d7510f..8c1a31cf2 100644
--- a/src/mailman/rest/root.py
+++ b/src/mailman/rest/root.py
@@ -27,16 +27,19 @@ __all__ = [
from base64 import b64decode
from restish import guard, http, resource
+from zope.component import getUtility
from mailman.config import config
from mailman.core.constants import system_preferences
from mailman.core.system import system
+from mailman.interfaces.listmanager import IListManager
from mailman.rest.addresses import AllAddresses, AnAddress
from mailman.rest.domains import ADomain, AllDomains
from mailman.rest.helpers import etag, path_to
from mailman.rest.lists import AList, AllLists
from mailman.rest.members import AMember, AllMembers, FindMembers
from mailman.rest.preferences import ReadOnlyPreferences
+from mailman.rest.templates import TemplateFinder
from mailman.rest.users import AUser, AllUsers
@@ -144,3 +147,24 @@ class TopLevel(resource.Resource):
else:
user_id = segments.pop(0)
return AUser(user_id), segments
+
+ @resource.child()
+ def templates(self, request, segments):
+ """/<api>/templates/<fqdn_listname>/<template>/[<language>]
+
+ Use content negotiation to request language and suffix (content-type).
+ """
+ if len(segments) == 3:
+ fqdn_listname, template, language = segments
+ elif len(segments) == 2:
+ fqdn_listname, template = segments
+ language = 'en'
+ else:
+ return http.bad_request()
+ mlist = getUtility(IListManager).get(fqdn_listname)
+ if mlist is None:
+ return http.not_found()
+ # XXX dig out content-type from request
+ content_type = None
+ return TemplateFinder(
+ fqdn_listname, template, language, content_type)
diff --git a/src/mailman/utilities/i18n.py b/src/mailman/utilities/i18n.py
index e2d14390a..c02c6d5ba 100644
--- a/src/mailman/utilities/i18n.py
+++ b/src/mailman/utilities/i18n.py
@@ -17,13 +17,14 @@
"""i18n template search and interpolation."""
-from __future__ import absolute_import, unicode_literals
+from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
'TemplateNotFoundError',
'find',
'make',
+ 'search',
]
@@ -32,6 +33,7 @@ import sys
import errno
from itertools import product
+from pkg_resources import resource_filename
from mailman.config import config
from mailman.core.constants import system_preferences
@@ -52,62 +54,29 @@ class TemplateNotFoundError(MailmanException):
-def _search(template_file, mailing_list=None, language=None):
- """Generator that provides file system search order."""
+def search(template_file, mlist=None, language=None):
+ """Generator that provides file system search order.
- languages = ['en', system_preferences.preferred_language.code]
- if mailing_list is not None:
- languages.append(mailing_list.preferred_language.code)
- if language is not None:
- languages.append(language)
- languages.reverse()
- # File system locations to search.
- paths = [config.TEMPLATE_DIR,
- os.path.join(config.TEMPLATE_DIR, 'site')]
- if mailing_list is not None:
- paths.append(os.path.join(config.TEMPLATE_DIR,
- mailing_list.mail_host))
- paths.append(os.path.join(config.LIST_DATA_DIR,
- mailing_list.fqdn_listname))
- paths.reverse()
- for language, path in product(languages, paths):
- yield os.path.join(path, language, template_file)
+ This is Mailman's internal template search algorithm. The first locations
+ searched are within the $var_dir/templates directory, allowing a site to
+ override a template for a specific mailing list, all the mailing lists in
+ a domain, or site-wide.
-
-
-def find(template_file, mailing_list=None, language=None, _trace=False):
- """Locate an i18n template file.
-
- When something in Mailman needs a template file, it always asks for the
- file through this interface. The results of the search is path to the
- 'matching' template, with the search order depending on whether
- `mailing_list` and `language` are provided.
-
- When looking for a template in a specific language, there are 4 locations
- that are searched, in this order:
+ The <language> path component is variable, and described below.
* The list-specific language directory
- <var_dir>/lists/<fqdn_listname>/<language>
+ <var_dir>/templates/lists/<mlist.fqdn_listname>/<language>
* The domain-specific language directory
- <template_dir>/<list-host-name>/<language>
+ <var_dir>/templates/domains/<mlist.mail_host>/<language>
* The site-wide language directory
- <template_dir>/site/<language>
-
- * The global default language directory
- <template_dir>/<language>
-
- The first match stops the search. In this way, you can specialize
- templates at the desired level, or if you only use the default templates,
- you don't need to change anything. NEVER modify files in
- <template_dir>/<language> since Mailman will overwrite these when you
- upgrade. Instead you can use <template_dir>/site.
+ <var_dir>/templates/site/<language>
The <language> path component is calculated as follows, in this order:
* The `language` parameter if given
- * `mailing_list.preferred_language` if given
+ * `mlist.preferred_language` if given
* The server's default language
* English ('en')
@@ -117,32 +86,59 @@ def find(template_file, mailing_list=None, language=None, _trace=False):
is 'de' and the `language` parameter is 'it', these locations are searched
in order:
- * <var_dir>/lists/test@example.com/it/foo.txt
- * <template_dir>/example.com/it/foo.txt
- * <template_dir>/site/it/foo.txt
- * <template_dir>/it/foo.txt
+ * <var_dir>/templates/lists/test@example.com/it/foo.txt
+ * <var_dir>/templates/domains/example.com/it/foo.txt
+ * <var_dir>/templates/site/it/foo.txt
- * <var_dir>/lists/test@example.com/de/foo.txt
- * <template_dir>/example.com/de/foo.txt
- * <template_dir>/site/de/foo.txt
- * <template_dir>/de/foo.txt
+ * <var_dir>/templates/lists/test@example.com/de/foo.txt
+ * <var_dir>/templates/domains/example.com/de/foo.txt
+ * <var_dir>/templates/site/de/foo.txt
- * <var_dir>/lists/test@example.com/fr/foo.txt
- * <template_dir>/example.com/fr/foo.txt
- * <template_dir>/site/fr/foo.txt
- * <template_dir>/fr/foo.txt
+ * <var_dir>/templates/lists/test@example.com/fr/foo.txt
+ * <var_dir>/templates/domains/example.com/fr/foo.txt
+ * <var_dir>/templates/site/fr/foo.txt
- * <var_dir>/lists/test@example.com/en/foo.txt
- * <template_dir>/example.com/en/foo.txt
- * <template_dir>/site/en/foo.txt
- * <template_dir>/en/foo.txt
+ * <var_dir>/templates/lists/test@example.com/en/foo.txt
+ * <var_dir>/templates/domains/example.com/en/foo.txt
+ * <var_dir>/templates/site/en/foo.txt
+
+ After all those paths are searched, the final fallback is the English
+ template within the Mailman source tree.
+
+ * <source_dir>/templates/en/foo.txt
+ """
+ # The languages in search order.
+ languages = ['en', system_preferences.preferred_language.code]
+ if mlist is not None:
+ languages.append(mlist.preferred_language.code)
+ if language is not None:
+ languages.append(language)
+ languages.reverse()
+ # The non-language qualified $var_dir paths in search order.
+ paths = [os.path.join(config.VAR_DIR, 'templates', 'site')]
+ if mlist is not None:
+ paths.append(os.path.join(
+ config.VAR_DIR, 'templates', 'domains', mlist.mail_host))
+ paths.append(os.path.join(
+ config.VAR_DIR, 'templates', 'lists', mlist.fqdn_listname))
+ paths.reverse()
+ for language, path in product(languages, paths):
+ yield os.path.join(path, language, template_file)
+ # Finally, fallback to the in-tree English template.
+ templates_dir = resource_filename('mailman', 'templates')
+ yield os.path.join(templates_dir, 'en', template_file)
+
+
+
+def find(template_file, mlist=None, language=None, _trace=False):
+ """Use Mailman's internal template search order to find a template.
:param template_file: The name of the template file to search for.
:type template_file: string
- :param mailing_list: Optional mailing list used as the context for
+ :param mlist: Optional mailing list used as the context for
searching for the template file. The list's preferred language will
influence the search, as will the list's data directory.
- :type mailing_list: `IMailingList`
+ :type mlist: `IMailingList`
:param language: Optional language code, which influences the search.
:type language: string
:param _trace: Enable printing of debugging information during
@@ -153,26 +149,26 @@ def find(template_file, mailing_list=None, language=None, _trace=False):
:rtype: (string, file)
:raises TemplateNotFoundError: when the template could not be found.
"""
- raw_search_order = _search(template_file, mailing_list, language)
+ raw_search_order = search(template_file, mlist, language)
for path in raw_search_order:
try:
if _trace:
- print >> sys.stderr, '@@@', path,
+ print('@@@', path, end='', file=sys.stderr)
fp = open(path)
except IOError as error:
if error.errno == errno.ENOENT:
if _trace:
- print >> sys.stderr, 'MISSING'
+ print('MISSING', file=sys.stderr)
else:
raise
else:
if _trace:
- print >> sys.stderr, 'FOUND:', path
+ print('FOUND:', path, file=sys.stderr)
return path, fp
raise TemplateNotFoundError(template_file)
-def make(template_file, mailing_list=None, language=None, wrap=True,
+def make(template_file, mlist=None, language=None, wrap=True,
_trace=False, **kw):
"""Locate and 'make' a template file.
@@ -181,10 +177,10 @@ def make(template_file, mailing_list=None, language=None, wrap=True,
:param template_file: The name of the template file to search for.
:type template_file: string
- :param mailing_list: Optional mailing list used as the context for
+ :param mlist: Optional mailing list used as the context for
searching for the template file. The list's preferred language will
influence the search, as will the list's data directory.
- :type mailing_list: `IMailingList`
+ :type mlist: `IMailingList`
:param language: Optional language code, which influences the search.
:type language: string
:param wrap: When True, wrap the text.
@@ -197,7 +193,7 @@ def make(template_file, mailing_list=None, language=None, wrap=True,
:rtype: string
:raises TemplateNotFoundError: when the template could not be found.
"""
- path, fp = find(template_file, mailing_list, language, _trace)
+ path, fp = find(template_file, mlist, language, _trace)
try:
# XXX Removing the trailing newline is a hack carried over from
# Mailman 2. The (stripped) template text is then passed through the
diff --git a/src/mailman/utilities/tests/test_templates.py b/src/mailman/utilities/tests/test_templates.py
index 1b2f1db07..d205eef34 100644
--- a/src/mailman/utilities/tests/test_templates.py
+++ b/src/mailman/utilities/tests/test_templates.py
@@ -17,7 +17,7 @@
"""Testing i18n template search and interpolation."""
-from __future__ import absolute_import, unicode_literals
+from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
@@ -29,13 +29,14 @@ import shutil
import tempfile
import unittest
+from pkg_resources import resource_filename
from zope.component import getUtility
from mailman.app.lifecycle import create_list
from mailman.config import config
from mailman.interfaces.languages import ILanguageManager
from mailman.testing.layers import ConfigLayer
-from mailman.utilities.i18n import TemplateNotFoundError, _search, find, make
+from mailman.utilities.i18n import TemplateNotFoundError, find, make, search
@@ -45,14 +46,13 @@ class TestSearchOrder(unittest.TestCase):
layer = ConfigLayer
def setUp(self):
- self.template_dir = tempfile.mkdtemp()
+ self.var_dir = tempfile.mkdtemp()
config.push('no template dir', """\
[mailman]
default_language: fr
[paths.testing]
- template_dir: {0}/t
- var_dir: {0}/v
- """.format(self.template_dir))
+ var_dir: {0}
+ """.format(self.var_dir))
language_manager = getUtility(ILanguageManager)
language_manager.add('de', 'utf-8', 'German')
language_manager.add('it', 'utf-8', 'Italian')
@@ -61,82 +61,110 @@ class TestSearchOrder(unittest.TestCase):
def tearDown(self):
config.pop('no template dir')
- shutil.rmtree(self.template_dir)
+ shutil.rmtree(self.var_dir)
def _stripped_search_order(self, template_file,
mailing_list=None, language=None):
- raw_search_order = _search(template_file, mailing_list, language)
+ # Return the search path order for a given template, possibly using
+ # the mailing list and the language as context. Note that this only
+ # returns the search path, and does not check for whether the paths
+ # exist or not.
+ #
+ # Replace the tempdir prefix with a placeholder for more readable and
+ # reproducible tests. Essentially the paths below are rooted at
+ # $var_dir, except those files that live within Mailman's source
+ # tree. The former will use /v/ as the root and the latter will use
+ # /m/ as the root.
+ in_tree = os.path.dirname(resource_filename('mailman', 'templates'))
+ raw_search_order = search(template_file, mailing_list, language)
for path in raw_search_order:
- yield path[len(self.template_dir):]
+ if path.startswith(self.var_dir):
+ path = '/v' + path[len(self.var_dir):]
+ elif path.startswith(in_tree):
+ path = '/m' + path[len(in_tree):]
+ else:
+ # This will cause tests to fail, so keep the full bogus
+ # pathname for better debugging.
+ pass
+ yield path
def test_fully_specified_search_order(self):
search_order = self._stripped_search_order('foo.txt', self.mlist, 'it')
- # language argument
- self.assertEqual(next(search_order),
- '/v/lists/l@example.com/it/foo.txt')
- self.assertEqual(next(search_order), '/t/example.com/it/foo.txt')
- self.assertEqual(next(search_order), '/t/site/it/foo.txt')
- self.assertEqual(next(search_order), '/t/it/foo.txt')
- # mlist.preferred_language
- self.assertEqual(next(search_order),
- '/v/lists/l@example.com/de/foo.txt')
- self.assertEqual(next(search_order), '/t/example.com/de/foo.txt')
- self.assertEqual(next(search_order), '/t/site/de/foo.txt')
- self.assertEqual(next(search_order), '/t/de/foo.txt')
- # site's default language
- self.assertEqual(next(search_order),
- '/v/lists/l@example.com/fr/foo.txt')
- self.assertEqual(next(search_order), '/t/example.com/fr/foo.txt')
- self.assertEqual(next(search_order), '/t/site/fr/foo.txt')
- self.assertEqual(next(search_order), '/t/fr/foo.txt')
- # English
- self.assertEqual(next(search_order),
- '/v/lists/l@example.com/en/foo.txt')
- self.assertEqual(next(search_order), '/t/example.com/en/foo.txt')
- self.assertEqual(next(search_order), '/t/site/en/foo.txt')
- self.assertEqual(next(search_order), '/t/en/foo.txt')
+ # For convenience.
+ def nexteq(path):
+ self.assertEqual(next(search_order), path)
+ # 1: Use the given language argument
+ nexteq('/v/templates/lists/l@example.com/it/foo.txt')
+ nexteq('/v/templates/domains/example.com/it/foo.txt')
+ nexteq('/v/templates/site/it/foo.txt')
+ # 2: Use mlist.preferred_language
+ nexteq('/v/templates/lists/l@example.com/de/foo.txt')
+ nexteq('/v/templates/domains/example.com/de/foo.txt')
+ nexteq('/v/templates/site/de/foo.txt')
+ # 3: Use the site's default language
+ nexteq('/v/templates/lists/l@example.com/fr/foo.txt')
+ nexteq('/v/templates/domains/example.com/fr/foo.txt')
+ nexteq('/v/templates/site/fr/foo.txt')
+ # 4: English
+ nexteq('/v/templates/lists/l@example.com/en/foo.txt')
+ nexteq('/v/templates/domains/example.com/en/foo.txt')
+ nexteq('/v/templates/site/en/foo.txt')
+ # 5: After all the site-admin override paths have been searched, the
+ # Mailman in-tree paths are searched. Note that Mailman only ships
+ # one set of English templates.
+ nexteq('/m/templates/en/foo.txt')
def test_no_language_argument_search_order(self):
search_order = self._stripped_search_order('foo.txt', self.mlist)
- # mlist.preferred_language
- self.assertEqual(next(search_order),
- '/v/lists/l@example.com/de/foo.txt')
- self.assertEqual(next(search_order), '/t/example.com/de/foo.txt')
- self.assertEqual(next(search_order), '/t/site/de/foo.txt')
- self.assertEqual(next(search_order), '/t/de/foo.txt')
- # site's default language
- self.assertEqual(next(search_order),
- '/v/lists/l@example.com/fr/foo.txt')
- self.assertEqual(next(search_order), '/t/example.com/fr/foo.txt')
- self.assertEqual(next(search_order), '/t/site/fr/foo.txt')
- self.assertEqual(next(search_order), '/t/fr/foo.txt')
- # English
- self.assertEqual(next(search_order),
- '/v/lists/l@example.com/en/foo.txt')
- self.assertEqual(next(search_order), '/t/example.com/en/foo.txt')
- self.assertEqual(next(search_order), '/t/site/en/foo.txt')
- self.assertEqual(next(search_order), '/t/en/foo.txt')
+ # For convenience.
+ def nexteq(path):
+ self.assertEqual(next(search_order), path)
+ # 1: Use mlist.preferred_language
+ nexteq('/v/templates/lists/l@example.com/de/foo.txt')
+ nexteq('/v/templates/domains/example.com/de/foo.txt')
+ nexteq('/v/templates/site/de/foo.txt')
+ # 2: Use the site's default language
+ nexteq('/v/templates/lists/l@example.com/fr/foo.txt')
+ nexteq('/v/templates/domains/example.com/fr/foo.txt')
+ nexteq('/v/templates/site/fr/foo.txt')
+ # 3: English
+ nexteq('/v/templates/lists/l@example.com/en/foo.txt')
+ nexteq('/v/templates/domains/example.com/en/foo.txt')
+ nexteq('/v/templates/site/en/foo.txt')
+ # 4: After all the site-admin override paths have been searched, the
+ # Mailman in-tree paths are searched. Note that Mailman only ships
+ # one set of English templates.
+ nexteq('/m/templates/en/foo.txt')
def test_no_mailing_list_argument_search_order(self):
search_order = self._stripped_search_order('foo.txt', language='it')
- # language argument
- self.assertEqual(next(search_order), '/t/site/it/foo.txt')
- self.assertEqual(next(search_order), '/t/it/foo.txt')
- # site's default language
- self.assertEqual(next(search_order), '/t/site/fr/foo.txt')
- self.assertEqual(next(search_order), '/t/fr/foo.txt')
- # English
- self.assertEqual(next(search_order), '/t/site/en/foo.txt')
- self.assertEqual(next(search_order), '/t/en/foo.txt')
+ # For convenience.
+ def nexteq(path):
+ self.assertEqual(next(search_order), path)
+ # 1: Use the given language argument
+ nexteq('/v/templates/site/it/foo.txt')
+ # 2: Use the site's default language
+ nexteq('/v/templates/site/fr/foo.txt')
+ # 3: English
+ nexteq('/v/templates/site/en/foo.txt')
+ # 4: After all the site-admin override paths have been searched, the
+ # Mailman in-tree paths are searched. Note that Mailman only ships
+ # one set of English templates.
+ nexteq('/m/templates/en/foo.txt')
def test_no_optional_arguments_search_order(self):
search_order = self._stripped_search_order('foo.txt')
- # site's default language
- self.assertEqual(next(search_order), '/t/site/fr/foo.txt')
- self.assertEqual(next(search_order), '/t/fr/foo.txt')
- # English
- self.assertEqual(next(search_order), '/t/site/en/foo.txt')
- self.assertEqual(next(search_order), '/t/en/foo.txt')
+ # For convenience.
+ def nexteq(path):
+ self.assertEqual(next(search_order), path)
+ # 1: Use the site's default language
+ nexteq('/v/templates/site/fr/foo.txt')
+ # 2: English
+ nexteq('/v/templates/site/en/foo.txt')
+ # 3: After all the site-admin override paths have been searched, the
+ # Mailman in-tree paths are searched. Note that Mailman only ships
+ # one set of English templates.
+ nexteq('/m/templates/en/foo.txt')
@@ -146,60 +174,53 @@ class TestFind(unittest.TestCase):
layer = ConfigLayer
def setUp(self):
- self.template_dir = tempfile.mkdtemp()
+ self.var_dir = tempfile.mkdtemp()
config.push('template config', """\
[paths.testing]
- template_dir: {0}
- """.format(self.template_dir))
+ var_dir: {0}
+ """.format(self.var_dir))
# The following MUST happen AFTER the push() above since pushing a new
# config also clears out the language manager.
getUtility(ILanguageManager).add('xx', 'utf-8', 'Xlandia')
self.mlist = create_list('test@example.com')
self.mlist.preferred_language = 'xx'
self.fp = None
- # Populate global tempdir with a few fake templates.
- self.xxdir = os.path.join(self.template_dir, 'xx')
- os.mkdir(self.xxdir)
- with open(os.path.join(self.xxdir, 'global.txt'), 'w') as fp:
- fp.write('Global template')
- self.sitedir = os.path.join(self.template_dir, 'site', 'xx')
- os.makedirs(self.sitedir)
- with open(os.path.join(self.sitedir, 'site.txt'), 'w') as fp:
- fp.write('Site template')
- self.domaindir = os.path.join(self.template_dir, 'example.com', 'xx')
- os.makedirs(self.domaindir)
- with open(os.path.join(self.domaindir, 'domain.txt'), 'w') as fp:
- fp.write('Domain template')
- self.listdir = os.path.join(self.mlist.data_path, 'xx')
- os.makedirs(self.listdir)
- with open(os.path.join(self.listdir, 'list.txt'), 'w') as fp:
- fp.write('List template')
+ # Populate the template directories with a few fake templates.
+ def write(text, path):
+ os.makedirs(os.path.dirname(path))
+ with open(path, 'w') as fp:
+ fp.write(text)
+ self.xxsite = os.path.join(
+ self.var_dir, 'templates', 'site', 'xx', 'site.txt')
+ write('Site template', self.xxsite)
+ self.xxdomain = os.path.join(
+ self.var_dir, 'templates',
+ 'domains', 'example.com', 'xx', 'domain.txt')
+ write('Domain template', self.xxdomain)
+ self.xxlist = os.path.join(
+ self.var_dir, 'templates',
+ 'lists', 'test@example.com', 'xx', 'list.txt')
+ write('List template', self.xxlist)
def tearDown(self):
if self.fp is not None:
self.fp.close()
config.pop('template config')
- shutil.rmtree(self.template_dir)
- shutil.rmtree(self.listdir)
-
- def test_find_global_template(self):
- filename, self.fp = find('global.txt', language='xx')
- self.assertEqual(filename, os.path.join(self.xxdir, 'global.txt'))
- self.assertEqual(self.fp.read(), 'Global template')
+ shutil.rmtree(self.var_dir)
def test_find_site_template(self):
filename, self.fp = find('site.txt', language='xx')
- self.assertEqual(filename, os.path.join(self.sitedir, 'site.txt'))
+ self.assertEqual(filename, self.xxsite)
self.assertEqual(self.fp.read(), 'Site template')
def test_find_domain_template(self):
filename, self.fp = find('domain.txt', self.mlist)
- self.assertEqual(filename, os.path.join(self.domaindir, 'domain.txt'))
+ self.assertEqual(filename, self.xxdomain)
self.assertEqual(self.fp.read(), 'Domain template')
def test_find_list_template(self):
filename, self.fp = find('list.txt', self.mlist)
- self.assertEqual(filename, os.path.join(self.listdir, 'list.txt'))
+ self.assertEqual(filename, self.xxlist)
self.assertEqual(self.fp.read(), 'List template')
def test_template_not_found(self):
@@ -219,41 +240,41 @@ class TestMake(unittest.TestCase):
layer = ConfigLayer
def setUp(self):
- self.template_dir = tempfile.mkdtemp()
+ self.var_dir = tempfile.mkdtemp()
config.push('template config', """\
[paths.testing]
- template_dir: {0}
- """.format(self.template_dir))
+ var_dir: {0}
+ """.format(self.var_dir))
# The following MUST happen AFTER the push() above since pushing a new
# config also clears out the language manager.
getUtility(ILanguageManager).add('xx', 'utf-8', 'Xlandia')
self.mlist = create_list('test@example.com')
self.mlist.preferred_language = 'xx'
- # Populate the template directory with some samples.
- self.xxdir = os.path.join(self.template_dir, 'xx')
- os.mkdir(self.xxdir)
- with open(os.path.join(self.xxdir, 'nosub.txt'), 'w') as fp:
- print >> fp, """\
+ # Populate the template directories with a few fake templates.
+ path = os.path.join(self.var_dir, 'templates', 'site', 'xx')
+ os.makedirs(path)
+ with open(os.path.join(path, 'nosub.txt'), 'w') as fp:
+ print("""\
This is a global template.
It has no substitutions.
It will be wrapped.
-"""
- with open(os.path.join(self.xxdir, 'subs.txt'), 'w') as fp:
- print >> fp, """\
+""", file=fp)
+ with open(os.path.join(path, 'subs.txt'), 'w') as fp:
+ print("""\
This is a $kind template.
It has $howmany substitutions.
It will be wrapped.
-"""
- with open(os.path.join(self.xxdir, 'nowrap.txt'), 'w') as fp:
- print >> fp, """\
+""", file=fp)
+ with open(os.path.join(path, 'nowrap.txt'), 'w') as fp:
+ print("""\
This is a $kind template.
It has $howmany substitutions.
It will not be wrapped.
-"""
+""", file=fp)
def tearDown(self):
config.pop('template config')
- shutil.rmtree(self.template_dir)
+ shutil.rmtree(self.var_dir)
def test_no_substitutions(self):
self.assertEqual(make('nosub.txt', self.mlist), """\