-
-
Save chumaumenze/6391c4567ec518c3592828a647d8d1b9 to your computer and use it in GitHub Desktop.
Jinja2 template loader for django
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.core.urlresolvers import get_callable | |
from django.template import TemplateDoesNotExist | |
from django.template.loader import BaseLoader | |
from django.template.context import BaseContext | |
from django.conf import settings | |
import jinja2 | |
def monkey_patch_django(): | |
""" | |
Patching some django objects to make them "safe" for jinja's escape() function. | |
Good for us it uses __html__() method. | |
""" | |
# Django's SafeString and SafeUnicode should not be escaped: | |
from django.utils.safestring import SafeData | |
SafeData.__html__ = lambda self: self | |
from jinja2 import escape | |
from django.forms import BaseForm, Media | |
from django.forms.forms import BoundField | |
from django.forms.formsets import BaseFormSet | |
from django.forms.util import ErrorDict, ErrorList | |
# If unicode returns SafeData, then escape will pass it outside unmodified thanks to patch above | |
# If it's just a string it will be escaped | |
for cls in (BaseForm, Media, BoundField, BaseFormSet, ErrorDict, ErrorList): | |
cls.__html__ = lambda self: escape(unicode(self)) | |
class Template(object): | |
""" | |
A container for jinja2 Template class | |
""" | |
def __init__(self, template, origin=None, name='<Unknown Template>'): | |
self.template = template | |
self.origin = origin | |
self.name = name | |
def render(self, context): | |
# make a flat dict from django Context | |
if isinstance(context, BaseContext): | |
d = {} | |
for u in context.dicts: | |
d.update(u) | |
else: | |
d = context | |
return self.template.render(d) | |
class Loader(BaseLoader): | |
is_usable = True | |
def __init__(self, *args, **kwargs): | |
""" | |
Creating jinja2 environment | |
""" | |
assert hasattr(settings, 'TEMPLATE_DIRS'), 'Jinja2 template loader needs TEMPLATE_DIRS setting' | |
monkey_patch_django() | |
extras = self._get_env_extras() | |
options = getattr(settings, 'JINJA2_ENVIRONMENT_OPTIONS', {}) | |
options['extensions'] = extras['extensions'] | |
options['loader'] = jinja2.FileSystemLoader(settings.TEMPLATE_DIRS) | |
### Some special tuning of jinja2 environment goes here | |
# Number of compiled jinja2 templates in process memory | |
options['cache_size'] = -1 | |
# Check whether template file is changed only in development | |
#options['auto_reload'] = settings.DEBUG | |
# Use jinja's bytecode cache | |
options['bytecode_cache'] = jinja2.FileSystemBytecodeCache(settings.HOME_DIR + '/tmp/jinja_cache') | |
self.env = jinja2.Environment(**options) | |
self.env.filters.update(extras['filters']) | |
self.env.globals.update(extras['globals']) | |
self.env.tests.update(extras['tests']) | |
def _get_env_extras(self): | |
""" | |
Creates a dict of extensions, filters, globals and tests from settings | |
""" | |
extensions, filters, objects, tests = [], {}, {}, {} | |
# add the globally defined extension list | |
extensions.extend(list(getattr(settings, 'JINJA2_EXTENSIONS', []))) | |
# parse filters, globals and tests settings | |
def from_setting(setting): | |
retval = {} | |
setting = getattr(settings, setting, {}) | |
if isinstance(setting, dict): | |
for key, value in setting.iteritems(): | |
retval[key] = callable(value) and value or get_callable(value) | |
else: | |
for value in setting: | |
value = callable(value) and value or get_callable(value) | |
retval[value.__name__] = value | |
return retval | |
filters.update(from_setting('JINJA2_FILTERS')) | |
objects.update(from_setting('JINJA2_GLOBALS')) | |
tests.update(from_setting('JINJA2_TESTS')) | |
return dict( | |
extensions=extensions, | |
filters=filters, | |
globals=objects, | |
tests=tests, | |
) | |
def load_template(self, template_name, template_dirs=None): | |
# Leave .html extension for django template (admin, contrib, etc) | |
if template_name.endswith('.html'): | |
raise TemplateDoesNotExist(template_name) | |
try: | |
template = self.env.get_template(template_name) | |
return Template(template, None, template_name), None | |
except jinja2.TemplateNotFound: | |
raise TemplateDoesNotExist(template_name) | |
def load_template_source(self, template_name, template_dirs=None): | |
# Leave .html extension for django template (admin, contrib, etc) | |
if template_name.endswith('.html'): | |
raise TemplateDoesNotExist(template_name) | |
try: | |
source, filename, uptodate = self.env.loader.get_source(self.env, template_name) | |
return source, filename | |
except jinja2.TemplateNotFound: | |
raise TemplateDoesNotExist(template_name) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
# First try to load with our loader then fallback to django default templates | |
# We should use another (not .html) extension for our Jinja2 templates in order to make this work | |
TEMPLATE_LOADERS = ( | |
'jinjalink_loader.Loader', | |
# these loaders here for admin, contrib and third-party apps | |
'django.template.loaders.filesystem.Loader', | |
'django.template.loaders.app_directories.Loader', | |
) | |
# These settings work as you expect for both Jinja2 and Django templates | |
TEMPLATE_DIRS = ( | |
HOME_DIR + '/templates' | |
) | |
TEMPLATE_CONTEXT_PROCESSORS = ( | |
"django.core.context_processors.auth", | |
"django.core.context_processors.debug", | |
"django.core.context_processors.i18n", | |
"django.core.context_processors.media", | |
"django.core.context_processors.request", | |
) | |
# And these are for Jinja2 only | |
JINJA2_EXTENSIONS = ( | |
'jinja2.ext.with_', | |
) | |
JINJA2_FILTERS = { | |
# use django filters in jinja | |
'floatformat': 'django.template.defaultfilters.floatformat', | |
'slugify': 'django.template.defaultfilters.slugify', | |
# add your custom ones here | |
... | |
} | |
# You can also just list them, function names will be used as filter names: | |
#JINJA2_FILTERS = ('django.template.defaultfilters.floatformat', | |
# 'django.template.defaultfilters.slugify') | |
# Used similar to filters | |
JINJA2_GLOBALS = { | |
'now': 'datetime.now', | |
} | |
JINJA2_TESTS = {} | |
# and add some options here | |
JINJA2_ENVIRONMENT_OPTIONS = { | |
'autoescape': True, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# an example is borrowed from django tutorial | |
from django.shortcuts import render_to_response, get_object_or_404 | |
# Using Django templates | |
def detail(request, poll_id): | |
p = get_object_or_404(Poll, pk=poll_id) | |
return render_to_response('polls/detail.html', {'poll': p}) | |
# Using Jinja2 templates | |
# Any extension but ".html" will do. I simply like .j2 | |
def detail(request, poll_id): | |
p = get_object_or_404(Poll, pk=poll_id) | |
return render_to_response('polls/detail.j2', {'poll': p}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment