Upload
trankiet
View
225
Download
1
Embed Size (px)
Citation preview
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