How to create custom template tags and filters¶
Django’s template language comes with a wide variety of built-in
tags and filters designed to address the
presentation logic needs of your application. Nevertheless, you may
find yourself needing functionality that is not covered by the core
set of template primitives. You can extend the template engine by
defining custom tags and filters using Python, and then make them
available to your templates using the {% load %}
tag.
Code layout¶
The most common place to specify custom template tags and filters is inside
a Django app. If they relate to an existing app, it makes sense to bundle them
there; otherwise, they can be added to a new app. When a Django app is added
to INSTALLED_APPS
, any tags it defines in the conventional location
described below are automatically made available to load within templates.
The app should contain a templatetags
directory, at the same level as
models.py
, views.py
, etc. If this doesn’t already exist, create it -
don’t forget the __init__.py
file to ensure the directory is treated as a
Python package.
Development server won’t automatically restart
After adding the templatetags
module, you will need to restart your
server before you can use the tags or filters in templates.
Your custom tags and filters will live in a module inside the templatetags
directory. The name of the module file is the name you’ll use to load the tags
later, so be careful to pick a name that won’t clash with custom tags and
filters in another app.
For example, if your custom tags/filters are in a file called
poll_extras.py
, your app layout might look like this:
polls/
__init__.py
models.py
templatetags/
__init__.py
poll_extras.py
views.py
And in your template you would use the following:
{% load poll_extras %}
The app that contains the custom tags must be in INSTALLED_APPS
in
order for the {% load %}
tag to work. This is a security feature:
It allows you to host Python code for many template libraries on a single host
machine without enabling access to all of them for every Django installation.
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 app.
To be a valid tag library, the module must contain a module-level variable
named register
that is a template.Library
instance, in which all the
tags and filters are registered. So, near the top of your module, put the
following:
from django import template
register = template.Library()
Alternatively, template tag modules can be registered through the
'libraries'
argument to
DjangoTemplates
. This is useful if
you want to use a different label from the template tag module name when
loading template tags. It also enables you to register tags without installing
an application.
Detrás de escenas
For a ton 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.
For more information on the load
tag, read its documentation.
Escribir filtros plantilla de personalizada¶
Custom filters are Python functions that take one or two arguments:
The value of the variable (input) – not necessarily a string.
The value of the argument – this 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 variable var
and the argument "bar"
.
Since the template language doesn’t provide exception handling, any exception raised from a template filter will be exposed as a server error. Thus, filter functions should avoid raising exceptions if there is a reasonable fallback value to return. In case of input that represents a clear bug in a template, raising an exception may still be better than silent failure which hides the bug.
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:
{{ somevariable|cut:"0" }}
Most filters don’t take arguments. In this case, leave the argument out of your function:
def lower(value): # Only one argument.
"""Converts a string into all lowercase"""
return value.lower()
Registering custom filters¶
- django.template.Library.filter()¶
Once 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 compilation function – a Python function (not the name of the function as a string).
Puede usar register.filter()
como decorador en su lugar:
@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 above, Django
will use the function’s name as the filter name.
Finally, register.filter()
also accepts three keyword arguments,
is_safe
, needs_autoescape
, and expects_localtime
. These arguments
are described in filters and auto-escaping and
filters and time zones below.
Filtros de plantilla que esperan cadenas¶
- django.template.defaultfilters.stringfilter()¶
If you’re writing a template filter that only expects a string as the first
argument, you should use the decorator stringfilter
. This will
convert an object to its string value before being passed to your function:
from django import template
from django.template.defaultfilters import stringfilter
register = template.Library()
@register.filter
@stringfilter
def lower(value):
return value.lower()
This way, you’ll be able to pass, say, an integer to this filter, and it
won’t cause an AttributeError
(because integers don’t have lower()
methods).
Filters and auto-escaping¶
When writing a custom filter, give some thought to how the filter will interact with Django’s auto-escaping behavior. Note that two types of strings can be passed around inside the template code:
Raw strings are the native Python strings. On output, they’re escaped if auto-escaping is in effect and presented unchanged, otherwise.
Safe strings are strings that have been marked safe from further escaping at output time. Any necessary escaping has already been done. They’re commonly used for output that contains raw HTML that is intended to be interpreted as-is on the client side.
Internally, these strings are of type
SafeString
. You can test for them using code like:from django.utils.safestring import SafeString if isinstance(value, SafeString): # Do something with the "safe" string. ...
Template filter code falls into one of two situations:
Your filter does not introduce any HTML-unsafe characters (
<
,>
,'
,"
or&
) into the result that were not already present. In this case, you can let Django take care of all the auto-escaping handling for you. All you need to do is set theis_safe
flag toTrue
when you register your filter function, like so:@register.filter(is_safe=True) def myfilter(value): return value
This flag tells Django that if a «safe» string is passed into your filter, the result will still be «safe» and if a non-safe string is passed in, Django will automatically escape it, if necessary.
You can think of this as meaning «this filter is safe – it doesn’t introduce any possibility of unsafe HTML.»
The reason
is_safe
is necessary is because there are plenty of normal string operations that will turn aSafeData
object back into a normalstr
object and, rather than try to catch them all, which would be very difficult, Django repairs the damage after the filter has completed.For example, suppose you have a filter that adds the string
xx
to the end of any input. Since this introduces no dangerous HTML characters to the result (aside from any that were already present), you should mark your filter withis_safe
:@register.filter(is_safe=True) def add_xx(value): return "%sxx" % value
When this filter is used in a template where auto-escaping is enabled, Django will escape the output whenever the input is not already marked as «safe».
Por defecto,
is_safe
esFalse
, y puede omitirlo en cualquier filtro en el que no sea necesario.Be careful when deciding if your filter really does leave safe strings as safe. If you’re removing characters, you might inadvertently leave unbalanced HTML tags or entities in the result. For example, removing a
>
from the input might turn<a>
into<a
, which would need to be escaped on output to avoid causing problems. Similarly, removing a semicolon (;
) can turn&
into&
, which is no longer a valid entity and thus needs further escaping. Most cases won’t be nearly this tricky, but keep an eye out for any problems like that when reviewing your code.Marking a filter
is_safe
will coerce the filter’s return value to a string. If your filter should return a boolean or other non-string value, marking itis_safe
will probably have unintended consequences (such as converting a boolean False to the string “False”).Alternatively, your filter code can manually take care of any necessary escaping. This is necessary when you’re introducing new HTML markup into the result. You want to mark the output as safe from further escaping so that your HTML markup isn’t escaped further, so you’ll need to handle the input yourself.
To mark the output as a safe string, use
django.utils.safestring.mark_safe()
.Be careful, though. You need to do more than just mark the output as safe. You need to ensure it really is safe, and what you do depends on whether auto-escaping is in effect. The idea is to write filters that can operate in templates where auto-escaping is either on or off in order to make things easier for your template authors.
In order for your filter to know the current auto-escaping state, set the
needs_autoescape
flag toTrue
when you register your filter function. (If you don’t specify this flag, it defaults toFalse
). This flag tells Django that your filter function wants to be passed an extra keyword argument, calledautoescape
, that isTrue
if auto-escaping is in effect andFalse
otherwise. It is recommended to set the default of theautoescape
parameter toTrue
, so that if you call the function from Python code it will have escaping enabled by default.Por ejemplo, escribamos un filtro que enfatiza el primer carácter de una cadena:
from django import template from django.utils.html import conditional_escape from django.utils.safestring import mark_safe register = template.Library() @register.filter(needs_autoescape=True) def initial_letter_filter(text, autoescape=True): first, other = text[0], text[1:] if autoescape: esc = conditional_escape else: esc = lambda x: x result = "<strong>%s</strong>%s" % (esc(first), esc(other)) return mark_safe(result)
The
needs_autoescape
flag and theautoescape
keyword argument mean that our function will know whether automatic escaping is in effect when the filter is called. We useautoescape
to decide whether the input data needs to be passed throughdjango.utils.html.conditional_escape
or not. (In the latter case, we use the identity function as the «escape» function.) Theconditional_escape()
function is likeescape()
except it only escapes input that is not aSafeData
instance. If aSafeData
instance is passed toconditional_escape()
, the data is returned unchanged.Finally, in the above example, we remember to mark the result as safe so that our HTML is inserted directly into the template without further escaping.
There’s no need to worry about the
is_safe
flag in this case (although including it wouldn’t hurt anything). Whenever you manually handle the auto-escaping issues and return a safe string, theis_safe
flag won’t change anything either way.
Advertencia
Avoiding XSS vulnerabilities when reusing built-in filters
Django’s built-in filters have autoescape=True
by default in order to
get the proper autoescaping behavior and avoid a cross-site script
vulnerability.
In older versions of Django, be careful when reusing Django’s built-in
filters as autoescape
defaults to None
. You’ll need to pass
autoescape=True
to get autoescaping.
For example, if you wanted to write a custom filter called
urlize_and_linebreaks
that combined the urlize
and
linebreaksbr
filters, the filter would look like:
from django.template.defaultfilters import linebreaksbr, urlize
@register.filter(needs_autoescape=True)
def urlize_and_linebreaks(text, autoescape=True):
return linebreaksbr(urlize(text, autoescape=autoescape), autoescape=autoescape)
Entonces:
{{ comment|urlize_and_linebreaks }}
sería equivalente a:
{{ comment|urlize|linebreaksbr }}
Filtros y zonas horarias¶
If you write a custom filter that operates on datetime
objects, you’ll usually register it with the expects_localtime
flag set to
True
:
@register.filter(expects_localtime=True)
def businesshours(value):
try:
return 9 <= value.hour < 17
except AttributeError:
return ""
When this flag is set, if the first argument to your filter is a time zone aware datetime, Django will convert it to the current time zone before passing it to your filter when appropriate, according to rules for time zones conversions in templates.
Escribir etiquetas de plantilla de personalizadas¶
Tags are more complex than filters, because tags can do anything. Django provides a number of shortcuts that make writing most types of tags easier. First we’ll explore those shortcuts, then explain how to write a tag from scratch for those cases when the shortcuts aren’t powerful enough.
Etiquetas simples¶
- django.template.Library.simple_tag()¶
Many template tags take a number of arguments – strings or template variables
– and return a result after doing some processing based solely on
the input arguments and some external information. For example, a
current_time
tag might accept a format string and return the time as a
string formatted accordingly.
To ease the creation of these types of tags, Django provides a helper function,
simple_tag
. This function, which is a method of
django.template.Library
, takes a function that accepts any number of
arguments, wraps it in a render
function and the other necessary bits
mentioned above and registers it with the template system.
Nuestra función current_time
podría por consiguiente escribirse así:
import datetime
from django import template
register = template.Library()
@register.simple_tag
def current_time(format_string):
return datetime.datetime.now().strftime(format_string)
Algunas cosas a tener en cuenta sobre la función auxiliar simple_tag
:
Checking for the required number of arguments, etc., has already been done by the time our function is called, so we don’t need to do that.
The quotes around the argument (if any) have already been stripped away, so we receive a plain string.
If the argument was a template variable, our function is passed the current value of the variable, not the variable itself.
Unlike other tag utilities, simple_tag
passes its output through
conditional_escape()
if the template context is in
autoescape mode, to ensure correct HTML and protect you from XSS
vulnerabilities.
If additional escaping is not desired, you will need to use
mark_safe()
if you are absolutely sure that your
code does not contain XSS vulnerabilities. For building small HTML snippets,
use of format_html()
instead of mark_safe()
is
strongly recommended.
If your template tag needs to access the current context, you can use the
takes_context
argument when registering your tag:
@register.simple_tag(takes_context=True)
def current_time(context, format_string):
timezone = context["timezone"]
return your_get_current_time_method(timezone, format_string)
Note that the first argument must be called context
.
For more information on how the takes_context
option works, see the section
on inclusion tags.
If you need to rename your tag, you can provide a custom name for it:
register.simple_tag(lambda x: x - 1, name="minusone")
@register.simple_tag(name="minustwo")
def some_function(value):
return value - 2
simple_tag
functions may accept any number of positional or keyword
arguments. For example:
@register.simple_tag
def my_tag(a, b, *args, **kwargs):
warning = kwargs["warning"]
profile = kwargs["profile"]
...
return ...
Then in the template any number of arguments, separated by spaces, may be
passed to the template tag. Like in Python, the values for keyword arguments
are set using the equal sign (»=
») and must be provided after the
positional arguments. For example:
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
It’s possible to store the tag results in a template variable rather than
directly outputting it. This is done by using the as
argument followed by
the variable name. Doing so enables you to output the content yourself where
you see fit:
{% current_time "%Y-%m-%d %I:%M %p" as the_time %}
<p>The time is {{ the_time }}.</p>
Inclusion tags¶
- django.template.Library.inclusion_tag()¶
Another common type of template tag is the type that displays some data by rendering another template. For example, Django’s admin interface uses custom template tags to display the buttons along the bottom of the «add/change» form pages. Those buttons always look the same, but the link targets change depending on the object being edited – so they’re a perfect case for using a small template that is filled w