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:
James Socol 2015-09-21 12:55:35 -04:00
parent f8472bcac6
commit 64402d2fcc
5 changed files with 155 additions and 151 deletions

View File

@ -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.

View File

@ -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',
) )

View File

@ -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)

View File

@ -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())

View File

@ -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)