Django Jinja2 Aymeric Augustin DjangoCong 2016 Jardin des Plantes, Avranches, 9 avril 2016
I m Aymeric Amalfi Core Developer since 2011 Chief Technical Officer since 2015 Time zones Python 3 Transactions App loading Jinja2 First peer-to-peer insurance broker in France We re hiring! 2
Why Jinja2? Aren t Django templates just fine? 3
Jinja2 is fast. 10-20x faster than Django templates. 4 Image from http://www.gentside.com/dragster/wallpaper
Django templates are slow. 10-20x slower than Jinja2. 5 Image from https://commons.wikimedia.org/wiki/file:grand_bi_sur_la_terrasse_dufferin_vers_1900.jpg
Let s try it on the fractalideas.com home page! >>> import statistics, timeit >>> setup = \... "from django.template.loader import render_to_string" >>> round(statistics.median(timeit.repeat(... "render_to_string('home.html', using='django')",... setup, repeat=10, number=1000)), 2) 1.18 # ms >>> round(statistics.median(timeit.repeat(... "render_to_string('home.html', using='jinja2')",... setup, repeat=10, number=1000)), 2) 0.35 # ms 6
Let s try it on the fractalideas.com home page! More than three times faster! Not even a millisecond faster. Page loads 0.004% faster with a cold cache. 7
Let s try it on the fractalideas.com home page! More than three times faster! Not even a millisecond faster. Page loads 0.02% faster with a warm cache. 8
Template rendering speed (often) doesn t matter Light content? Anything is fast enough Heavy static content? Pre-render & cache it Heavy dynamic content? Render in the browser If you re thinking about switching template languages, consider both server-side and client-side options (React) Until you ve optimized everything else, templates are unlikely to be a bottleneck 9
Why Jinja2? Aren t Django templates just fine? 10
Jinja2 is a modern and designer-friendly templating language for Python, modelled after Django s templates. http://jinja.pocoo.org/docs/ 11
Advantages of Jinja2 More comfortable for developers Django expects templates to be written by designers Easier to customize Try writing a Django template tag without the docs Meaningfully faster in some use cases Template-based widget rendering (#15667) 12
Drawbacks of Jinja2 Less common than Django templates Many authors of pluggable apps don t know where to start (yet) Targets experienced developers Docs suggest handling escaping manually for performance Scoping behavior for included blocks is declarative etc. 13
Features of Jinja2 from its docs homepage sandboxed execution powerful automatic HTML escaping system for XSS prevention template inheritance compiles down to the optimal python code just in time optional ahead-of-time template compilation easy to debug ( ) configurable syntax sounds important (to me) 14
Jinja2 is a templating language for Python designed for experienced developers, modelled after Django s templates. paraphrased from http://jinja.pocoo.org/docs/ 15
Third-party solutions Coffin (2008 - reborn in 2015) Alternative APIs such as render() Jingo (2010) Template loader (class-based API since Django 1.2) Django-Jinja (2012) More opinionated and full-featured template loader 16
Templates in Django < 1.8 Discussed for historical purposes only these versions are unsupported! 17
Django < 1.8 - Rendering a template # django/template/loaders.py (simplified) def render_to_string(template_name, dictionary): template = get_template(template_name, dirs) context = Context(dictionary) return template.render(context) 18
Django < 1.8 - Rendering a template # django/template/loaders.py (simplified) def get_template(template_name): template, origin = find_template(template_name) return Template(template, origin, template_name) 19
Django < 1.8 - Rendering a template # django/template/loaders.py (simplified) def find_template(name, dirs=none): global template_source_loaders if template_source_loaders is None:... # init from settings.template_loaders for loader in template_source_loaders:... # try loading with loader(name) raise TemplateDoesNotExist(name) global configuration 20
Django < 1.8 - Actually rendering a template # django/shortcuts.py (drastically simplified) def render(request, template_name, dictionary): template = get_template(template_name, dirs) context = RequestContext(request, dictionary) return HttpResponse(template.render(context)) strong coupling 21
Context processors are a poor API How many context processors depend on request? Aside from the request context processor, that is. How hard is it to make a context processor lazy? In order to avoid overhead in templates that don t need it. Just an API for {{ global_func(request) }}? Django templates cannot call functions with arguments. 22
Multiple templates engines in Django 1.8 If templates weren t boring, they d have been fixed earlier. 23
24
25
26
Plan 1. Write a Django Enhancement Proposal (DEP) Lots of reading; some writing 2. Refactor Django templates as a library (optional) Actually not optional 3. Implement the DEP Sounds easy 27
Concept django.template.* django.template.loader d.t.backends.django django.template.* d.t.backends.jinja2 jinja2 28
this project avoids encoding the legacy of the Django template language in APIs DEP 182 29
Jinja2 for Djangonauts Wake up this is the just do this section. 30
Install Jinja2 $ pip install jinja2 31
Define a Jinja2 environment # project/jinja2.py from jinja2 import Environment def environment(**options): options.setdefault('extensions', []).extend([...]) env = Environment(**options) env.filters.update({... }) env.globals.update({... }) env.tests.update({... }) return env 32
Install translations (optional) # project/jinja2.py from django.utils import translation def environment(**options): #... env.install_gettext_translations(translation, newstyle=true) #... return env 33
Point the TEMPLATES setting to the environment TEMPLATES = [{ 'BACKEND': 'django.template.backends' '.jinja2.jinja2', 'DIRS': [os.path.join(base_dir, 'templates')], 'OPTIONS': { 'environment': 'project.jinja2.environment', }, }, { 'BACKEND': 'django.template.backends' '.django.djangotemplates', 'APP_DIRS': True, }] 34
Globals # project/jinja2.py from django.core.urlresolvers import reverse env.globals.update({ }) 'url': reverse, # templates/header.html {{ url('account_login') }} 35
Filters # project/jinja2.py from urllib.parse import quote env.filters.update({ }) 'urlquote': quote, # templates/header.html?next={{ request.get_full_path() urlquote }} 36
Tests # project/jinja2.py from django.utils.timezone import is_aware env.tests.update({ }) 'aware': is_aware, # templates/datetime.html {% if at is aware %}{{ at.tzname() }}{% endif %} 37
Extensions # project/jinja2.py options.setdefault('extensions', []).extend([ 'jinja2.ext.i18n', ]) # or, alternatively: env.add_extension('jinja2.ext.i18n') # templates/header.html {{ gettext("log in") }} 38
Jinja2 tips You can go back to sleep the slides will be online. 39
Calling methods # Django templates {{ qotd }} # Jinja2 {{ qotd() }} 40
Inserting the CSRF token in a form # Django templates {% csrf_token %} # Jinja2 {{ csrf_input }} # Both <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}" /> 41
Replacing a context processor # project/jinja2.py from django.contrib.messages import get_messages env.globals.update({ }) 'get_messages': get_messages, 42
Replacing a context processor # templates/header.html {% for message in get_messages(request) %} <div>{{ message }}</div> {% endfor %} 43
Replacing a context processor - alternative # project/jinja2.py from django.contrib.messages import get_messages @jinja2.contextfunction def _get_messages(context): return get_messages(context['request']) env.globals.update({ }) 'get_messages': _get_messages, 44
Replacing a context processor - alternative # templates/header.html {% for message in get_messages() %} <div>{{ message }}</div> {% endfor %} 45
Replacing a template tag # spam/templatetags.py from django import template register = template.library() @register.simple_tag(takes_context=true) def eat_spam(context, arg1, arg2): #... return html 46
Replacing a template tag # templates/spam.html {% load spam %} {% eat_spam arg1 arg2 %} 47
Replacing a template tag # spam/jinja2.py @jinja2.contextfunction def eat_spam(context, arg1, arg2): #... return html # and register it in env.globals as shown above. 48
Replacing a template tag # templates/spam.html {{ eat_spam(arg1, arg2) }} 49
Most extensions only add globals # eat/jinja2.py class EatExtension(Extension): def init (self, env): env.globals['eat'] = {'spam': eat_spam} # templates/eat.html {{ eat.spam() }} 50
Thank you! Questions? Estuaire de la Sée, Avranches, 9 avril 2016