Presenter: Christine Cheung (http://www.xtine.net/) (@plaidxtine)
PyCon 2012 presentation page: https://us.pycon.org/2012/schedule/presentation/80/
Slides: http://speakerdeck.com/u/xtine/p/django-templating-more-than-just-blocks
Video: http://pyvideo.org/video/697/django-templating-more-than-just-blocks
Outline
- Intro to Templating
- Effective Use of Built-In Tags
- Extending Templates
- Template Loading
- What's New
- This is the End User Experience
- Balance between power and ease
- Design
- Django template theme for your IDE
- syntax highlighting, autocompletion
- django-debug-toolbar
- template paths, session variables
- Print out tag/filter reference guide
- Keep templates in one place
- Think
8
coding conventions- consistent spacing
{% load %}
all template tags up top- try
{# comment #}
rather than<!-- this -->
- also,
{% comment %}{% endcomment %}
- also,
Start with
base.html
<!doctype html> <head> <title>{% block title %}demo{% endblock title %}</title> </head> <body> {% block content %}{% endblock content %} </body> </html>
Then have pages inherit from it
{% extends "base.html" %} {% block title %}the foo page{% endblock title %} {% block content %} <div id="foo"> this is a bar. </div> {% endblock content %}
I use these in practically every project:
- title
- meta_tags, robots,
- extra_head (CSS, etc.),
- content
- extra_js (so that JavaScript can go at bottom of page which improves page-load time)
- End your block structures
{% block title %}foo{% endblock title %}
- instead of
{%block title %}foo{% endblock %}
- Can't repeat blocks
- however: context processor, include, custom template tag
- Don't "over block"
{% include "snippet.html" %}
- great for repeating template segments
- try not to include in an include -- gets confusing
- Tend to be objects passed from a view
- Modify objects with filters
{{ variable | lower }}
- Loop through etc. using tags
{% if variable %}foo{% else %}bar{% endif %}
{% for entry in blog_entries %}<h2>{{ entry.title }}</h2><p>{{ entry.body }}</p>{% endfor %}
- You can also create your own filters and tags (see Django docs on custom template tags and filters)
- Modify objects with filters
By default, Django's security is rather solid on the template side of things...
- but if you use safe or
{% autoescape %}
- * make sure you sanitize the data!*
Name {% url %}
tags as much as possible
- define URL patterns in
urls.py
url(r'^foo/$', foo, name="foo"),
<a href="{% url "foo" %}">foo</a>
{{ STATIC_URL }}css/style.css
- Not
/static/css/style.css
For heavy form action, take a look at:
- django-floppyforms (HTML 5)
- django-crispy-forms (used to be django-uni-form)
{% include form.html %}
as_ul
(docs) makes more sense thanas_p
oras_table
There are multiple ways to accomplish the same task.
No ultimately right or wrong way
- use what suits you or your team
An example
The long way:
{% if foo.bar %}
{{ foo.bar }}
{% else %}
{{ foo.baz }}
{% endif %}
or the shorter way:
{% firstof foo.bar foo.baz %}
demo/ models.py templatetags/ __init__.py demo_utils.py view.py
Given that we have template tags in demo/templatetags/demo_utils.py
{% load demo_utils %}
from django import template
register = template.Library()
@register.filter(name='remove')
def cut(value, argument):
# remove passed arguments from value
return value.replace(argument, '')
{{ foo|remove:'bar' }}
@register.filter
def lower(value):
# lowercased value with no passed arguments
return value.lower()
{{ foo|lower }}
Tags are a bit more complex
- two steps: compiling and rendering
Decide its purpose
- but start simple
better to have many tags that do many things rather than one tag that does many things
<p>
It is now
{% current_time "%Y-%m-%d %I:%M %p" %}
</p>
from django import template
register = template.Library()
@register.simple_tag
def current_time(format_string):
try:
return datetime.datetime.now().strftime(str(format_string))
except UnicodeEncodeError:
return 'oh noes current time borked'
import datetime
from django import template
register = template.Library()
@register.tag(name="current_time")
def do_current_time(parser, token):
try:
tag_name, format_string = token.split_contents()
except ValueError:
msg = '%r tag requires a single argument' % token.split_contents()[0]
raise template.TemplateSyntaxError(msg)
class CurrentTimeNode(template.Node):
def __init__(self, format_string):
self.format_string = str(format_string)
def render(self, context):
now = datetime.datetime.now()
return now.strftime(self.format_string)
- makes it simple to define syntax for a tag
- class-based template tags
- extensible argument parse for less boilerplate
Do not write a template tag that runs logic or at worst, even run Python from a custom tag
- it defeats purpose of a templating language
- dangerous
- difficult to support
Use cases
TEMPLATE_LOADERS
setting (Django docs)
from django.conf import settings
from django.template import TemplateDoesNotExist
def load_template_source(template_name, template_dirs=None):
for filepath in get_template_sources(template_name, template_dirs):
try:
# load in some templates yo
except IOError:
pass
raise TemplateDoesNotExist(template_name)
You can replace the built in templating engine
But why?
- More familiar with another templating language
- Performance boost
- Different logic control and handling
but you risk "frankensteining" your project.
Pros
- functions callable from templates - don't have to write tags and filters
- loop controls - more powerful flow control
- multiple filter arguments
- slight performance increase
Cons
- more dependencies and overhead
- extra time spent on development and support
- risk putting too much logic in templates
- minimal speed increase
Cache template loader
- compiles template files
- from Adrian Holovaty (one of the creators of Django)
- flattens template files
but also remember other bottlenecks...
Custom project and app templates
- startapp/startproject -- template (docs on Django 1.4 custom project and app templates)
- combine with your favorite boilerplate
Else if
{% elif %}
(docs on minor features)
Questions????