added Django/Jinja 2 compatibility layer and several bugfixes
This commit is contained in:
parent
2f205b38f7
commit
3e7af037c9
1
AUTHORS
1
AUTHORS
@ -1,3 +1,4 @@
|
||||
Christopher D. Leary <cdleary@gmail.com>
|
||||
Michael Elsdoerfer <michael@elsdoerfer.info>
|
||||
David Cramer <dcramer@gmail.com>
|
||||
Rick van Hattem <rick@fawo.nl>
|
||||
|
11
README.rst
11
README.rst
@ -133,13 +133,10 @@ templates anyway, it might be a good opportunity for this change.
|
||||
|
||||
(*) http://groups.google.com/group/django-developers/browse_thread/thread/f323338045ac2e5e
|
||||
|
||||
Jinja2's ``TemplateSyntaxError`` (and potentially other exception types)
|
||||
are not compatible with Django's own template exceptions with respect to
|
||||
the TEMPLATE_DEBUG facility. If TEMPLATE_DEBUG is enabled and Jinja2 raises
|
||||
an exception, Django's error 500 page will sometimes not be able to handle
|
||||
it and crash. The solution is to disable the TEMPLATE_DEBUG setting in
|
||||
Django. See http://code.djangoproject.com/ticket/10216 for further
|
||||
information.
|
||||
This version of coffin modifies Jinja 2's ``TemplateSyntaxError`` to be
|
||||
compatible with Django. So there is no need to disable ``TEMPLATE_DEBUG``.
|
||||
You can just keep `TEPMLATE_DEBUG=True`` in your settings to benefit from both
|
||||
Jinja 2 and Django's template debugging.
|
||||
|
||||
``coffin.template.loader`` is a port of ``django.template.loader`` and
|
||||
comes with a Jinja2-enabled version of ``get_template()``.
|
||||
|
@ -12,12 +12,19 @@ _JINJA_I18N_EXTENSION_NAME = 'jinja2.ext.i18n'
|
||||
|
||||
class CoffinEnvironment(Environment):
|
||||
def __init__(self, filters={}, globals={}, tests={}, loader=None, extensions=[], **kwargs):
|
||||
from django.conf import settings
|
||||
if not loader:
|
||||
loader = loaders.ChoiceLoader(self._get_loaders())
|
||||
all_ext = self._get_all_extensions()
|
||||
|
||||
extensions.extend(all_ext['extensions'])
|
||||
super(CoffinEnvironment, self).__init__(extensions=extensions, loader=loader, **kwargs)
|
||||
super(CoffinEnvironment, self).__init__(
|
||||
extensions=extensions,
|
||||
loader=loader,
|
||||
cache_size=-1,
|
||||
auto_reload=settings.DEBUG,
|
||||
**kwargs
|
||||
)
|
||||
self.filters.update(filters)
|
||||
self.filters.update(all_ext['filters'])
|
||||
self.globals.update(globals)
|
||||
@ -70,14 +77,16 @@ class CoffinEnvironment(Environment):
|
||||
pass
|
||||
else:
|
||||
for f in os.listdir(path):
|
||||
if f == '__init__.py':
|
||||
if f == '__init__.py' or f.startswith('.'):
|
||||
continue
|
||||
|
||||
if f.endswith('.py'):
|
||||
try:
|
||||
# TODO: will need updating when #6587 lands
|
||||
# libs.append(get_library(
|
||||
# "django.templatetags.%s" % os.path.splitext(f)[0]))
|
||||
libs.append(get_library(os.path.splitext(f)[0]))
|
||||
library = os.path.splitext(f)[0]
|
||||
libs.append(get_library(library))
|
||||
|
||||
except InvalidTemplateLibrary:
|
||||
pass
|
||||
@ -103,7 +112,7 @@ class CoffinEnvironment(Environment):
|
||||
# add the globally defined extension list
|
||||
extensions.extend(list(getattr(settings, 'JINJA2_EXTENSIONS', [])))
|
||||
|
||||
def from_setting(setting):
|
||||
def from_setting(setting, call=False):
|
||||
retval = {}
|
||||
setting = getattr(settings, setting, {})
|
||||
if isinstance(setting, dict):
|
||||
@ -113,10 +122,16 @@ class CoffinEnvironment(Environment):
|
||||
for value in setting:
|
||||
value = callable(value) and value or get_callable(value)
|
||||
retval[value.__name__] = value
|
||||
|
||||
if call:
|
||||
for k, v in retval.items():
|
||||
if callable(v):
|
||||
retval[k] = v()
|
||||
return retval
|
||||
|
||||
filters.update(from_setting('JINJA2_FILTERS'))
|
||||
globals.update(from_setting('JINJA2_GLOBALS'))
|
||||
globals.update(from_setting('JINJA2_CONSTANTS', True))
|
||||
tests.update(from_setting('JINJA2_TESTS'))
|
||||
|
||||
# add extensions defined in application's templatetag libraries
|
||||
|
@ -1,8 +1,14 @@
|
||||
from jinja2 import (
|
||||
exceptions as _jinja2_exceptions,
|
||||
environment as _jinja2_environment,
|
||||
)
|
||||
from django.template import (
|
||||
Context as DjangoContext,
|
||||
add_to_builtins as django_add_to_builtins,
|
||||
import_library)
|
||||
from jinja2 import Template as _Jinja2Template
|
||||
import_library,
|
||||
TemplateSyntaxError as DjangoTemplateSyntaxError,
|
||||
loader as django_loader,
|
||||
)
|
||||
|
||||
# Merge with ``django.template``.
|
||||
from django.template import __all__
|
||||
@ -12,8 +18,81 @@ from django.template import *
|
||||
from library import *
|
||||
|
||||
|
||||
class Template(_Jinja2Template):
|
||||
"""Fixes the incompabilites between Jinja2's template class and
|
||||
def _generate_django_exception(e, source=None):
|
||||
'''Generate a Django exception from a Jinja exception'''
|
||||
from django.views.debug import linebreak_iter
|
||||
import re
|
||||
|
||||
if source:
|
||||
exception = DjangoTemplateSyntaxError(e.message)
|
||||
exception_dict = e.__dict__
|
||||
del exception_dict['source']
|
||||
|
||||
# Fetch the entire template in a string
|
||||
template_string = source[0].reload()
|
||||
|
||||
# Get the line number from the error message, if available
|
||||
match = re.match('.* at (\d+)$', e.message)
|
||||
|
||||
start_index = 0
|
||||
stop_index = 0
|
||||
if match:
|
||||
# Convert the position found in the stacktrace to a position
|
||||
# the Django template debug system can use
|
||||
position = int(match.group(1)) + source[1][0] + 1
|
||||
|
||||
for index in linebreak_iter(template_string):
|
||||
if index >= position:
|
||||
stop_index = min(index, position + 3)
|
||||
start_index = min(index, position - 2)
|
||||
break
|
||||
start_index = index
|
||||
|
||||
else:
|
||||
# So there wasn't a matching error message, in that case we
|
||||
# simply have to highlight the entire line instead of the specific
|
||||
# words
|
||||
ignore_lines = -1
|
||||
for i, index in enumerate(linebreak_iter(template_string)):
|
||||
if source[1][0] > index:
|
||||
ignore_lines += 1
|
||||
|
||||
if i - ignore_lines == e.lineno:
|
||||
stop_index = index
|
||||
break
|
||||
|
||||
start_index = index
|
||||
|
||||
# Convert the positions to a source that is compatible with the
|
||||
# Django template debugger
|
||||
source = source[0], (
|
||||
start_index,
|
||||
stop_index,
|
||||
)
|
||||
else:
|
||||
# No source available so we let Django fetch it for us
|
||||
lineno = e.lineno - 1
|
||||
template_string, source = django_loader.find_template_source(e.name)
|
||||
exception = DjangoTemplateSyntaxError(e.message)
|
||||
|
||||
# Find the positions by the line number given in the exception
|
||||
start_index = 0
|
||||
for i in range(lineno):
|
||||
start_index = template_string.index('\n', start_index + 1)
|
||||
|
||||
source = source, (
|
||||
start_index + 1,
|
||||
template_string.index('\n', start_index + 1) + 1,
|
||||
)
|
||||
|
||||
# Set our custom source as source for the exception so the Django
|
||||
# template debugger can use it
|
||||
exception.source = source
|
||||
return exception
|
||||
|
||||
|
||||
class Template(_jinja2_environment.Template):
|
||||
'''Fixes the incompabilites between Jinja2's template class and
|
||||
Django's.
|
||||
|
||||
The end result should be a class that renders Jinja2 templates but
|
||||
@ -22,15 +101,24 @@ class Template(_Jinja2Template):
|
||||
This includes flattening a ``Context`` instance passed to render
|
||||
and making sure that this class will automatically use the global
|
||||
coffin environment.
|
||||
"""
|
||||
'''
|
||||
|
||||
def __new__(cls, template_string, origin=None, name=None):
|
||||
def __new__(cls, template_string, origin=None, name=None, source=None):
|
||||
# We accept the "origin" and "name" arguments, but discard them
|
||||
# right away - Jinja's Template class (apparently) stores no
|
||||
# equivalent information.
|
||||
|
||||
# source is expected to be a Django Template Loader source, it is not
|
||||
# required but helps to provide useful stacktraces when executing
|
||||
# Jinja code from Django templates
|
||||
from coffin.common import env
|
||||
|
||||
return env.from_string(template_string, template_class=cls)
|
||||
try:
|
||||
template = env.from_string(template_string, template_class=cls)
|
||||
template.source = source
|
||||
return template
|
||||
except _jinja2_exceptions.TemplateSyntaxError, e:
|
||||
raise _generate_django_exception(e, source)
|
||||
|
||||
def __iter__(self):
|
||||
# TODO: Django allows iterating over the templates nodes. Should
|
||||
@ -38,22 +126,49 @@ class Template(_Jinja2Template):
|
||||
raise NotImplementedError()
|
||||
|
||||
def render(self, context=None):
|
||||
"""Differs from Django's own render() slightly in that makes the
|
||||
'''Differs from Django's own render() slightly in that makes the
|
||||
``context`` parameter optional. We try to strike a middle ground
|
||||
here between implementing Django's interface while still supporting
|
||||
Jinja's own call syntax as well.
|
||||
"""
|
||||
if context is None:
|
||||
'''
|
||||
if not context:
|
||||
context = {}
|
||||
else:
|
||||
context = dict_from_django_context(context)
|
||||
assert isinstance(context, dict) # Required for **-operator.
|
||||
return super(Template, self).render(**context)
|
||||
|
||||
try:
|
||||
return super(Template, self).render(context)
|
||||
except _jinja2_exceptions.TemplateSyntaxError, e:
|
||||
raise _generate_django_exception(e)
|
||||
except _jinja2_exceptions.UndefinedError, e:
|
||||
# UndefinedErrors don't have a source attribute so we create one
|
||||
import sys
|
||||
import traceback
|
||||
exc_traceback = sys.exc_info()[-1]
|
||||
trace = traceback.extract_tb(exc_traceback)[-1]
|
||||
e.lineno = trace[1]
|
||||
source = None
|
||||
|
||||
# If we're getting <template> than we're being call from a memory
|
||||
# template, this occurs when we use the {% jinja %} template tag
|
||||
# In that case we use the Django source and find our position
|
||||
# within that
|
||||
if trace[0] == '<template>' and hasattr(self, 'source'):
|
||||
source = self.source
|
||||
e.name = source[0].name
|
||||
e.source = source
|
||||
else:
|
||||
e.name = trace[0]
|
||||
|
||||
# We have to cleanup the trace manually, Python does _not_ clean
|
||||
# it up for us!
|
||||
del exc_traceback, trace
|
||||
|
||||
raise _generate_django_exception(e, source)
|
||||
|
||||
|
||||
def dict_from_django_context(context):
|
||||
"""Flattens a Django :class:`django.template.context.Context` object.
|
||||
"""
|
||||
'''Flattens a Django :class:`django.template.context.Context` object.'''
|
||||
if not isinstance(context, DjangoContext):
|
||||
return context
|
||||
else:
|
||||
@ -69,7 +184,7 @@ builtins = []
|
||||
|
||||
|
||||
def add_to_builtins(module_name):
|
||||
"""Add the given module to both Coffin's list of default template
|
||||
'''Add the given module to both Coffin's list of default template
|
||||
libraries as well as Django's. This makes sense, since Coffin
|
||||
libs are compatible with Django libraries.
|
||||
|
||||
@ -84,10 +199,12 @@ def add_to_builtins(module_name):
|
||||
XXX/TODO: Why do we need our own custom list of builtins? Our
|
||||
Library object is compatible, remember!? We can just add them
|
||||
directly to Django's own list of builtins.
|
||||
"""
|
||||
'''
|
||||
builtins.append(import_library(module_name))
|
||||
django_add_to_builtins(module_name)
|
||||
|
||||
|
||||
add_to_builtins('coffin.template.defaulttags')
|
||||
add_to_builtins('coffin.template.defaultfilters')
|
||||
add_to_builtins('coffin.template.defaultfilters')
|
||||
add_to_builtins('coffin.template.interop')
|
||||
|
||||
|
@ -5,15 +5,17 @@ TODO: Most of the filters in here need to be updated for autoescaping.
|
||||
|
||||
from coffin.template import Library
|
||||
from jinja2.runtime import Undefined
|
||||
# from jinja2 import Markup
|
||||
from jinja2 import filters
|
||||
|
||||
register = Library()
|
||||
|
||||
@register.filter(jinja2_only=True)
|
||||
def url(view_name, *args, **kwargs):
|
||||
from coffin.template.defaulttags import url
|
||||
return url._reverse(view_name, args, kwargs)
|
||||
|
||||
register.filter(url, jinja2_only=True)
|
||||
register.object(url)
|
||||
|
||||
@register.filter(jinja2_only=True)
|
||||
def timesince(value, *arg):
|
||||
if value is None or isinstance(value, Undefined):
|
||||
@ -94,4 +96,9 @@ def floatformat(value, arg=-1):
|
||||
result = django_filter_to_jinja2(floatformat)(value, arg)
|
||||
if result == '': # django couldn't handle the value
|
||||
raise ValueError(value)
|
||||
return result
|
||||
return result
|
||||
|
||||
@register.filter(jinja2_only=True)
|
||||
def default(value, default_value=u'', boolean=True):
|
||||
return filters.do_default(value, default_value, boolean)
|
||||
|
||||
|
@ -86,7 +86,8 @@ class LoadExtension(Extension):
|
||||
#
|
||||
# TODO: Actually, there's ``nodes.EnvironmentAttribute``.
|
||||
#ae_setting = object.__new__(nodes.InternalName)
|
||||
#nodes.Node.__init__(ae_setting, 'environment.autoescape', lineno=lineno)
|
||||
#nodes.Node.__init__(ae_setting, 'environment.autoescape',
|
||||
lineno=lineno)
|
||||
#temp = parser.free_identifier()
|
||||
#body.insert(0, nodes.Assign(temp, ae_setting))
|
||||
#body.insert(1, nodes.Assign(ae_setting, nodes.Const(True)))
|
||||
@ -133,7 +134,7 @@ class URLExtension(Extension):
|
||||
bits = []
|
||||
name_allowed = True
|
||||
while True:
|
||||
if stream.current.test_any('dot', 'sub'):
|
||||
if stream.current.test_any('dot', 'sub', 'colon'):
|
||||
bits.append(stream.next())
|
||||
name_allowed = True
|
||||
elif stream.current.test('name') and name_allowed:
|
||||
@ -160,15 +161,18 @@ class URLExtension(Extension):
|
||||
else:
|
||||
args.append(parser.parse_expression())
|
||||
|
||||
make_call_node = lambda *kw: \
|
||||
self.call_method('_reverse',
|
||||
args=[viewname, nodes.List(args), nodes.Dict(kwargs)],
|
||||
kwargs=kw)
|
||||
def make_call_node(*kw):
|
||||
return self.call_method('_reverse', args=[
|
||||
viewname,
|
||||
nodes.List(args),
|
||||
nodes.Dict(kwargs),
|
||||
], kwargs=kw)
|
||||
|
||||
# if an as-clause is specified, write the result to context...
|
||||
if stream.next_if('name:as'):
|
||||
var = nodes.Name(stream.expect('name').value, 'store')
|
||||
call_node = make_call_node(nodes.Keyword('fail', nodes.Const(False)))
|
||||
call_node = make_call_node(nodes.Keyword('fail',
|
||||
nodes.Const(False)))
|
||||
return nodes.Assign(var, call_node)
|
||||
# ...otherwise print it out.
|
||||
else:
|
||||
@ -223,13 +227,13 @@ class WithExtension(Extension):
|
||||
parser.stream.expect('name:as')
|
||||
name = parser.stream.expect('name')
|
||||
body = parser.parse_statements(['name:endwith'], drop_needle=True)
|
||||
# Use a local variable instead of a macro argument to alias
|
||||
# Use a local variable instead of a macro argument to alias
|
||||
# the expression. This allows us to nest "with" statements.
|
||||
body.insert(0, nodes.Assign(nodes.Name(name.value, 'store'), value))
|
||||
return nodes.CallBlock(
|
||||
self.call_method('_render_block'), [], [], body).\
|
||||
set_lineno(lineno)
|
||||
|
||||
|
||||
def _render_block(self, caller=None):
|
||||
return caller()
|
||||
|
||||
@ -312,12 +316,12 @@ class CacheExtension(Extension):
|
||||
from django.core.cache import cache # delay depending in settings
|
||||
from django.utils.http import urlquote
|
||||
from django.utils.hashcompat import md5_constructor
|
||||
|
||||
|
||||
try:
|
||||
expire_time = int(expire_time)
|
||||
except (ValueError, TypeError):
|
||||
raise TemplateSyntaxError('"%s" tag got a non-integer '
|
||||
'timeout value: %r' % (list(self.tags)[0], expire_time), lineno)
|
||||
raise TemplateSyntaxError('"%s" tag got a non-integer timeout '
|
||||
'value: %r' % (list(self.tags)[0], expire_time), lineno)
|
||||
|
||||
args_string = u':'.join([urlquote(v) for v in vary_on])
|
||||
args_md5 = md5_constructor(args_string)
|
||||
@ -343,7 +347,7 @@ class SpacelessExtension(Extension):
|
||||
body = parser.parse_statements(['name:endspaceless'], drop_needle=True)
|
||||
return nodes.CallBlock(
|
||||
self.call_method('_strip_spaces', [], [], None, None),
|
||||
[], [], body
|
||||
[], [], body,
|
||||
).set_lineno(lineno)
|
||||
|
||||
def _strip_spaces(self, caller=None):
|
||||
@ -366,7 +370,7 @@ class CsrfTokenExtension(Extension):
|
||||
def parse(self, parser):
|
||||
lineno = parser.stream.next().lineno
|
||||
return nodes.Output([
|
||||
self.call_method('_render', [nodes.Name('csrf_token', 'load')])
|
||||
self.call_method('_render', [nodes.Name('csrf_token', 'load')]),
|
||||
]).set_lineno(lineno)
|
||||
|
||||
def _render(self, csrf_token):
|
||||
@ -382,11 +386,11 @@ cache = CacheExtension
|
||||
spaceless = SpacelessExtension
|
||||
csrf_token = CsrfTokenExtension
|
||||
|
||||
|
||||
register = Library()
|
||||
register.tag(load)
|
||||
register.tag(url)
|
||||
register.tag(with_)
|
||||
register.tag(cache)
|
||||
register.tag(spaceless)
|
||||
register.tag(csrf_token)
|
||||
register.tag(csrf_token)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
from django.template import Library as DjangoLibrary, InvalidTemplateLibrary
|
||||
from jinja2.ext import Extension as Jinja2Extension
|
||||
import types
|
||||
from coffin.interop import (
|
||||
DJANGO, JINJA2,
|
||||
guess_filter_type, jinja2_filter_to_django, django_filter_to_jinja2)
|
||||
@ -142,7 +143,8 @@ class Library(DjangoLibrary):
|
||||
return super(Library, self).tag(name_or_node, compile_function)
|
||||
|
||||
def tag_function(self, func_or_node):
|
||||
if issubclass(func_or_node, Jinja2Extension):
|
||||
if not isinstance(func_or_node, types.FunctionType) and \
|
||||
issubclass(func_or_node, Jinja2Extension):
|
||||
self.jinja2_extensions.append(func_or_node)
|
||||
return func_or_node
|
||||
else:
|
||||
|
@ -1,66 +1,66 @@
|
||||
"""Replacement for ``django.template.loader`` that uses Jinja 2.
|
||||
|
||||
The module provides a generic way to load templates from an arbitrary
|
||||
backend storage (e.g. filesystem, database).
|
||||
"""
|
||||
|
||||
from coffin.template import Template as CoffinTemplate
|
||||
from jinja2 import TemplateNotFound
|
||||
|
||||
|
||||
def find_template_source(name, dirs=None):
|
||||
# This is Django's most basic loading function through which
|
||||
# all template retrievals go. Not sure if Jinja 2 publishes
|
||||
# an equivalent, but no matter, it mostly for internal use
|
||||
# anyway - developers will want to start with
|
||||
# ``get_template()`` or ``get_template_from_string`` anyway.
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def get_template(template_name):
|
||||
# Jinja will handle this for us, and env also initializes
|
||||
# the loader backends the first time it is called.
|
||||
from coffin.common import env
|
||||
return env.get_template(template_name)
|
||||
|
||||
|
||||
def get_template_from_string(source):
|
||||
"""
|
||||
Does not support then ``name`` and ``origin`` parameters from
|
||||
the Django version.
|
||||
"""
|
||||
from coffin.common import env
|
||||
return env.from_string(source)
|
||||
|
||||
|
||||
def render_to_string(template_name, dictionary=None, context_instance=None):
|
||||
"""Loads the given ``template_name`` and renders it with the given
|
||||
dictionary as context. The ``template_name`` may be a string to load
|
||||
a single template using ``get_template``, or it may be a tuple to use
|
||||
``select_template`` to find one of the templates in the list.
|
||||
|
||||
``dictionary`` may also be Django ``Context`` object.
|
||||
|
||||
Returns a string.
|
||||
"""
|
||||
dictionary = dictionary or {}
|
||||
if isinstance(template_name, (list, tuple)):
|
||||
template = select_template(template_name)
|
||||
else:
|
||||
template = get_template(template_name)
|
||||
if context_instance:
|
||||
context_instance.update(dictionary)
|
||||
else:
|
||||
context_instance = dictionary
|
||||
return template.render(context_instance)
|
||||
|
||||
|
||||
def select_template(template_name_list):
|
||||
"Given a list of template names, returns the first that can be loaded."
|
||||
for template_name in template_name_list:
|
||||
try:
|
||||
return get_template(template_name)
|
||||
except TemplateNotFound:
|
||||
continue
|
||||
# If we get here, none of the templates could be loaded
|
||||
raise TemplateNotFound(', '.join(template_name_list))
|
||||
"""Replacement for ``django.template.loader`` that uses Jinja 2.
|
||||
|
||||
The module provides a generic way to load templates from an arbitrary
|
||||
backend storage (e.g. filesystem, database).
|
||||
"""
|
||||
|
||||
from coffin.template import Template as CoffinTemplate
|
||||
from jinja2 import TemplateNotFound
|
||||
|
||||
|
||||
def find_template_source(name, dirs=None):
|
||||
# This is Django's most basic loading function through which
|
||||
# all template retrievals go. Not sure if Jinja 2 publishes
|
||||
# an equivalent, but no matter, it mostly for internal use
|
||||
# anyway - developers will want to start with
|
||||
# ``get_template()`` or ``get_template_from_string`` anyway.
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def get_template(template_name):
|
||||
# Jinja will handle this for us, and env also initializes
|
||||
# the loader backends the first time it is called.
|
||||
from coffin.common import env
|
||||
return env.get_template(template_name)
|
||||
|
||||
|
||||
def get_template_from_string(source):
|
||||
"""
|
||||
Does not support then ``name`` and ``origin`` parameters from
|
||||
the Django version.
|
||||
"""
|
||||
from coffin.common import env
|
||||
return env.from_string(source)
|
||||
|
||||
|
||||
def render_to_string(template_name, dictionary=None, context_instance=None):
|
||||
"""Loads the given ``template_name`` and renders it with the given
|
||||
dictionary as context. The ``template_name`` may be a string to load
|
||||
a single template using ``get_template``, or it may be a tuple to use
|
||||
``select_template`` to find one of the templates in the list.
|
||||
|
||||
``dictionary`` may also be Django ``Context`` object.
|
||||
|
||||
Returns a string.
|
||||
"""
|
||||
dictionary = dictionary or {}
|
||||
if isinstance(template_name, (list, tuple)):
|
||||
template = select_template(template_name)
|
||||
else:
|
||||
template = get_template(template_name)
|
||||
if context_instance:
|
||||
context_instance.update(dictionary)
|
||||
else:
|
||||
context_instance = dictionary
|
||||
return template.render(context_instance)
|
||||
|
||||
|
||||
def select_template(template_name_list):
|
||||
"Given a list of template names, returns the first that can be loaded."
|
||||
for template_name in template_name_list:
|
||||
try:
|
||||
return get_template(template_name)
|
||||
except TemplateNotFound:
|
||||
continue
|
||||
# If we get here, none of the templates could be loaded
|
||||
raise TemplateNotFound(', '.join(template_name_list))
|
||||
|
Loading…
Reference in New Issue
Block a user