Although most of your interactions with Django’s template language will be in the role of template author, you may want to customize and extend the template engine – either to make it do something it doesn’t already do, or to make your job easier in some other way.
This chapter delves deep into the guts of Django’s template system. It covers what you need to know if you plan to extend the system or if you’re just curious about how it works. It also covers the auto-escaping feature, a security measure you’ll no doubt notice over time as you continue to use Django.
If you’re looking to use the Django template system as part of another application (i.e., without the rest of the framework), make sure to read the “Configuring the Template System in Standalone Mode” section later in the chapter.
First, let’s quickly review a number of terms introduced in Chapter 4:
A template is a text document, or a normal Python string, that is marked up using the Django template language. A template can contain template tags and variables.
A
template tag
is a symbol within a template that does something. This
definition is deliberately vague. For example, a template tag can produce
content, serve as a control structure (an
if
statement or
for
loop), grab content from a database, or enable access to other template
tags.
Template tags are surrounded by
{%
and
%}
:
{% if is_logged_in %}
Thanks for logging in!
{% else %}
Please log in.
{% endif %}
A variable is a symbol within a template that outputs a value.
Variable tags are surrounded by
{{
and
}}
:
My first name is {{ first_name }}. My last name is {{ last_name }}.
A context is a name -> value mapping (similar to a Python dictionary) that is passed to a template.
A template renders a context by replacing the variable “holes” with values from the context and executing all template tags.
For more details about the basics of these terms, refer back to Chapter 4.
The rest of this chapter discusses ways of extending the template engine. First, though, let’s take a quick look at a few internals left out of Chapter 4 for simplicity.
When rendering a template, you need a context. This can be an instance of
django.template.Context
, but Django also comes with a subclass,
django.template.RequestContext
, that acts slightly differently.
RequestContext
adds a bunch of variables to your template context by
default – things like the
HttpRequest
object or information about the
currently logged-in user. The
render()
shortcut creates a
RequestContext
unless it is passed a different context instance explicitly.
Use
RequestContext
when you don’t want to have to specify the same set of
variables in a series of templates. For example, consider these two views:
from django.template import loader, Context
def view_1(request):
# ...
t = loader.get_template('template1.html')
c = Context({
'app': 'My app',
'user': request.user,
'ip_address': request.META['REMOTE_ADDR'],
'message': 'I am view 1.'
})
return t.render(c)
def view_2(request):
# ...
t = loader.get_template('template2.html')
c = Context({
'app': 'My app',
'user': request.user,
'ip_address': request.META['REMOTE_ADDR'],
'message': 'I am the second view.'
})
return t.render(c)
(Note that we’re deliberately
not
using the
render()
shortcut
in these examples – we’re manually loading the templates, constructing the
context objects and rendering the templates. We’re “spelling out” all of the
steps for the purpose of clarity.)
Each view passes the same three variables –
app
,
user
and
ip_address
– to its template. Wouldn’t it be nice if we could remove that
redundancy?
RequestContext
and
context processors
were created to solve this
problem. Context processors let you specify a number of variables that get set
in each context automatically – without you having to specify the variables in
each
render()
call. The catch is that you have to use
RequestContext
instead of
Context
when you render a template.
The most low-level way of using context processors is to create some processors
and pass them to
RequestContext
. Here’s how the above example could be
written with context processors:
from django.template import loader, RequestContext
def custom_proc(request):
"A context processor that provides 'app', 'user' and 'ip_address'."
return {
'app': 'My app',
'user': request.user,
'ip_address': request.META['REMOTE_ADDR']
}
def view_1(request):
# ...
t = loader.get_template('template1.html')
c = RequestContext(request, {'message': 'I am view 1.'},
processors=[custom_proc])
return t.render(c)
def view_2(request):
# ...
t = loader.get_template('template2.html')
c = RequestContext(request, {'message': 'I am the second view.'},
processors=[custom_proc])
return t.render(c)
Let’s step through this code:
First, we define a function
custom_proc
. This is a context processor
– it takes an
HttpRequest
object and returns a dictionary of
variables to use in the template context. That’s all it does.
We’ve changed the two view functions to use
RequestContext
instead
of
Context
. There are two differences in how the context is
constructed. One,
RequestContext
requires the first argument to be an
HttpRequest
object – the one that was passed into the view function
in the first place (
request
). Two,
RequestContext
takes an
optional
processors
argument, which is a list or tuple of context
processor functions to use. Here, we pass in
custom_proc
, the custom
processor we defined above.
Each view no longer has to include
app
,
user
or
ip_address
in
its context construction, because those are provided by
custom_proc
.
Each view
still
has the flexibility to introduce any custom template
variables it might need. In this example, the
message
template
variable is set differently in each view.
In Chapter 4, we introduced the
render()
shortcut, which saves
you from having to call
loader.get_template()
, then create a
Context
,
then call the
render()
method on the template. In order to demonstrate the
lower-level workings of context processors, the above examples didn’t use
render()
, . But it’s possible – and preferable – to use
context processors with
render()
. Do this with the
context_instance
argument, like so:
from django.shortcuts import render
from django.template import RequestContext
def custom_proc(request):
"A context processor that provides 'app', 'user' and 'ip_address'."
return {
'app': 'My app',
'user': request.user,
'ip_address': request.META['REMOTE_ADDR']
}
def view_1(request):
# ...
return render(request, 'template1.html',
{'message': 'I am view 1.'},
context_instance=RequestContext(request, processors=[custom_proc]))
def view_2(request):
# ...
return render(request, 'template2.html',
{'message': 'I am the second view.'},
context_instance=RequestContext(request, processors=[custom_proc]))
Here, we’ve trimmed down each view’s template rendering code to a single (wrapped) line.
This is an improvement, but, evaluating the conciseness of this code, we have
to admit we’re now almost overdosing on the
other
end of the spectrum. We’ve
removed redundancy in data (our template variables) at the cost of adding
redundancy in code (in the
processors
call). Using context processors
doesn’t save you much typing if you have to type
processors
all the time.
For that reason, Django provides support for
global
context processors. The
TEMPLATE_CONTEXT_PROCESSORS
setting (in your
settings.py
) designates
which context processors should
always
be applied to
RequestContext
. This
removes the need to specify
processors
each time you use
RequestContext
.
By default,
TEMPLATE_CONTEXT_PROCESSORS
is set to the following:
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
)
This setting is a tuple of callables that use the same interface as our
custom_proc
function above – functions that take a request object as their
argument and return a dictionary of items to be merged into the context. Note
that the values in
TEMPLATE_CONTEXT_PROCESSORS
are specified as
strings
,
which means the processors are required to be somewhere on your Python path
(so you can refer to them from the setting).
Each processor is applied in order. That is, if one processor adds a variable to the context and a second processor adds a variable with the same name, the second will override the first.
Django provides a number of simple context processors, including the ones that are enabled by default:
If
TEMPLATE_CONTEXT_PROCESSORS
contains this processor, every
RequestContext
will contain these variables:
user
: A
django.contrib.auth.models.User
instance representing the
current logged-in user (or an
AnonymousUser
instance, if the client
isn’t logged in).
messages
: A list of messages (as strings) for the current logged-in
user. Behind the scenes, this variable calls
request.user.get_and_delete_messages()
for every request. That method
collects the user’s messages and deletes them from the database.
perms
: An instance of
django.core.context_processors.PermWrapper
,
which represents the permissions the current logged-in user has.
See Chapter 14 for more information on users, permissions, and messages.
This processor pushes debugging information down to the template layer. If
TEMPLATE_CONTEXT_PROCESSORS
contains this processor, every
RequestContext
will contain these variables:
debug
: The value of your
DEBUG
setting (either
True
or
False
). You can use this variable in templates to test whether you’re
in debug mode.
sql_queries
: A list of
{'sql':
...,
'time':
...}
dictionaries
representing every SQL query that has happened so far during the request
and how long it took. The list is in the order in which the queries were
issued.
Because debugging information is sensitive, this context processor will only add variables to the context if both of the following conditions are true:
The
DEBUG
setting is
True
.
The request came from an IP address in the
INTERNAL_IPS
setting.
Astute readers will notice that the
debug
template variable will never have
the value
False
because, if
DEBUG
is
False
, then the
debug
template variable won’t be populated in the first place.
If this processor is enabled, every
RequestContext
will contain these
variables:
LANGUAGES
: The value of the
LANGUAGES
setting.
LANGUAGE_CODE
:
request.LANGUAGE_CODE
if it exists; otherwise, the
value of the
LANGUAGE_CODE
setting.
Appendix D provides more information about these two settings.
If this processor is enabled, every
RequestContext
will contain a variable
request
, which is the current
HttpRequest
object. Note that this
processor is not enabled by default; you have to activate it.
You might want to use this if you find your templates needing to access
attributes of the current
HttpRequest
such as the IP address:
{{ request.REMOTE_ADDR }}
Here are a few tips for rolling your own:
Make each context processor responsible for the smallest subset of functionality possible. It’s easy to use multiple processors, so you might as well split functionality into logical pieces for future reuse.
Keep in mind that any context processor in
TEMPLATE_CONTEXT_PROCESSORS
will be available in
every
template powered by that settings file, so
try to pick variable names that are unlikely to conflict with variable
names your templates might be using independently. As variable names are
case-sensitive, it’s not a bad idea to use all caps for variables that a
processor provides.
It doesn’t matter where on the filesystem they live, as long as they’re
on your Python path so you can point to them from the
TEMPLATE_CONTEXT_PROCESSORS
setting. With that said, the convention
is to save them in a file called
context_processors.py
within your
app or project.
When generating HTML from templates, there’s always a risk that a variable will include characters that affect the resulting HTML. For example, consider this template fragment:
Hello, {{ name }}.
At first, this seems like a harmless way to display a user’s name, but consider what would happen if the user entered his name as this:
<script>alert('hello')</script>
With this name value, the template would be rendered as:
Hello, <script>alert('hello')</script>
…which means the browser would pop-up a JavaScript alert box!
Similarly, what if the name contained a
'<'
symbol, like this?
<b>username
That would result in a rendered template like this:
Hello, <b>username
…which, in turn, would result in the remainder of the Web page being bolded!
Clearly, user-submitted data shouldn’t be trusted blindly and inserted directly into your Web pages, because a malicious user could use this kind of hole to do potentially bad things. This type of security exploit is called a Cross Site Scripting (XSS) attack. (For more on security, see Chapter 20.)
To avoid this problem, you have two options:
One, you can make sure to run each untrusted variable through the
escape
filter, which converts potentially harmful HTML characters to
unharmful ones. This was the default solution in Django for its first few
years, but the problem is that it puts the onus on
you
, the developer /
template author, to ensure you’re escaping everything. It’s easy to forget
to escape data.
Two, you can take advantage of Django’s automatic HTML escaping. The remainder of this section describes how auto-escaping works.
By default in Django, every template automatically escapes the output of every variable tag. Specifically, these five characters are escaped:
<
is converted to
<
>
is converted to
>
'
(single quote) is converted to
'
"
(double quote) is converted to
"
&
is converted to
&
Again, we stress that this behavior is on by default. If you’re using Django’s template system, you’re protected.
If you don’t want data to be auto-escaped, on a per-site, per-template level or per-variable level, you can turn it off in several ways.
Why would you want to turn it off? Because sometimes, template variables contain data that you intend to be rendered as raw HTML, in which case you don’t want their contents to be escaped. For example, you might store a blob of trusted HTML in your database and want to embed that directly into your template. Or, you might be using Django’s template system to produce text that is not HTML – like an e-mail message, for instance.
To disable auto-escaping for an individual variable, use the
safe
filter:
This will be escaped: {{ data }}
This will not be escaped: {{ data|safe }}
Think of
safe
as shorthand for
safe from further escaping
or
can be
safely interpreted as HTML
. In this example, if
data
contains
'<b>'
,
the output will be:
This will be escaped: <b>
This will not be escaped: <b>
To control auto-escaping for a template, wrap the template (or just a
particular section of the template) in the
autoescape
tag, like so:
{% autoescape off %}
Hello {{ name }}
{% endautoescape %}
The
autoescape
tag takes either
on
or
off
as its argument. At
times, you might want to force auto-escaping when it would otherwise be
disabled. Here is an example template:
Auto-escaping is on by default. Hello {{ name }}
{% autoescape off %}
This will not be auto-escaped: {{ data }}.
Nor this: {{ other_data }}
{% autoescape on %}
Auto-escaping applies again: {{ name }}
{% endautoescape %}
{% endautoescape %}
The auto-escaping tag passes its effect on to templates that extend the
current one as well as templates included via the
include
tag, just like
all block tags. For example:
# base.html
{% autoescape off %}
<h1>{% block title %}{% endblock %}</h1>
{% block content %}
{% endblock %}
{% endautoescape %}
# child.html
{% extends "base.html" %}
{% block title %}This & that{% endblock %}
{% block content %}{{ greeting }}{% endblock %}
Because auto-escaping is turned off in the base template, it will also be
turned off in the child template, resulting in the following rendered
HTML when the
greeting
variable contains the string
<b>Hello!</b>
:
<h1>This & that</h1>
<b>Hello!</b>
Generally, template authors don’t need to worry about auto-escaping very much. Developers on the Python side (people writing views and custom filters) need to think about the cases in which data shouldn’t be escaped, and mark data appropriately, so things work in the template.
If you’re creating a template that might be used in situations where you’re
not sure whether auto-escaping is enabled, then add an
escape
filter to any
variable that needs escaping. When auto-escaping is on, there’s no danger of
the
escape
filter
double-escaping
data – the
escape
filter does not
affect auto-escaped variables.
As we mentioned earlier, filter arguments can be strings:
{{ data|default:"This is a string literal." }}
All string literals are inserted
without
any automatic escaping into the
template – they act as if they were all passed through the
safe
filter.
The reasoning behind this is that the template author is in control of what
goes into the string literal, so they can make sure the text is correctly
escaped when the template is written.
This means you would write
{{ data|default:"3 < 2" }}
…rather than
{{ data|default:"3 < 2" }} <-- Bad! Don't do this.
This doesn’t affect what happens to data coming from the variable itself. The variable’s contents are still automatically escaped, if necessary, because they’re beyond the control of the template author.
Generally, you’ll store templates in files on your filesystem, but you can use custom template loaders to load templates from other sources.
Django has two ways to load templates:
django.template.loader.get_template(template_name)
:
get_template
returns the compiled template (a
Template
object) for the template
with the given name. If the template doesn’t exist, a
TemplateDoesNotExist
exception will be raised.
django.template.loader.select_template(template_name_list)
:
select_template
is just like
get_template
, except it takes a list
of template names. Of the list, it returns the first template that exists.
If none of the templates exist, a
TemplateDoesNotExist
exception will
be raised.
As covered in Chapter 4, each of these functions by default uses your
TEMPLATE_DIRS
setting to load templates. Internally, however, these
functions actually delegate to a template loader for the heavy lifting.
Some of loaders are disabled by default, but you can activate them by editing
the
TEMPLATE_LOADERS
setting.
TEMPLATE_LOADERS
should be a tuple of
strings, where each string represents a template loader. These template loaders
ship with Django:
django.template.loaders.filesystem.load_template_source
: This loader
loads templates from the filesystem, according to
TEMPLATE_DIRS
. It is
enabled by default.
django.template.loaders.app_directories.load_template_source
: This
loader loads templates from Django applications on the filesystem. For
each application in
INSTALLED_APPS
, the loader looks for a
templates
subdirectory. If the directory exists, Django looks for
templates there.
This means you can store templates with your individual applications,
making it easy to distribute Django applications with default templates.
For example, if
INSTALLED_APPS
contains
('myproject.polls',
'myproject.music')
, then
get_template('foo.html')
will look for
templates in this order:
/path/to/myproject/polls/templates/foo.html
/path/to/myproject/music/templates/foo.html
Note that the loader performs an optimization when it is first imported:
it caches a list of which
INSTALLED_APPS
packages have a
templates
subdirectory.
This loader is enabled by default.
django.template.loaders.eggs.load_template_source
: This loader is just
like
app_directories
, except it loads templates from Python eggs
rather than from the filesystem. This loader is disabled by default;
you’ll need to enable it if you’re using eggs to distribute your
application. (Python eggs are a way of compressing Python code into a
single file.)
Django uses the template loaders in order according to the
TEMPLATE_LOADERS
setting. It uses each loader until a loader finds a match.
Now that you understand a bit more about the internals of the template system, let’s look at how to extend the system with custom code.
Most template customization comes in the form of custom template tags and/or filters. Although the Django template language comes with many built-in tags and filters, you’ll probably assemble your own libraries of tags and filters that fit your own needs. Fortunately, it’s quite easy to define your own functionality.
Whether you’re writing custom tags or filters, the first thing to do is to create a template library – a small bit of infrastructure Django can hook into.
Creating a template library is a two-step process:
First, decide which Django application should house the template library.
If you’ve created an app via
manage.py
startapp
, you can put it in
there, or you can create another app solely for the template library.
We’d recommend the latter, because your filters might be useful to you
in future projects.
Whichever route you take, make sure to add the app to your
INSTALLED_APPS
setting. We’ll explain this shortly.
Second, create a
templatetags
directory in the appropriate Django
application’s package. It should be on the same level as
models.py
,
views.py
, and so forth. For example:
books/
__init__.py
models.py
templatetags/
views.py
Create two empty files in the
templatetags
directory: an
__init__.py
file (to indicate to Python that this is a package containing Python code)
and a file that will contain your custom tag/filter definitions. The name
of the latter file is what you’ll use to load the tags later. For example,
if your custom tags/filters are in a file called
poll_extras.py
, you’d
write the following in a template:
{% load poll_extras %}
The
{%
load
%}
tag looks at your
INSTALLED_APPS
setting and only
allows the loading of template libraries within installed Django
applications. This is a security feature; it allows you to host Python
code for many template libraries on a single computer without enabling
access to all of them for every Django installation.
If you write a template library that isn’t tied to any particular models/views,
it’s valid and quite normal to have a Django application package that contains
only a
templatetags
package. There’s no limit on how many modules you put in
the
templatetags
package. Just keep in mind that a
{%
load
%}
statement
will load tags/filters for the given Python module name, not the name of the
application.
Once you’ve created that Python module, you’ll just have to write a bit of Python code, depending on whether you’re writing filters or tags.
To be a valid tag library, the module must contain a module-level variable named
register
that is an instance of
template.Library
. This is the data
structure in which all the tags and filters are registered. So, near the top of
your module, insert the following:
from django import template
register = template.Library()
Note
For a fine selection of examples, read the source code for Django’s default
filters and tags. They’re in
django/template/defaultfilters.py
and
django/template/defaulttags.py
, respectively. Some applications in
django.contrib
also contain template libraries.
Once you’ve created this
register
variable, you’ll use it to create template
filters and tags.
Custom filters are just Python functions that take one or two arguments:
The value of the variable (input)
The value of the argument, which can have a default value or be left out altogether
For example, in the filter
{{
var|foo:"bar"
}}
, the filter
foo
would be
passed the contents of the variable
var
and the argument
"bar"
.
Filter functions should always return something. They shouldn’t raise exceptions, and they should fail silently. If there’s an error, they should return either the original input or an empty string, whichever makes more sense.
Here’s an example filter definition:
def cut(value, arg):
"Removes all values of arg from the given string"
return value.replace(arg, '')
And here’s an example of how that filter would be used to cut spaces from a variable’s value:
{{ somevariable|cut:" " }}
Most filters don’t take arguments. In this case, just leave the argument out of your function:
def lower(value): # Only one argument.
"Converts a string into all lowercase"
return value.lower()
When you’ve written your filter definition, you need to register it with your
Library
instance, to make it available to Django’s template language:
register.filter('cut', cut)
register.filter('lower', lower)
The
Library.filter()
method takes two arguments:
The name of the filter (a string)
The filter function itself
If you’re using Python 2.4 or above, you can use
register.filter()
as a
decorator instead:
@register.filter(name='cut')
def cut(value, arg):
return value.replace(arg, '')
@register.filter
def lower(value):
return value.lower()
If you leave off the
name
argument, as in the second example, Django
will use the function’s name as the filter name.
Here, then, is a complete template library example, supplying the
cut
filter:
from django import template
register = template.Library()
@register.filter(name='cut')
def cut(value, arg):
return value.replace(arg, '')
For each template tag the parser encounters, it calls a Python function with
the tag contents and the parser object itself. This function is responsible for
returning a
Node
instance based on the contents of the tag.
For example, let’s write a template tag,
{%
current_time
%}
, that displays
the current date/time, formatted according to a parameter given in the tag, in
strftime
syntax (see
http://www.djangoproject.com/r/python/strftime/
).
It’s a good idea to decide the tag syntax before anything else. In our case,
let’s say the tag should be used like this:
<p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p>
Note
Yes, this template tag is redundant–Django’s default
{%
now
%}
tag does
the same task with simpler syntax. This template tag is presented here just
for example purposes.
The parser for this function should grab the parameter and create a
Node
object:
from django import template
register = template.Library()
def do_current_time(parser, token):
try:
# split_contents() knows not to split quoted strings.
tag_name, format_string = token.split_contents()
except ValueError:
msg = '%r tag requires a single argument' % token.split_contents()[0]
raise template.TemplateSyntaxError(msg)
return CurrentTimeNode(format_string[1:-1])
There’s a lot going here:
Each template tag compilation function takes two arguments,
parser
and
token
.
parser
is the template parser object. We don’t use it
in this example.
token
is the token currently being parsed by the
parser.
token.contents
is a string of the raw contents of the tag. In our
example, it’s
'current_time
"%Y-%m-%d
%I:%M
%p"'
.
The
token.split_contents()
method separates the arguments on spaces
while keeping quoted strings together. Avoid using
token.contents.split()
(which just uses Python’s standard
string-splitting semantics). It’s not as robust, as it naively splits on
all
spaces, including those within quoted strings.
This function is responsible for raising
django.template.TemplateSyntaxError
, with helpful messages, for any
syntax error.
Don’t hard-code the tag’s name in your error messages, because that
couples the tag’s name to your function.
token.split_contents()[0]
will
always
be the name of your tag – even when the tag has no
arguments.
The function returns a
CurrentTimeNode
(which we’ll create shortly)
containing everything the node needs to know about this tag. In this
case, it just passes the argument
"%Y-%m-%d
%I:%M
%p"
. The
leading and trailing quotes from the template tag are removed with
format_string[1:-1]
.
Template tag compilation functions
must
return a
Node
subclass;
any other return value is an error.
The second step in writing custom tags is to define a
Node
subclass that
has a
render()
method. Continuing the preceding example, we need to define
CurrentTimeNode
:
import datetime
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)
These two functions (
__init__()
and
render()
) map directly to the two
steps in template processing (compilation and rendering). Thus, the
initialization function only needs to store the format string for later use,
and the
render()
function does the real work.
Like template filters, these rendering functions should fail silently instead of raising errors. The only time that template tags are allowed to raise errors is at compilation time.
Finally, you need to register the tag with your module’s
Library
instance.
Registering custom tags is very similar to registering custom filters (as
explained above). Just instantiate a
template.Library
instance and call
its
tag()
method. For example:
register.tag('current_time', do_current_time)
The
tag()
method takes two arguments:
The name of the template tag (string).
The compilation function.
As with filter registration, it is also possible to use
register.tag
as a
decorator in Python 2.4 and above:
@register.tag(name="current_time")
def do_current_time(parser, token):
# ...
@register.tag
def shout(parser, token):
# ...
If you leave off the
name
argument, as in the second example, Django
will use the function’s name as the tag name.
The previous section’s example simply returned a value. Often it’s useful to set template variables instead of returning values. That way, template authors can just use the variables that your template tags set.
To set a variable in the context, use dictionary assignment on the context
object in the
render()
method. Here’s an updated version of
CurrentTimeNode
that sets a template variable,
current_time
, instead of
returning it:
class CurrentTimeNode2(template.Node):
def __init__(self, format_string):
self.format_string = str(format_string)
def render(self, context):
now = datetime.datetime.now()
context['current_time'] = now.strftime(self.format_string)
return ''
(We’ll leave the creation of a
do_current_time2
function, plus the
registration of that function to a
current_time2
template tag, as exercises
for the reader.)
Note that
render()
returns an empty string.
render()
should always
return a string, so if all the template tag does is set a variable,
render()
should return an empty string.
Here’s how you’d use this new version of the tag:
{% current_time2 "%Y-%M-%d %I:%M %p" %}
<p>The time is {{ current_time }}.</p>
But there’s a problem with
CurrentTimeNode2
: the variable name
current_time
is hard-coded. This means you’ll need to make sure your
template doesn’t use
{{
current_time
}}
anywhere else, because
{%
current_time2
%}
will blindly overwrite that variable’s value.
A cleaner solution is to make the template tag specify the name of the variable to be set, like so:
{% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}
<p>The current time is {{ my_current_time }}.</p>
To do so, you’ll need to refactor both the compilation function and the
Node
class, as follows:
import re
class CurrentTimeNode3(template.Node):
def __init__(self, format_string, var_name):
self.format_string = str(format_string)
self.var_name = var_name
def render(self, context):
now = datetime.datetime.now()
context[self.var_name] = now.strftime(self.format_string)
return ''
def do_current_time(parser, token):
# This version uses a regular expression to parse tag contents.
try:
# Splitting by None == splitting by spaces.
tag_name, arg = token.contents.split(None, 1)
except ValueError:
msg = '%r tag requires arguments' % token.contents[0]
raise template.TemplateSyntaxError(msg)
m = re.search(r'(.*?) as (\w+)', arg)
if m:
fmt, var_name = m.groups()
else:
msg = '%r tag had invalid arguments' % tag_name
raise template.TemplateSyntaxError(msg)
if not (fmt[0] == fmt[-1] and fmt[0] in ('"', "'")):
msg = "%r tag's argument should be in quotes" % tag_name
raise template.TemplateSyntaxError(msg)
return CurrentTimeNode3(fmt[1:-1], var_name)
Now
do_current_time()
passes the format string and the variable name to
CurrentTimeNode3
.
Template tags can work as blocks containing other tags (like
{%
if
%}
,
{%
for
%}
, etc.). To create a template tag like this, use
parser.parse()
in your compilation function.
Here’s how the standard
{%
comment
%}
tag is implemented:
def do_comment(parser, token):
nodelist = parser.parse(('endcomment',))
parser.delete_first_token()
return CommentNode()
class CommentNode(template.Node):
def render(self, context):
return ''
parser.parse()
takes a tuple of names of template tags to parse until. It
returns an instance of
django.template.NodeList
, which is a list of all
Node
objects that the parser encountered
before
it encountered any of
the tags named in the tuple.
So in the preceding example,
nodelist
is a list of all nodes between
{%
comment
%}
and
{%
endcomment
%}
, not counting
{%
comment
%}
and
{%
endcomment
%}
themselves.
After
parser.parse()
is called, the parser hasn’t yet “consumed” the
{%
endcomment
%}
tag, so the code needs to explicitly call
parser.delete_first_token()
to prevent that tag from being processed
twice.
Then
CommentNode.render()
simply returns an empty string. Anything
between
{%
comment
%}
and
{%
endcomment
%}
is ignored.
In the previous example,
do_comment()
discarded everything between
{%
comment
%}
and
{%
endcomment
%}
. It’s also
possible to do something with the code between template tags instead.
For example, here’s a custom template tag,
{%
upper
%}
, that capitalizes
everything between itself and
{%
endupper
%}
:
{% upper %}
This will appear in uppercase, {{ user_name }}.
{% endupper %}
As in the previous example, we’ll use
parser.parse()
. This time, we
pass the resulting
nodelist
to
Node
:
def do_upper(parser, token):
nodelist = parser.parse(('endupper',))
parser.delete_first_token()
return UpperNode(nodelist)
class UpperNode(template.Node):
def __init__(self, nodelist):
self.nodelist = nodelist
def render(self, context):
output = self.nodelist.render(context)
return output.upper()
The only new concept here is
self.nodelist.render(context)
in
UpperNode.render()
. This simply calls
render()
on each
Node
in the
node list.
For more examples of complex rendering, see the source code for
{%
if
%}
,
{%
for
%}
,
{%
ifequal
%}
, and
{%
ifchanged
%}
. They live in
django/template/defaulttags.py
.
Django’s built-in template loaders (described in the “Inside Template Loading” section above) will usually cover all your template-loading needs, but it’s pretty easy to write your own if you need special loading logic. For example, you could load templates from a database, or directly from a Subversion repository using Subversion’s Python bindings, or (as shown shortly) from a ZIP archive.
A template loader – that is, each entry in the
TEMPLATE_LOADERS
setting
– is expected to be a callable object with this interface:
load_template_source(template_name, template_dirs=None)
The
template_name
argument is the name of the template to load (as passed
to
loader.get_template()
or
loader.select_template()
), and
template_dirs
is an optional list of directories to search instead of
TEMPLATE_DIRS
.
If a loader is able to successfully load a template, it should return a tuple:
(template_source,
template_path)
. Here,
template_source
is the
template string that will be compiled by the template engine, and
template_path
is the path the template was loaded from. That path might be
shown to the user for debugging purposes, so it should quickly identify where
the template was loaded from.
If the loader is unable to load a template, it should raise
django.template.TemplateDoesNotExist
.
Each loader function should also have an
is_usable
function attribute.
This is a Boolean that informs the template engine whether this loader
is available in the current Python installation. For example, the eggs loader
(which is capable of loading templates from Python eggs) sets
is_usable
to
False
if the
pkg_resources
module isn’t installed, because
pkg_resources
is necessary to read data from eggs.
An example should help clarify all of this. Here’s a template loader function
that can load templates from a ZIP file. It uses a custom setting,
TEMPLATE_ZIP_FILES
, as a search path instead of
TEMPLATE_DIRS
, and it
expects each item on that path to be a ZIP file containing templates:
from django.conf import settings
from django.template import TemplateDoesNotExist
import zipfile
def load_template_source(template_name, template_dirs=None):
"Template loader that loads templates from a ZIP file."
template_zipfiles = getattr(settings, "TEMPLATE_ZIP_FILES", [])
# Try each ZIP file in TEMPLATE_ZIP_FILES.
for fname in template_zipfiles:
try:
z = zipfile.ZipFile(fname)
source = z.read(template_name)
except (IOError, KeyError):
continue
z.close()
# We found a template, so return the source.
template_path = "%s:%s" % (fname, template_name)
return (source, template_path)
# If we reach here, the template couldn't be loaded
raise TemplateDoesNotExist(template_name)
# This loader is always usable (since zipfile is included with Python)
load_template_source.is_usable = True
The only step left if we want to use this loader is to add it to the
TEMPLATE_LOADERS
setting. If we put this code in a package called
mysite.zip_loader
, then we add
mysite.zip_loader.load_template_source
to
TEMPLATE_LOADERS
.
Note
This section is only of interest to people trying to use the template system as an output component in another application. If you are using the template system as part of a Django application, the information presented here doesn’t apply to you.
Normally, Django loads all the configuration information it needs from its own
default configuration file, combined with the settings in the module given
in the
DJANGO_SETTINGS_MODULE
environment variable. (This was explained in
“A special Python prompt” in Chapter 4.) But if you’re using the template
system independently of the rest of Django, the environment variable approach
isn’t very convenient, because you probably want to configure the template
system in line with the rest of your application rather than dealing with
settings files and pointing to them via environment variables.
To solve this problem, you need to use the manual configuration option described
fully in Appendix D. In a nutshell, you need to import the appropriate pieces of
the template system and then,
before
you call any of the template functions,
call
django.conf.settings.configure()
with any settings you wish to specify.
You might want to consider setting at least
TEMPLATE_DIRS
(if you are
going to use template loaders),
DEFAULT_CHARSET
(although the default of
utf-8
is probably fine) and
TEMPLATE_DEBUG
. All available settings are
described in Appendix D, and any setting starting with
TEMPLATE_
is of
obvious interest.
Continuing this section’s theme of advanced topics, the next chapter covers advanced usage of Django models.