include tags are now able to select between multiple templates
and take the first that exists, if a list of templates is
given.
--HG--
branch : trunk
diff --git a/jinja2/__init__.py b/jinja2/__init__.py
index b7a14f7..41dd615 100644
--- a/jinja2/__init__.py
+++ b/jinja2/__init__.py
@@ -49,7 +49,8 @@
# exceptions
from jinja2.exceptions import TemplateError, UndefinedError, \
- TemplateNotFound, TemplateSyntaxError, TemplateAssertionError
+ TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \
+ TemplateAssertionError
# decorators and public utilities
from jinja2.filters import environmentfilter, contextfilter
@@ -62,7 +63,7 @@
'ChoiceLoader', 'BytecodeCache', 'FileSystemBytecodeCache',
'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined',
'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
- 'TemplateSyntaxError', 'TemplateAssertionError', 'environmentfilter',
- 'contextfilter', 'Markup', 'escape', 'environmentfunction',
- 'contextfunction', 'clear_caches', 'is_undefined'
+ 'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
+ 'environmentfilter', 'contextfilter', 'Markup', 'escape',
+ 'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined'
]
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index 523b7bf..36d829a 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -890,7 +890,17 @@
if node.ignore_missing:
self.writeline('try:')
self.indent()
- self.writeline('template = environment.get_template(', node)
+
+ func_name = 'get_or_select_template'
+ if isinstance(node.template, nodes.Const):
+ if isinstance(node.template.value, basestring):
+ func_name = 'get_template'
+ elif isinstance(node.template.value, (tuple, list)):
+ func_name = 'select_template'
+ elif isinstance(node.template, (nodes.Tuple, nodes.List)):
+ func_name = 'select_template'
+
+ self.writeline('template = environment.%s(' % func_name, node)
self.visit(node.template, frame)
self.write(', %r)' % self.name)
if node.ignore_missing:
diff --git a/jinja2/environment.py b/jinja2/environment.py
index a08aad2..d5a4515 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -16,7 +16,8 @@
from jinja2.optimizer import optimize
from jinja2.compiler import generate
from jinja2.runtime import Undefined, new_context
-from jinja2.exceptions import TemplateSyntaxError
+from jinja2.exceptions import TemplateSyntaxError, TemplateNotFound, \
+ TemplatesNotFound
from jinja2.utils import import_string, LRUCache, Markup, missing, \
concat, consume, internalcode
@@ -526,6 +527,20 @@
return template
@internalcode
+ def _load_template(self, name, globals):
+ if self.loader is None:
+ raise TypeError('no loader for this environment specified')
+ if self.cache is not None:
+ template = self.cache.get(name)
+ if template is not None and (not self.auto_reload or \
+ template.is_up_to_date):
+ return template
+ template = self.loader.load(self, name, globals)
+ if self.cache is not None:
+ self.cache[name] = template
+ return template
+
+ @internalcode
def get_template(self, name, parent=None, globals=None):
"""Load a template from the loader. If a loader is configured this
method ask the loader for the template and returns a :class:`Template`.
@@ -538,21 +553,43 @@
If the template does not exist a :exc:`TemplateNotFound` exception is
raised.
"""
- if self.loader is None:
- raise TypeError('no loader for this environment specified')
if parent is not None:
name = self.join_path(name, parent)
+ return self._load_template(name, self.make_globals(globals))
- if self.cache is not None:
- template = self.cache.get(name)
- if template is not None and (not self.auto_reload or \
- template.is_up_to_date):
- return template
+ @internalcode
+ def select_template(self, names, parent=None, globals=None):
+ """Works like :meth:`get_template` but tries a number of templates
+ before it fails. If it cannot find any of the templates, it will
+ raise a :exc:`TemplatesNotFound` exception.
- template = self.loader.load(self, name, self.make_globals(globals))
- if self.cache is not None:
- self.cache[name] = template
- return template
+ .. versionadded:: 2.2
+ """
+ if not names:
+ raise TemplatesNotFound(message=u'Tried to select from an empty list '
+ u'of templates.')
+ globals = self.make_globals(globals)
+ for name in names:
+ if parent is not None:
+ name = self.join_path(name, parent)
+ try:
+ return self._load_template(name, globals)
+ except TemplateNotFound:
+ pass
+ raise TemplatesNotFound(names)
+
+ @internalcode
+ def get_or_select_template(self, template_name_or_list,
+ parent=None, globals=None):
+ """
+ Does a typecheck and dispatches to :meth:`select_template` if an
+ iterable of template names is given, otherwise to :meth:`get_template`.
+
+ .. versionadded:: 2.2
+ """
+ if isinstance(template_name_or_list, basestring):
+ return self.get_template(template_name_or_list, parent, globals)
+ return self.select_template(template_name_or_list, parent, globals)
def from_string(self, source, globals=None, template_class=None):
"""Load a template from a string. This parses the source given and
diff --git a/jinja2/exceptions.py b/jinja2/exceptions.py
index 182c061..96194a9 100644
--- a/jinja2/exceptions.py
+++ b/jinja2/exceptions.py
@@ -29,9 +29,39 @@
class TemplateNotFound(IOError, LookupError, TemplateError):
"""Raised if a template does not exist."""
- def __init__(self, name):
- IOError.__init__(self, name)
+ # looks weird, but removes the warning descriptor that just
+ # bogusly warns us about message being deprecated
+ message = None
+
+ def __init__(self, name, message=None):
+ IOError.__init__(self)
+ if message is None:
+ message = name
+ self.message = message
self.name = name
+ self.templates = [name]
+
+ def __unicode__(self):
+ return self.message
+
+ def __str__(self):
+ return self.message.encode('utf-8')
+
+
+class TemplatesNotFound(TemplateNotFound):
+ """Like :class:`TemplateNotFound` but raised if multiple templates
+ are selected. This is a subclass of :class:`TemplateNotFound`
+ exception, so just catching the base exception will catch both.
+
+ .. versionadded:: 2.2
+ """
+
+ def __init__(self, names=(), message=None):
+ if message is None:
+ message = u'non of the templates given were found: ' + \
+ u', '.join(names)
+ TemplateNotFound.__init__(self, names and names[-1] or None, message)
+ self.templates = list(names)
class TemplateSyntaxError(TemplateError):
diff --git a/jinja2/meta.py b/jinja2/meta.py
index 2da3ebb..67113ab 100644
--- a/jinja2/meta.py
+++ b/jinja2/meta.py
@@ -70,8 +70,33 @@
"""
for node in ast.find_all((nodes.Extends, nodes.FromImport, nodes.Import,
nodes.Include)):
- if isinstance(node.template, nodes.Const) and \
- isinstance(node.template.value, basestring):
+ if not isinstance(node.template, nodes.Const):
+ # a tuple with some non consts in there
+ if isinstance(node.template, (nodes.Tuple, nodes.List)):
+ for template_name in node.template.items:
+ # something const, only yield the strings and ignore
+ # non-string consts that really just make no sense
+ if isinstance(template_name, nodes.Const):
+ if isinstance(template_name.value, basestring):
+ yield template_name.value
+ # something dynamic in there
+ else:
+ yield None
+ # something dynamic we don't know about here
+ else:
+ yield None
+ continue
+ # constant is a basestring, direct template name
+ if isinstance(node.template.value, basestring):
yield node.template.value
+ # a tuple or list (latter *should* not happen) made of consts,
+ # yield the consts that are strings. We could warn here for
+ # non string values
+ elif isinstance(node, nodes.Include) and \
+ isinstance(node.template.value, (tuple, list)):
+ for template_name in node.template.value:
+ if isinstance(template_name, basestring):
+ yield template_name
+ # something else we don't care about, we could warn here
else:
yield None