Skip to content

Instantly share code, notes, and snippets.

@Suor
Created May 14, 2011 12:08
Show Gist options
  • Save Suor/972162 to your computer and use it in GitHub Desktop.
Save Suor/972162 to your computer and use it in GitHub Desktop.
Jinja2 template loader for django
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)
...
# 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
...
}
# Used similar to filters
JINJA2_GLOBALS = {
'now': 'datetime.now',
}
JINJA2_TESTS = {}
# and add some options here
JINJA2_ENVIRONMENT_OPTIONS = {
'autoescape': True,
}
# 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
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