Use the Django>=1.7 app registry
- Drops support for anything < 1.7. - Use `django.apps.apps.get_app_configs()` for template loaders and helper discovery. - Delays environment creation as long as possible, and caches it, should fix #50. - Removes direct access to jingo.env, use jingo.get_env() instead. - Removes env dependency from Registry(). - Moves Template() higher in the module and sets Environment().template_class directly. - Adds `django.contrib.admin.apps.SimpleAdminConfig` to test settings to ensure support for AppConfig classes, fixes #68.
This commit is contained in:
parent
f8472bcac6
commit
64402d2fcc
@ -51,9 +51,9 @@ class with a literal string, e.g.::
|
|||||||
|
|
||||||
then you'll need to change that code slightly, to::
|
then you'll need to change that code slightly, to::
|
||||||
|
|
||||||
from jingo import env
|
from jingo import get_env
|
||||||
|
|
||||||
t = env.from_string('template_string')
|
t = get_env().from_string('template_string')
|
||||||
|
|
||||||
and then the template will be rendered with all the same features that Jingo
|
and then the template will be rendered with all the same features that Jingo
|
||||||
provides when rendering template files.
|
provides when rendering template files.
|
||||||
|
@ -4,6 +4,7 @@ path = lambda *a: os.path.join(ROOT, *a)
|
|||||||
|
|
||||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
|
'django.contrib.admin.apps.SimpleAdminConfig',
|
||||||
'jingo.tests.jinja_app',
|
'jingo.tests.jinja_app',
|
||||||
'jingo.tests.django_app',
|
'jingo.tests.django_app',
|
||||||
)
|
)
|
||||||
|
@ -7,6 +7,7 @@ import imp
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template.base import Origin, TemplateDoesNotExist
|
from django.template.base import Origin, TemplateDoesNotExist
|
||||||
from django.template.loader import BaseLoader
|
from django.template.loader import BaseLoader
|
||||||
@ -41,141 +42,6 @@ log = logging.getLogger('jingo')
|
|||||||
_helpers_loaded = False
|
_helpers_loaded = False
|
||||||
|
|
||||||
|
|
||||||
class Environment(jinja2.Environment):
|
|
||||||
|
|
||||||
def get_template(self, name, parent=None, globals=None):
|
|
||||||
"""Make sure our helpers get loaded before any templates."""
|
|
||||||
load_helpers()
|
|
||||||
return super(Environment, self).get_template(name, parent, globals)
|
|
||||||
|
|
||||||
def from_string(self, source, globals=None, template_class=None):
|
|
||||||
load_helpers()
|
|
||||||
return super(Environment, self).from_string(source, globals,
|
|
||||||
template_class)
|
|
||||||
|
|
||||||
|
|
||||||
def get_env():
|
|
||||||
"""Configure and return a jinja2 Environment."""
|
|
||||||
# Mimic Django's setup by loading templates from directories in
|
|
||||||
# TEMPLATE_DIRS and packages in INSTALLED_APPS.
|
|
||||||
x = ((jinja2.FileSystemLoader, settings.TEMPLATE_DIRS),
|
|
||||||
(jinja2.PackageLoader, settings.INSTALLED_APPS))
|
|
||||||
loaders = [loader(p) for loader, places in x for p in places]
|
|
||||||
|
|
||||||
opts = {'trim_blocks': True,
|
|
||||||
'extensions': ['jinja2.ext.i18n'],
|
|
||||||
'autoescape': True,
|
|
||||||
'auto_reload': settings.DEBUG,
|
|
||||||
'loader': jinja2.ChoiceLoader(loaders),
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasattr(settings, 'JINJA_CONFIG'):
|
|
||||||
if hasattr(settings.JINJA_CONFIG, '__call__'):
|
|
||||||
config = settings.JINJA_CONFIG()
|
|
||||||
else:
|
|
||||||
config = settings.JINJA_CONFIG
|
|
||||||
opts.update(config)
|
|
||||||
|
|
||||||
e = Environment(**opts)
|
|
||||||
# Install null translations since gettext isn't always loaded up during
|
|
||||||
# testing.
|
|
||||||
if ('jinja2.ext.i18n' in e.extensions or
|
|
||||||
'jinja2.ext.InternationalizationExtension' in e.extensions):
|
|
||||||
e.install_null_translations()
|
|
||||||
return e
|
|
||||||
|
|
||||||
|
|
||||||
def render_to_string(request, template, context=None):
|
|
||||||
"""
|
|
||||||
Render a template into a string.
|
|
||||||
"""
|
|
||||||
def get_context():
|
|
||||||
c = {} if context is None else context.copy()
|
|
||||||
for processor in get_standard_processors():
|
|
||||||
c.update(processor(request))
|
|
||||||
return c
|
|
||||||
|
|
||||||
# If it's not a Template, it must be a path to be loaded.
|
|
||||||
if not isinstance(template, jinja2.environment.Template):
|
|
||||||
template = env.get_template(template)
|
|
||||||
|
|
||||||
return template.render(get_context())
|
|
||||||
|
|
||||||
|
|
||||||
def load_helpers():
|
|
||||||
"""Try to import ``helpers.py`` from each app in INSTALLED_APPS."""
|
|
||||||
# We want to wait as long as possible to load helpers so there aren't any
|
|
||||||
# weird circular imports with jingo.
|
|
||||||
global _helpers_loaded
|
|
||||||
if _helpers_loaded:
|
|
||||||
return
|
|
||||||
_helpers_loaded = True
|
|
||||||
|
|
||||||
from jingo import helpers # noqa
|
|
||||||
|
|
||||||
for app in settings.INSTALLED_APPS:
|
|
||||||
try:
|
|
||||||
app_path = import_module(app).__path__
|
|
||||||
except AttributeError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
imp.find_module('helpers', app_path)
|
|
||||||
except ImportError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
import_module('%s.helpers' % app)
|
|
||||||
|
|
||||||
|
|
||||||
class Register(object):
|
|
||||||
"""Decorators to add filters and functions to the template Environment."""
|
|
||||||
|
|
||||||
def __init__(self, env):
|
|
||||||
self.env = env
|
|
||||||
|
|
||||||
def filter(self, f=None, override=True):
|
|
||||||
"""Adds the decorated function to Jinja's filter library."""
|
|
||||||
def decorator(f):
|
|
||||||
@functools.wraps(f)
|
|
||||||
def wrapper(*args, **kw):
|
|
||||||
return f(*args, **kw)
|
|
||||||
return self.filter(wrapper, override)
|
|
||||||
|
|
||||||
if not f:
|
|
||||||
return decorator
|
|
||||||
if override or f.__name__ not in self.env.filters:
|
|
||||||
self.env.filters[f.__name__] = f
|
|
||||||
return f
|
|
||||||
|
|
||||||
def function(self, f=None, override=True):
|
|
||||||
"""Adds the decorated function to Jinja's global namespace."""
|
|
||||||
def decorator(f):
|
|
||||||
@functools.wraps(f)
|
|
||||||
def wrapper(*args, **kw):
|
|
||||||
return f(*args, **kw)
|
|
||||||
return self.function(wrapper, override)
|
|
||||||
|
|
||||||
if not f:
|
|
||||||
return decorator
|
|
||||||
if override or f.__name__ not in self.env.globals:
|
|
||||||
self.env.globals[f.__name__] = f
|
|
||||||
return f
|
|
||||||
|
|
||||||
def inclusion_tag(self, template):
|
|
||||||
"""Adds a function to Jinja, but like Django's @inclusion_tag."""
|
|
||||||
def decorator(f):
|
|
||||||
@functools.wraps(f)
|
|
||||||
def wrapper(*args, **kw):
|
|
||||||
context = f(*args, **kw)
|
|
||||||
t = env.get_template(template).render(context)
|
|
||||||
return jinja2.Markup(t)
|
|
||||||
return self.function(wrapper)
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
env = get_env()
|
|
||||||
register = Register(env)
|
|
||||||
|
|
||||||
|
|
||||||
class Template(jinja2.Template):
|
class Template(jinja2.Template):
|
||||||
|
|
||||||
def render(self, context={}):
|
def render(self, context={}):
|
||||||
@ -206,9 +72,141 @@ class Template(jinja2.Template):
|
|||||||
return super(Template, self).render(context_dict)
|
return super(Template, self).render(context_dict)
|
||||||
|
|
||||||
|
|
||||||
|
class Environment(jinja2.Environment):
|
||||||
|
template_class = Template
|
||||||
|
|
||||||
|
def get_template(self, name, parent=None, globals=None):
|
||||||
|
"""Make sure our helpers get loaded before any templates."""
|
||||||
|
load_helpers()
|
||||||
|
return super(Environment, self).get_template(name, parent, globals)
|
||||||
|
|
||||||
|
def from_string(self, source, globals=None, template_class=None):
|
||||||
|
load_helpers()
|
||||||
|
return super(Environment, self).from_string(source, globals,
|
||||||
|
template_class)
|
||||||
|
|
||||||
|
|
||||||
|
_env = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_env():
|
||||||
|
"""Configure and return a jinja2 Environment."""
|
||||||
|
global _env
|
||||||
|
if _env:
|
||||||
|
return _env
|
||||||
|
# Mimic Django's setup by loading templates from directories in
|
||||||
|
# TEMPLATE_DIRS and packages in INSTALLED_APPS.
|
||||||
|
loaders = [jinja2.FileSystemLoader(d) for d in settings.TEMPLATE_DIRS]
|
||||||
|
loaders += [jinja2.PackageLoader(c.name) for c in apps.get_app_configs()]
|
||||||
|
|
||||||
|
opts = {
|
||||||
|
'trim_blocks': True,
|
||||||
|
'extensions': ['jinja2.ext.i18n'],
|
||||||
|
'autoescape': True,
|
||||||
|
'auto_reload': settings.DEBUG,
|
||||||
|
'loader': jinja2.ChoiceLoader(loaders),
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasattr(settings, 'JINJA_CONFIG'):
|
||||||
|
if hasattr(settings.JINJA_CONFIG, '__call__'):
|
||||||
|
config = settings.JINJA_CONFIG()
|
||||||
|
else:
|
||||||
|
config = settings.JINJA_CONFIG
|
||||||
|
opts.update(config)
|
||||||
|
|
||||||
|
e = Environment(**opts)
|
||||||
|
# Install null translations since gettext isn't always loaded up during
|
||||||
|
# testing.
|
||||||
|
if ('jinja2.ext.i18n' in e.extensions or
|
||||||
|
'jinja2.ext.InternationalizationExtension' in e.extensions):
|
||||||
|
e.install_null_translations()
|
||||||
|
_env = e
|
||||||
|
return e
|
||||||
|
|
||||||
|
|
||||||
|
def render_to_string(request, template, context=None):
|
||||||
|
"""
|
||||||
|
Render a template into a string.
|
||||||
|
"""
|
||||||
|
def get_context():
|
||||||
|
c = {} if context is None else context.copy()
|
||||||
|
for processor in get_standard_processors():
|
||||||
|
c.update(processor(request))
|
||||||
|
return c
|
||||||
|
|
||||||
|
# If it's not a Template, it must be a path to be loaded.
|
||||||
|
if not isinstance(template, jinja2.environment.Template):
|
||||||
|
template = get_env().get_template(template)
|
||||||
|
|
||||||
|
return template.render(get_context())
|
||||||
|
|
||||||
|
|
||||||
|
def load_helpers():
|
||||||
|
"""Try to import ``helpers.py`` from each app in INSTALLED_APPS."""
|
||||||
|
# We want to wait as long as possible to load helpers so there aren't any
|
||||||
|
# weird circular imports with jingo.
|
||||||
|
global _helpers_loaded
|
||||||
|
if _helpers_loaded:
|
||||||
|
return
|
||||||
|
_helpers_loaded = True
|
||||||
|
|
||||||
|
from jingo import helpers # noqa
|
||||||
|
|
||||||
|
for config in apps.get_app_configs():
|
||||||
|
try:
|
||||||
|
imp.find_module('helpers', config.name)
|
||||||
|
except ImportError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
import_module('%s.helpers' % config.name)
|
||||||
|
|
||||||
|
|
||||||
|
class Register(object):
|
||||||
|
"""Decorators to add filters and functions to the template Environment."""
|
||||||
|
def filter(self, f=None, override=True):
|
||||||
|
"""Adds the decorated function to Jinja's filter library."""
|
||||||
|
def decorator(f):
|
||||||
|
@functools.wraps(f)
|
||||||
|
def wrapper(*args, **kw):
|
||||||
|
return f(*args, **kw)
|
||||||
|
return self.filter(wrapper, override)
|
||||||
|
|
||||||
|
if not f:
|
||||||
|
return decorator
|
||||||
|
if override or f.__name__ not in get_env().filters:
|
||||||
|
get_env().filters[f.__name__] = f
|
||||||
|
return f
|
||||||
|
|
||||||
|
def function(self, f=None, override=True):
|
||||||
|
"""Adds the decorated function to Jinja's global namespace."""
|
||||||
|
def decorator(f):
|
||||||
|
@functools.wraps(f)
|
||||||
|
def wrapper(*args, **kw):
|
||||||
|
return f(*args, **kw)
|
||||||
|
return self.function(wrapper, override)
|
||||||
|
|
||||||
|
if not f:
|
||||||
|
return decorator
|
||||||
|
if override or f.__name__ not in get_env().globals:
|
||||||
|
get_env().globals[f.__name__] = f
|
||||||
|
return f
|
||||||
|
|
||||||
|
def inclusion_tag(self, template):
|
||||||
|
"""Adds a function to Jinja, but like Django's @inclusion_tag."""
|
||||||
|
def decorator(f):
|
||||||
|
@functools.wraps(f)
|
||||||
|
def wrapper(*args, **kw):
|
||||||
|
context = f(*args, **kw)
|
||||||
|
t = get_env().get_template(template).render(context)
|
||||||
|
return jinja2.Markup(t)
|
||||||
|
return self.function(wrapper)
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
register = Register()
|
||||||
|
|
||||||
|
|
||||||
class Loader(BaseLoader):
|
class Loader(BaseLoader):
|
||||||
is_usable = True
|
is_usable = True
|
||||||
env.template_class = Template
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if has_engine:
|
if has_engine:
|
||||||
@ -238,7 +236,7 @@ class Loader(BaseLoader):
|
|||||||
raise TemplateDoesNotExist(template_name)
|
raise TemplateDoesNotExist(template_name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
template = env.get_template(template_name)
|
template = get_env().get_template(template_name)
|
||||||
return template, template.filename
|
return template, template.filename
|
||||||
except jinja2.TemplateNotFound:
|
except jinja2.TemplateNotFound:
|
||||||
raise TemplateDoesNotExist(template_name)
|
raise TemplateDoesNotExist(template_name)
|
||||||
@ -248,7 +246,7 @@ class Loader(BaseLoader):
|
|||||||
raise TemplateDoesNotExist(template_name)
|
raise TemplateDoesNotExist(template_name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
template = env.get_template(template_name)
|
template = get_env().get_template(template_name)
|
||||||
except jinja2.TemplateNotFound:
|
except jinja2.TemplateNotFound:
|
||||||
raise TemplateDoesNotExist(template_name)
|
raise TemplateDoesNotExist(template_name)
|
||||||
|
|
||||||
|
@ -12,10 +12,12 @@ except ImportError:
|
|||||||
import jingo
|
import jingo
|
||||||
|
|
||||||
|
|
||||||
@patch('jingo.env')
|
@patch('jingo.get_env')
|
||||||
def test_render(mock_env):
|
def test_render(mock_get_env):
|
||||||
mock_template = Mock()
|
mock_template = Mock()
|
||||||
|
mock_env = Mock()
|
||||||
mock_env.get_template.return_value = mock_template
|
mock_env.get_template.return_value = mock_template
|
||||||
|
mock_get_env.return_value = mock_env
|
||||||
|
|
||||||
response = render(Mock(), sentinel.template, status=32)
|
response = render(Mock(), sentinel.template, status=32)
|
||||||
mock_env.get_template.assert_called_with(sentinel.template)
|
mock_env.get_template.assert_called_with(sentinel.template)
|
||||||
@ -24,19 +26,22 @@ def test_render(mock_env):
|
|||||||
eq_(response.status_code, 32)
|
eq_(response.status_code, 32)
|
||||||
|
|
||||||
|
|
||||||
@patch('jingo.env')
|
@patch('jingo.get_env')
|
||||||
def test_render_to_string(mock_env):
|
def test_render_to_string(mock_get_env):
|
||||||
template = jinja2.environment.Template('The answer is {{ answer }}')
|
template = jinja2.environment.Template('The answer is {{ answer }}')
|
||||||
rendered = jingo.render_to_string(Mock(), template, {'answer': 42})
|
rendered = jingo.render_to_string(Mock(), template, {'answer': 42})
|
||||||
|
|
||||||
eq_(rendered, 'The answer is 42')
|
eq_(rendered, 'The answer is 42')
|
||||||
|
|
||||||
|
|
||||||
@patch('jingo.env.get_template')
|
def test_inclusion_tag():
|
||||||
def test_inclusion_tag(get_template):
|
|
||||||
@jingo.register.inclusion_tag('xx.html')
|
@jingo.register.inclusion_tag('xx.html')
|
||||||
def tag(x):
|
def tag(x):
|
||||||
return {'z': x}
|
return {'z': x}
|
||||||
get_template.return_value = jinja2.environment.Template('<{{ z }}>')
|
|
||||||
t = jingo.env.from_string('{{ tag(1) }}')
|
env = jingo.get_env()
|
||||||
eq_('<1>', t.render())
|
with patch.object(env, 'get_template') as mock_get_template:
|
||||||
|
temp = jinja2.environment.Template('<{{ z }}>')
|
||||||
|
mock_get_template.return_value = temp
|
||||||
|
t = env.from_string('{{ tag(1) }}')
|
||||||
|
eq_('<1>', t.render())
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django.test.html import HTMLParseError, parse_html
|
from django.test.html import HTMLParseError, parse_html
|
||||||
from nose.tools import eq_
|
from nose.tools import eq_
|
||||||
|
|
||||||
from jingo import env
|
from jingo import get_env
|
||||||
|
|
||||||
|
|
||||||
def htmleq_(html1, html2, msg=None):
|
def htmleq_(html1, html2, msg=None):
|
||||||
@ -31,5 +31,5 @@ def assert_and_parse_html(html, user_msg, msg):
|
|||||||
|
|
||||||
|
|
||||||
def render(s, context={}):
|
def render(s, context={}):
|
||||||
t = env.from_string(s)
|
t = get_env().from_string(s)
|
||||||
return t.render(context)
|
return t.render(context)
|
||||||
|
Loading…
Reference in New Issue
Block a user