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>
|
Christopher D. Leary <cdleary@gmail.com>
|
||||||
Michael Elsdoerfer <michael@elsdoerfer.info>
|
Michael Elsdoerfer <michael@elsdoerfer.info>
|
||||||
David Cramer <dcramer@gmail.com>
|
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
|
(*) http://groups.google.com/group/django-developers/browse_thread/thread/f323338045ac2e5e
|
||||||
|
|
||||||
Jinja2's ``TemplateSyntaxError`` (and potentially other exception types)
|
This version of coffin modifies Jinja 2's ``TemplateSyntaxError`` to be
|
||||||
are not compatible with Django's own template exceptions with respect to
|
compatible with Django. So there is no need to disable ``TEMPLATE_DEBUG``.
|
||||||
the TEMPLATE_DEBUG facility. If TEMPLATE_DEBUG is enabled and Jinja2 raises
|
You can just keep `TEPMLATE_DEBUG=True`` in your settings to benefit from both
|
||||||
an exception, Django's error 500 page will sometimes not be able to handle
|
Jinja 2 and Django's template debugging.
|
||||||
it and crash. The solution is to disable the TEMPLATE_DEBUG setting in
|
|
||||||
Django. See http://code.djangoproject.com/ticket/10216 for further
|
|
||||||
information.
|
|
||||||
|
|
||||||
``coffin.template.loader`` is a port of ``django.template.loader`` and
|
``coffin.template.loader`` is a port of ``django.template.loader`` and
|
||||||
comes with a Jinja2-enabled version of ``get_template()``.
|
comes with a Jinja2-enabled version of ``get_template()``.
|
||||||
|
@ -12,12 +12,19 @@ _JINJA_I18N_EXTENSION_NAME = 'jinja2.ext.i18n'
|
|||||||
|
|
||||||
class CoffinEnvironment(Environment):
|
class CoffinEnvironment(Environment):
|
||||||
def __init__(self, filters={}, globals={}, tests={}, loader=None, extensions=[], **kwargs):
|
def __init__(self, filters={}, globals={}, tests={}, loader=None, extensions=[], **kwargs):
|
||||||
|
from django.conf import settings
|
||||||
if not loader:
|
if not loader:
|
||||||
loader = loaders.ChoiceLoader(self._get_loaders())
|
loader = loaders.ChoiceLoader(self._get_loaders())
|
||||||
all_ext = self._get_all_extensions()
|
all_ext = self._get_all_extensions()
|
||||||
|
|
||||||
extensions.extend(all_ext['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(filters)
|
||||||
self.filters.update(all_ext['filters'])
|
self.filters.update(all_ext['filters'])
|
||||||
self.globals.update(globals)
|
self.globals.update(globals)
|
||||||
@ -70,14 +77,16 @@ class CoffinEnvironment(Environment):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
for f in os.listdir(path):
|
for f in os.listdir(path):
|
||||||
if f == '__init__.py':
|
if f == '__init__.py' or f.startswith('.'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if f.endswith('.py'):
|
if f.endswith('.py'):
|
||||||
try:
|
try:
|
||||||
# TODO: will need updating when #6587 lands
|
# TODO: will need updating when #6587 lands
|
||||||
# libs.append(get_library(
|
# libs.append(get_library(
|
||||||
# "django.templatetags.%s" % os.path.splitext(f)[0]))
|
# "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:
|
except InvalidTemplateLibrary:
|
||||||
pass
|
pass
|
||||||
@ -103,7 +112,7 @@ class CoffinEnvironment(Environment):
|
|||||||
# add the globally defined extension list
|
# add the globally defined extension list
|
||||||
extensions.extend(list(getattr(settings, 'JINJA2_EXTENSIONS', [])))
|
extensions.extend(list(getattr(settings, 'JINJA2_EXTENSIONS', [])))
|
||||||
|
|
||||||
def from_setting(setting):
|
def from_setting(setting, call=False):
|
||||||
retval = {}
|
retval = {}
|
||||||
setting = getattr(settings, setting, {})
|
setting = getattr(settings, setting, {})
|
||||||
if isinstance(setting, dict):
|
if isinstance(setting, dict):
|
||||||
@ -113,10 +122,16 @@ class CoffinEnvironment(Environment):
|
|||||||
for value in setting:
|
for value in setting:
|
||||||
value = callable(value) and value or get_callable(value)
|
value = callable(value) and value or get_callable(value)
|
||||||
retval[value.__name__] = value
|
retval[value.__name__] = value
|
||||||
|
|
||||||
|
if call:
|
||||||
|
for k, v in retval.items():
|
||||||
|
if callable(v):
|
||||||
|
retval[k] = v()
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
filters.update(from_setting('JINJA2_FILTERS'))
|
filters.update(from_setting('JINJA2_FILTERS'))
|
||||||
globals.update(from_setting('JINJA2_GLOBALS'))
|
globals.update(from_setting('JINJA2_GLOBALS'))
|
||||||
|
globals.update(from_setting('JINJA2_CONSTANTS', True))
|
||||||
tests.update(from_setting('JINJA2_TESTS'))
|
tests.update(from_setting('JINJA2_TESTS'))
|
||||||
|
|
||||||
# add extensions defined in application's templatetag libraries
|
# 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 (
|
from django.template import (
|
||||||
Context as DjangoContext,
|
Context as DjangoContext,
|
||||||
add_to_builtins as django_add_to_builtins,
|
add_to_builtins as django_add_to_builtins,
|
||||||
import_library)
|
import_library,
|
||||||
from jinja2 import Template as _Jinja2Template
|
TemplateSyntaxError as DjangoTemplateSyntaxError,
|
||||||
|
loader as django_loader,
|
||||||
|
)
|
||||||
|
|
||||||
# Merge with ``django.template``.
|
# Merge with ``django.template``.
|
||||||
from django.template import __all__
|
from django.template import __all__
|
||||||
@ -12,8 +18,81 @@ from django.template import *
|
|||||||
from library import *
|
from library import *
|
||||||
|
|
||||||
|
|
||||||
class Template(_Jinja2Template):
|
def _generate_django_exception(e, source=None):
|
||||||
"""Fixes the incompabilites between Jinja2's template class and
|
'''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.
|
Django's.
|
||||||
|
|
||||||
The end result should be a class that renders Jinja2 templates but
|
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
|
This includes flattening a ``Context`` instance passed to render
|
||||||
and making sure that this class will automatically use the global
|
and making sure that this class will automatically use the global
|
||||||
coffin environment.
|
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
|
# We accept the "origin" and "name" arguments, but discard them
|
||||||
# right away - Jinja's Template class (apparently) stores no
|
# right away - Jinja's Template class (apparently) stores no
|
||||||
# equivalent information.
|
# 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
|
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):
|
def __iter__(self):
|
||||||
# TODO: Django allows iterating over the templates nodes. Should
|
# TODO: Django allows iterating over the templates nodes. Should
|
||||||
@ -38,22 +126,49 @@ class Template(_Jinja2Template):
|
|||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def render(self, context=None):
|
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
|
``context`` parameter optional. We try to strike a middle ground
|
||||||
here between implementing Django's interface while still supporting
|
here between implementing Django's interface while still supporting
|
||||||
Jinja's own call syntax as well.
|
Jinja's own call syntax as well.
|
||||||
"""
|
'''
|
||||||
if context is None:
|
if not context:
|
||||||
context = {}
|
context = {}
|
||||||
else:
|
else:
|
||||||
context = dict_from_django_context(context)
|
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):
|
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):
|
if not isinstance(context, DjangoContext):
|
||||||
return context
|
return context
|
||||||
else:
|
else:
|
||||||
@ -69,7 +184,7 @@ builtins = []
|
|||||||
|
|
||||||
|
|
||||||
def add_to_builtins(module_name):
|
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
|
libraries as well as Django's. This makes sense, since Coffin
|
||||||
libs are compatible with Django libraries.
|
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
|
XXX/TODO: Why do we need our own custom list of builtins? Our
|
||||||
Library object is compatible, remember!? We can just add them
|
Library object is compatible, remember!? We can just add them
|
||||||
directly to Django's own list of builtins.
|
directly to Django's own list of builtins.
|
||||||
"""
|
'''
|
||||||
builtins.append(import_library(module_name))
|
builtins.append(import_library(module_name))
|
||||||
django_add_to_builtins(module_name)
|
django_add_to_builtins(module_name)
|
||||||
|
|
||||||
|
|
||||||
add_to_builtins('coffin.template.defaulttags')
|
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 coffin.template import Library
|
||||||
from jinja2.runtime import Undefined
|
from jinja2.runtime import Undefined
|
||||||
# from jinja2 import Markup
|
from jinja2 import filters
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
@register.filter(jinja2_only=True)
|
|
||||||
def url(view_name, *args, **kwargs):
|
def url(view_name, *args, **kwargs):
|
||||||
from coffin.template.defaulttags import url
|
from coffin.template.defaulttags import url
|
||||||
return url._reverse(view_name, args, kwargs)
|
return url._reverse(view_name, args, kwargs)
|
||||||
|
|
||||||
|
register.filter(url, jinja2_only=True)
|
||||||
|
register.object(url)
|
||||||
|
|
||||||
@register.filter(jinja2_only=True)
|
@register.filter(jinja2_only=True)
|
||||||
def timesince(value, *arg):
|
def timesince(value, *arg):
|
||||||
if value is None or isinstance(value, Undefined):
|
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)
|
result = django_filter_to_jinja2(floatformat)(value, arg)
|
||||||
if result == '': # django couldn't handle the value
|
if result == '': # django couldn't handle the value
|
||||||
raise ValueError(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``.
|
# TODO: Actually, there's ``nodes.EnvironmentAttribute``.
|
||||||
#ae_setting = object.__new__(nodes.InternalName)
|
#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()
|
#temp = parser.free_identifier()
|
||||||
#body.insert(0, nodes.Assign(temp, ae_setting))
|
#body.insert(0, nodes.Assign(temp, ae_setting))
|
||||||
#body.insert(1, nodes.Assign(ae_setting, nodes.Const(True)))
|
#body.insert(1, nodes.Assign(ae_setting, nodes.Const(True)))
|
||||||
@ -133,7 +134,7 @@ class URLExtension(Extension):
|
|||||||
bits = []
|
bits = []
|
||||||
name_allowed = True
|
name_allowed = True
|
||||||
while True:
|
while True:
|
||||||
if stream.current.test_any('dot', 'sub'):
|
if stream.current.test_any('dot', 'sub', 'colon'):
|
||||||
bits.append(stream.next())
|
bits.append(stream.next())
|
||||||
name_allowed = True
|
name_allowed = True
|
||||||
elif stream.current.test('name') and name_allowed:
|
elif stream.current.test('name') and name_allowed:
|
||||||
@ -160,15 +161,18 @@ class URLExtension(Extension):
|
|||||||
else:
|
else:
|
||||||
args.append(parser.parse_expression())
|
args.append(parser.parse_expression())
|
||||||
|
|
||||||
make_call_node = lambda *kw: \
|
def make_call_node(*kw):
|
||||||
self.call_method('_reverse',
|
return self.call_method('_reverse', args=[
|
||||||
args=[viewname, nodes.List(args), nodes.Dict(kwargs)],
|
viewname,
|
||||||
kwargs=kw)
|
nodes.List(args),
|
||||||
|
nodes.Dict(kwargs),
|
||||||
|
], kwargs=kw)
|
||||||
|
|
||||||
# if an as-clause is specified, write the result to context...
|
# if an as-clause is specified, write the result to context...
|
||||||
if stream.next_if('name:as'):
|
if stream.next_if('name:as'):
|
||||||
var = nodes.Name(stream.expect('name').value, 'store')
|
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)
|
return nodes.Assign(var, call_node)
|
||||||
# ...otherwise print it out.
|
# ...otherwise print it out.
|
||||||
else:
|
else:
|
||||||
@ -223,13 +227,13 @@ class WithExtension(Extension):
|
|||||||
parser.stream.expect('name:as')
|
parser.stream.expect('name:as')
|
||||||
name = parser.stream.expect('name')
|
name = parser.stream.expect('name')
|
||||||
body = parser.parse_statements(['name:endwith'], drop_needle=True)
|
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.
|
# the expression. This allows us to nest "with" statements.
|
||||||
body.insert(0, nodes.Assign(nodes.Name(name.value, 'store'), value))
|
body.insert(0, nodes.Assign(nodes.Name(name.value, 'store'), value))
|
||||||
return nodes.CallBlock(
|
return nodes.CallBlock(
|
||||||
self.call_method('_render_block'), [], [], body).\
|
self.call_method('_render_block'), [], [], body).\
|
||||||
set_lineno(lineno)
|
set_lineno(lineno)
|
||||||
|
|
||||||
def _render_block(self, caller=None):
|
def _render_block(self, caller=None):
|
||||||
return caller()
|
return caller()
|
||||||
|
|
||||||
@ -312,12 +316,12 @@ class CacheExtension(Extension):
|
|||||||
from django.core.cache import cache # delay depending in settings
|
from django.core.cache import cache # delay depending in settings
|
||||||
from django.utils.http import urlquote
|
from django.utils.http import urlquote
|
||||||
from django.utils.hashcompat import md5_constructor
|
from django.utils.hashcompat import md5_constructor
|
||||||
|
|
||||||
try:
|
try:
|
||||||
expire_time = int(expire_time)
|
expire_time = int(expire_time)
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
raise TemplateSyntaxError('"%s" tag got a non-integer '
|
raise TemplateSyntaxError('"%s" tag got a non-integer timeout '
|
||||||
'timeout value: %r' % (list(self.tags)[0], expire_time), lineno)
|
'value: %r' % (list(self.tags)[0], expire_time), lineno)
|
||||||
|
|
||||||
args_string = u':'.join([urlquote(v) for v in vary_on])
|
args_string = u':'.join([urlquote(v) for v in vary_on])
|
||||||
args_md5 = md5_constructor(args_string)
|
args_md5 = md5_constructor(args_string)
|
||||||
@ -343,7 +347,7 @@ class SpacelessExtension(Extension):
|
|||||||
body = parser.parse_statements(['name:endspaceless'], drop_needle=True)
|
body = parser.parse_statements(['name:endspaceless'], drop_needle=True)
|
||||||
return nodes.CallBlock(
|
return nodes.CallBlock(
|
||||||
self.call_method('_strip_spaces', [], [], None, None),
|
self.call_method('_strip_spaces', [], [], None, None),
|
||||||
[], [], body
|
[], [], body,
|
||||||
).set_lineno(lineno)
|
).set_lineno(lineno)
|
||||||
|
|
||||||
def _strip_spaces(self, caller=None):
|
def _strip_spaces(self, caller=None):
|
||||||
@ -366,7 +370,7 @@ class CsrfTokenExtension(Extension):
|
|||||||
def parse(self, parser):
|
def parse(self, parser):
|
||||||
lineno = parser.stream.next().lineno
|
lineno = parser.stream.next().lineno
|
||||||
return nodes.Output([
|
return nodes.Output([
|
||||||
self.call_method('_render', [nodes.Name('csrf_token', 'load')])
|
self.call_method('_render', [nodes.Name('csrf_token', 'load')]),
|
||||||
]).set_lineno(lineno)
|
]).set_lineno(lineno)
|
||||||
|
|
||||||
def _render(self, csrf_token):
|
def _render(self, csrf_token):
|
||||||
@ -382,11 +386,11 @@ cache = CacheExtension
|
|||||||
spaceless = SpacelessExtension
|
spaceless = SpacelessExtension
|
||||||
csrf_token = CsrfTokenExtension
|
csrf_token = CsrfTokenExtension
|
||||||
|
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
register.tag(load)
|
register.tag(load)
|
||||||
register.tag(url)
|
register.tag(url)
|
||||||
register.tag(with_)
|
register.tag(with_)
|
||||||
register.tag(cache)
|
register.tag(cache)
|
||||||
register.tag(spaceless)
|
register.tag(spaceless)
|
||||||
register.tag(csrf_token)
|
register.tag(csrf_token)
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from django.template import Library as DjangoLibrary, InvalidTemplateLibrary
|
from django.template import Library as DjangoLibrary, InvalidTemplateLibrary
|
||||||
from jinja2.ext import Extension as Jinja2Extension
|
from jinja2.ext import Extension as Jinja2Extension
|
||||||
|
import types
|
||||||
from coffin.interop import (
|
from coffin.interop import (
|
||||||
DJANGO, JINJA2,
|
DJANGO, JINJA2,
|
||||||
guess_filter_type, jinja2_filter_to_django, django_filter_to_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)
|
return super(Library, self).tag(name_or_node, compile_function)
|
||||||
|
|
||||||
def tag_function(self, func_or_node):
|
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)
|
self.jinja2_extensions.append(func_or_node)
|
||||||
return func_or_node
|
return func_or_node
|
||||||
else:
|
else:
|
||||||
|
@ -1,66 +1,66 @@
|
|||||||
"""Replacement for ``django.template.loader`` that uses Jinja 2.
|
"""Replacement for ``django.template.loader`` that uses Jinja 2.
|
||||||
|
|
||||||
The module provides a generic way to load templates from an arbitrary
|
The module provides a generic way to load templates from an arbitrary
|
||||||
backend storage (e.g. filesystem, database).
|
backend storage (e.g. filesystem, database).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from coffin.template import Template as CoffinTemplate
|
from coffin.template import Template as CoffinTemplate
|
||||||
from jinja2 import TemplateNotFound
|
from jinja2 import TemplateNotFound
|
||||||
|
|
||||||
|
|
||||||
def find_template_source(name, dirs=None):
|
def find_template_source(name, dirs=None):
|
||||||
# This is Django's most basic loading function through which
|
# This is Django's most basic loading function through which
|
||||||
# all template retrievals go. Not sure if Jinja 2 publishes
|
# all template retrievals go. Not sure if Jinja 2 publishes
|
||||||
# an equivalent, but no matter, it mostly for internal use
|
# an equivalent, but no matter, it mostly for internal use
|
||||||
# anyway - developers will want to start with
|
# anyway - developers will want to start with
|
||||||
# ``get_template()`` or ``get_template_from_string`` anyway.
|
# ``get_template()`` or ``get_template_from_string`` anyway.
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
def get_template(template_name):
|
def get_template(template_name):
|
||||||
# Jinja will handle this for us, and env also initializes
|
# Jinja will handle this for us, and env also initializes
|
||||||
# the loader backends the first time it is called.
|
# the loader backends the first time it is called.
|
||||||
from coffin.common import env
|
from coffin.common import env
|
||||||
return env.get_template(template_name)
|
return env.get_template(template_name)
|
||||||
|
|
||||||
|
|
||||||
def get_template_from_string(source):
|
def get_template_from_string(source):
|
||||||
"""
|
"""
|
||||||
Does not support then ``name`` and ``origin`` parameters from
|
Does not support then ``name`` and ``origin`` parameters from
|
||||||
the Django version.
|
the Django version.
|
||||||
"""
|
"""
|
||||||
from coffin.common import env
|
from coffin.common import env
|
||||||
return env.from_string(source)
|
return env.from_string(source)
|
||||||
|
|
||||||
|
|
||||||
def render_to_string(template_name, dictionary=None, context_instance=None):
|
def render_to_string(template_name, dictionary=None, context_instance=None):
|
||||||
"""Loads the given ``template_name`` and renders it with the given
|
"""Loads the given ``template_name`` and renders it with the given
|
||||||
dictionary as context. The ``template_name`` may be a string to load
|
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
|
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.
|
``select_template`` to find one of the templates in the list.
|
||||||
|
|
||||||
``dictionary`` may also be Django ``Context`` object.
|
``dictionary`` may also be Django ``Context`` object.
|
||||||
|
|
||||||
Returns a string.
|
Returns a string.
|
||||||
"""
|
"""
|
||||||
dictionary = dictionary or {}
|
dictionary = dictionary or {}
|
||||||
if isinstance(template_name, (list, tuple)):
|
if isinstance(template_name, (list, tuple)):
|
||||||
template = select_template(template_name)
|
template = select_template(template_name)
|
||||||
else:
|
else:
|
||||||
template = get_template(template_name)
|
template = get_template(template_name)
|
||||||
if context_instance:
|
if context_instance:
|
||||||
context_instance.update(dictionary)
|
context_instance.update(dictionary)
|
||||||
else:
|
else:
|
||||||
context_instance = dictionary
|
context_instance = dictionary
|
||||||
return template.render(context_instance)
|
return template.render(context_instance)
|
||||||
|
|
||||||
|
|
||||||
def select_template(template_name_list):
|
def select_template(template_name_list):
|
||||||
"Given a list of template names, returns the first that can be loaded."
|
"Given a list of template names, returns the first that can be loaded."
|
||||||
for template_name in template_name_list:
|
for template_name in template_name_list:
|
||||||
try:
|
try:
|
||||||
return get_template(template_name)
|
return get_template(template_name)
|
||||||
except TemplateNotFound:
|
except TemplateNotFound:
|
||||||
continue
|
continue
|
||||||
# If we get here, none of the templates could be loaded
|
# If we get here, none of the templates could be loaded
|
||||||
raise TemplateNotFound(', '.join(template_name_list))
|
raise TemplateNotFound(', '.join(template_name_list))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user