summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman/utilities/i18n.py128
-rw-r--r--src/mailman/utilities/tests/test_templates.py141
2 files changed, 254 insertions, 15 deletions
diff --git a/src/mailman/utilities/i18n.py b/src/mailman/utilities/i18n.py
index 45262997b..a0f49a436 100644
--- a/src/mailman/utilities/i18n.py
+++ b/src/mailman/utilities/i18n.py
@@ -21,14 +21,138 @@ from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
+ 'TemplateNotFoundError',
'find',
'make',
]
+import os
+import errno
+
+from itertools import product
+
+from mailman.config import config
+from mailman.core.constants import system_preferences
+
+
-def find():
- pass
+class TemplateNotFoundError(Exception):
+ """The named template was not found."""
+
+ def __init__(self, template_file):
+ self.template_file = template_file
+
+
+
+def _search(template_file, mailing_list=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.host_name))
+ 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)
+
+
+
+def find(template_file, mailing_list=None, language=None):
+ """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 list-specific language directory
+ <var_dir>/lists/<fqdn_listname>/<language>
+
+ * The domain-specific language directory
+ <template_dir>/<list-host-name>/<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.
+
+ The <language> path component is calculated as follows, in this order:
+
+ * The `language` parameter if given
+ * `mailing_list.preferred_language` if given
+ * The server's default language
+ * English ('en')
+
+ Languages are iterated after each of the four locations are searched. So
+ for example, when searching for the 'foo.txt' template, where the server's
+ default language is 'fr', the mailing list's (test@example.com) language
+ 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>/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>/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>/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
+
+ :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
+ 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`
+ :param language: Optional language code, which influences the search.
+ :type language: string
+ :return: A tuple of the file system path to the first matching template,
+ and an open file object allowing reading of the file.
+ :rtype: (string, file)
+ :raises TemplateNotFoundError: when the template could not be found.
+ """
+ raw_search_order = _search(template_file, mailing_list, language)
+ for path in raw_search_order:
+ try:
+ fp = open(path)
+ except IOError as error:
+ if error.errno != errno.ENOENT:
+ raise
+ else:
+ return path, fp
+ raise TemplateNotFoundError(template_file)
def make():
diff --git a/src/mailman/utilities/tests/test_templates.py b/src/mailman/utilities/tests/test_templates.py
index 446595108..23f99b60e 100644
--- a/src/mailman/utilities/tests/test_templates.py
+++ b/src/mailman/utilities/tests/test_templates.py
@@ -36,13 +36,116 @@ 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 find, make
+from mailman.utilities.i18n import TemplateNotFoundError, _search, find, make
from mailman.Utils import findtext
+class TestSearchOrder(unittest.TestCase):
+ """Test internal search order for language templates."""
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ self.template_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))
+ language_manager = getUtility(ILanguageManager)
+ language_manager.add('de', 'utf-8', 'German')
+ language_manager.add('it', 'utf-8', 'Italian')
+ self.mlist = create_list('l@example.com')
+ self.mlist.preferred_language = 'de'
+
+ def tearDown(self):
+ config.pop('no template dir')
+ shutil.rmtree(self.template_dir)
+
+ def _stripped_search_order(self, template_file,
+ mailing_list=None, language=None):
+ raw_search_order = _search(template_file, mailing_list, language)
+ for path in raw_search_order:
+ yield path[len(self.template_dir):]
+
+ 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')
+
+ 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')
+
+ 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')
+
+ 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')
+
+
+
class TestFind(unittest.TestCase):
+ """Test template search."""
+
layer = ConfigLayer
def setUp(self):
@@ -56,52 +159,64 @@ class TestFind(unittest.TestCase):
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:
- print >> fp, 'Global template'
+ 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:
- print >> fp, 'Site template'
+ 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:
- print >> fp, 'Domain template'
+ 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:
- print >> fp, 'List template'
+ fp.write('List template')
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):
- text, filename = findtext('global.txt', lang='xx')
- self.assertEqual(text, 'Global template\n')
+ 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')
def test_find_site_template(self):
- text, filename = findtext('site.txt', lang='xx')
- self.assertEqual(text, 'Site template\n')
+ filename, self.fp = find('site.txt', language='xx')
self.assertEqual(filename, os.path.join(self.sitedir, 'site.txt'))
+ self.assertEqual(self.fp.read(), 'Site template')
def test_find_domain_template(self):
- text, filename = findtext('domain.txt', mlist=self.mlist)
- self.assertEqual(text, 'Domain template\n')
+ filename, self.fp = find('domain.txt', self.mlist)
self.assertEqual(filename, os.path.join(self.domaindir, 'domain.txt'))
+ self.assertEqual(self.fp.read(), 'Domain template')
def test_find_list_template(self):
- text, filename = findtext('list.txt', mlist=self.mlist)
- self.assertEqual(text, 'List template\n')
+ filename, self.fp = find('list.txt', self.mlist)
self.assertEqual(filename, os.path.join(self.listdir, 'list.txt'))
+ self.assertEqual(self.fp.read(), 'List template')
+ def test_template_not_found(self):
+ # Python 2.6 compatibility.
+ try:
+ find('missing.txt', self.mlist)
+ except TemplateNotFoundError as error:
+ self.assertEqual(error.template_file, 'missing.txt')
+ else:
+ raise AssertionError('TemplateNotFoundError expected')
def test_suite():
suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(TestSearchOrder))
suite.addTest(unittest.makeSuite(TestFind))
return suite