django - amazon s3 · pdf filedjango ! jinja2 aymeric augustin djangocong 2016 jardin des...
TRANSCRIPT
Django 💖
Jinja2 Aymeric Augustin DjangoCong 2016
Jardin des Plantes, Avranches, 9 avril 2016
👋 I’m Aymeric
2
Core Developersince 2011
Chief Technical Officersince 2015
• Time zones• Python 3• Transactions• App loading• Jinja2
First peer-to-peerinsurance broker
in France
We’re hiring!
Amalfi
Why Jinja2?
3
Aren’t Django templates just fine?
4
Jinja2 is fast.10-20x faster than Django templates.
Image from http://www.gentside.com/dragster/wallpaper
5
Django templates are slow.10-20x slower than Jinja2.
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!
6
>>> 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
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?
10
Aren’t Django templates just fine?
– http://jinja.pocoo.org/docs/
“Jinja2 is a modern and designer-friendly templating language for Python,
modelled after Django’s templates.”
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 syntax14
sounds important (to me)
– paraphrased from http://jinja.pocoo.org/docs/
“Jinja2 is a templating language for Python designed for experienced developers, modelled after Django’s templates.”
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
17
Discussed for historical purposes only — these versions are unsupported!
Django < 1.8 - Rendering a template
18
# 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)
Django < 1.8 - Rendering a template
19
# django/template/loaders.py (simplified)
def get_template(template_name):
template, origin = find_template(template_name)
return Template(template, origin, template_name)
Django < 1.8 - Rendering a template
20
# 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
Django < 1.8 - Actually rendering a template
21
# 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
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
23
If templates weren’t boring, they’d have been fixed earlier.
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
28
django.template.*
django.template.loader
d.t.backends.django d.t.backends.jinja2
django.template.* jinja2
– DEP 182
“this project avoids encoding the legacy of the Django template language in APIs”
29
Jinja2 for Djangonauts
30
Wake up — this is the “just do this” section.
Install Jinja2
31
$ pip install jinja2
Define a Jinja2 environment
32
# 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
Install translations (optional)
33
# project/jinja2.py
from django.utils import translation
def environment(**options):
# ...
env.install_gettext_translations(translation,
newstyle=True)
# ...
return env
Point the TEMPLATES setting to the environment
34
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,
}]
Globals
35
# project/jinja2.py
from django.core.urlresolvers import reverse
env.globals.update({
'url': reverse,
})
# templates/header.html
{{ url('account_login') }}
Filters
36
# project/jinja2.py
from urllib.parse import quote
env.filters.update({
'urlquote': quote,
})
# templates/header.html
?next={{ request.get_full_path() | urlquote }}
Tests
37
# 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 %}
Extensions
38
# project/jinja2.py
options.setdefault('extensions', []).extend([
'jinja2.ext.i18n',
])
# or, alternatively:
env.add_extension('jinja2.ext.i18n')
# templates/header.html
{{ gettext("Log in") }}
Jinja2 tips
39
You can go back to sleep — the slides will be online.
Calling methods
40
# Django templates
{{ qotd }}
# Jinja2
{{ qotd() }}
Inserting the CSRF token in a form
41
# Django templates
{% csrf_token %}
# Jinja2
{{ csrf_input }}
# Both
<input type="hidden"
name="csrfmiddlewaretoken"
value="{{ csrf_token }}" />
Replacing a context processor
42
# project/jinja2.py
from django.contrib.messages import get_messages
env.globals.update({
'get_messages': get_messages,
})
Replacing a context processor
43
# templates/header.html
{% for message in get_messages(request) %}
<div>{{ message }}</div>
{% endfor %}
Replacing a context processor - alternative
44
# 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,
})
Replacing a context processor - alternative
45
# templates/header.html
{% for message in get_messages() %}
<div>{{ message }}</div>
{% endfor %}
Replacing a template tag
46
# spam/templatetags.py
from django import template
register = template.Library()
@register.simple_tag(takes_context=True)
def eat_spam(context, arg1, arg2):
# ...
return html
Replacing a template tag
47
# templates/spam.html
{% load spam %}
{% eat_spam arg1 arg2 %}
Replacing a template tag
48
# spam/jinja2.py
@jinja2.contextfunction
def eat_spam(context, arg1, arg2):
# ...
return html
# and register it in env.globals as shown above.
Replacing a template tag
49
# templates/spam.html
{{ eat_spam(arg1, arg2) }}
Most extensions only add globals
50
# eat/jinja2.py
class EatExtension(Extension):
def __init__(self, env):
env.globals['eat'] = {'spam': eat_spam}
# templates/eat.html
{{ eat.spam() }}
Questions?
Thank you!
Estuaire de la Sée, Avranches, 9 avril 2016