Introduction to Custom Template Filters and Tags

This article is written by Aidas Bendoratis, the author of Web Development with Django Cookbook. In this article, we will cover the following recipes:

  • Following conventions for your own template filters and tags
  • Creating a template filter to show how many days have passed
  • Creating a template filter to extract the first media object
  • Creating a template filter to humanize URLs
  • Creating a template tag to include a template if it exists
  • Creating a template tag to load a QuerySet in a template
  • Creating a template tag to parse content as a template
  • Creating a template tag to modify request query parameters

As you know, Django has quite an extensive template system, with features such as template inheritance, filters for changing the representation of values, and tags for presentational logic. Moreover, Django allows you to add your own template filters and tags in your apps. Custom filters or tags should be located in a template-tag library file under the templatetags Python package in your app. Your template-tag library can then be loaded in any template with a {% load %} template tag. In this article, we will create several useful filters and tags that give more control to the template editors.

Following conventions for your own template filters and tags

Custom template filters and tags can become a total mess if you don't have persistent guidelines to follow. Template filters and tags should serve template editors as much as possible. They should be both handy and flexible. In this recipe, we will look at some conventions that should be used when enhancing the functionality of the Django template system.

How to do it...

Follow these conventions when extending the Django template system:

  1. Don't create or use custom template filters or tags when the logic for the page fits better in the view, context processors, or in model methods. When your page is context-specific, such as a list of objects or an object-detail view, load the object in the view. If you need to show some content on every page, create a context processor. Use custom methods of the model instead of template filters when you need to get some properties of an object not related to the context of the template.
  2. Name the template-tag library with the _tags suffix. When your app is named differently than your template-tag library, you can avoid ambiguous package importing problems.
  3. In the newly created library, separate filters from tags, for example, by using comments such as the following:
    # -*- coding: UTF-8 -*-
    from django import template
    register = template.Library()
    ### FILTERS ###
    # .. your filters go here..
    ### TAGS ###
    # .. your tags go here..
  4. Create template tags that are easy to remember by including the following constructs:
    • for [app_name.model_name]: Include this construct to use a specific model
    • using [template_name]: Include this construct to use a template for the output of the template tag
    • limit [count]: Include this construct to limit the results to a specific amount
    • as [context_variable]: Include this construct to save the results to a context variable that can be reused many times later
  5. Try to avoid multiple values defined positionally in template tags unless they are self-explanatory. Otherwise, this will likely confuse the template developers.
  6. Make as many arguments resolvable as possible. Strings without quotes should be treated as context variables that need to be resolved or as short words that remind you of the structure of the template tag components.

See also

  • The Creating a template filter to show how many days have passed recipe
  • The Creating a template filter to extract the first media object recipe
  • The Creating a template filter to humanize URLs recipe
  • The Creating a template tag to include a template if it exists recipe
  • The Creating a template tag to load a QuerySet in a template recipe
  • The Creating a template tag to parse content as a template recipe
  • The Creating a template tag to modify request query parameters recipe

Creating a template filter to show how many days have passed

Not all people keep track of the date, and when talking about creation or modification dates of cutting-edge information, for many of us, it is more convenient to read the time difference, for example, the blog entry was posted three days ago, the news article was published today, and the user last logged in yesterday. In this recipe, we will create a template filter named days_since that converts dates to humanized time differences.

Getting ready

Create the utils app and put it under INSTALLED_APPS in the settings, if you haven't done that yet. Then, create a Python package named templatetags inside this app (Python packages are directories with an empty __init__.py file).

How to do it...

Create a utility_tags.py file with this content:

#utils/templatetags/utility_tags.py
# -*- coding: UTF-8 -*-
from datetime import datetime
from django import template
from django.utils.translation import ugettext_lazy as _
from django.utils.timezone import now as tz_now
register = template.Library()
### FILTERS ###
@register.filter
def days_since(value):
""" Returns number of days between today and value."""
today = tz_now().date()
if isinstance(value, datetime.datetime):
value = value.date()
diff = today - value
if diff.days > 1:
return _("%s days ago") % diff.days
elif diff.days == 1:
return _("yesterday")
elif diff.days == 0:
return _("today")
else:
# Date is in the future; return formatted date.
return value.strftime("%B %d, %Y")

How it works...

If you use this filter in a template like the following, it will render something like yesterday or 5 days ago:

{% load utility_tags %}
{{ object.created|days_since }}

You can apply this filter to the values of the date and datetime types.

Each template-tag library has a register where filters and tags are collected. Django filters are functions registered by the register.filter decorator. By default, the filter in the template system will be named the same as the function or the other callable object. If you want, you can set a different name for the filter by passing name to the decorator, as follows:

@register.filter(name="humanized_days_since")
def days_since(value):
...

The filter itself is quite self-explanatory. At first, the current date is read. If the given value of the filter is of the datetime type, the date is extracted. Then, the difference between today and the extracted value is calculated. Depending on the number of days, different string results are returned.

There's more...

This filter is easy to extend to also show the difference in time, such as just now, 7 minutes ago, or 3 hours ago. Just operate the datetime values instead of the date values.

See also

  • The Creating a template filter to extract the first media object recipe
  • The Creating a template filter to humanize URLs recipe

Creating a template filter to extract the first media object

Imagine that you are developing a blog overview page, and for each post, you want to show images, music, or videos in that page taken from the content. In such a case, you need to extract the <img>, <object>, and <embed> tags out of the HTML content of the post. In this recipe, we will see how to do this using regular expressions in the get_first_media filter.

Getting ready

We will start with the utils app that should be set in INSTALLED_APPS in the settings and the templatetags package inside this app.

How to do it...

In the utility_tags.py file, add the following content:

#utils/templatetags/utility_tags.py
# -*- coding: UTF-8 -*-
import re
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
### FILTERS ###
media_file_regex = re.compile(r"<object .+?</object>|"
r"<(img|embed) [^>]+>") )
@register.filter
def get_first_media(content):
""" Returns the first image or flash file from the html
content """
m = media_file_regex.search(content)
media_tag = ""
if m:
media_tag = m.group()
return mark_safe(media_tag)

How it works...

While the HTML content in the database is valid, when you put the following code in the template, it will retrieve the <object>, <img>, or <embed> tags from the content field of the object, or an empty string if no media is found there:

{% load utility_tags %}

{{ object.content|get_first_media }}

At first, we define the compiled regular expression as media_file_regex, then in the filter, we perform a search for that regular expression pattern. By default, the result will show the <, >, and & symbols escaped as &lt;, &gt;, and &amp; entities. But we use the mark_safe function that marks the result as safe HTML ready to be shown in the template without escaping.

There's more...

It is very easy to extend this filter to also extract the <iframe> tags (which are more recently being used by Vimeo and YouTube for embedded videos) or the HTML5 <audio> and <video> tags. Just modify the regular expression like this:

media_file_regex = re.compile(r"<iframe .+?</iframe>|"
r"<audio .+?</ audio>|<video .+?</video>|"
r"<object .+?</object>|<(img|embed) [^>]+>")

See also

  • The Creating a template filter to show how many days have passed recipe
  • The Creating a template filter to humanize URLs recipe

Creating a template filter to humanize URLs

Usually, common web users enter URLs into address fields without protocol and trailing slashes. In this recipe, we will create a humanize_url filter used to present URLs to the user in a shorter format, truncating very long addresses, just like what Twitter does with the links in tweets.

Getting ready

As in the previous recipes, we will start with the utils app that should be set in INSTALLED_APPS in the settings, and should contain the templatetags package.

How to do it...

In the FILTERS section of the utility_tags.py template library in the utils app, let's add a filter named humanize_url and register it:

#utils/templatetags/utility_tags.py
# -*- coding: UTF-8 -*-
import re
from django import template
register = template.Library()
### FILTERS ###
@register.filter
def humanize_url(url, letter_count):
""" Returns a shortened human-readable URL """
letter_count = int(letter_count)
re_start = re.compile(r"^https?://")
re_end = re.compile(r"/$")
url = re_end.sub("", re_start.sub("", url))
if len(url) > letter_count:
url = u"%sā€¦" % url[:letter_count - 1]
return url

How it works...

We can use the humanize_url filter in any template like this:

{% load utility_tags %}
<a href="{{ object.website }}" target="_blank">
{{ object.website|humanize_url:30 }}
</a>

The filter uses regular expressions to remove the leading protocol and the trailing slash, and then shortens the URL to the given amount of letters, adding an ellipsis to the end if the URL doesn't fit into the specified letter count.

See also

  • The Creating a template filter to show how many days have passed recipe
  • The Creating a template filter to extract the first media object recipe
  • The Creating a template tag to include a template if it exists recipe

Creating a template tag to include a template if it exists

Django has the {% include %} template tag that renders and includes another template. However, in some particular situations, there is a problem that an error is raised if the template does not exist. In this recipe, we will show you how to create a {% try_to_include %} template tag that includes another template, but fails silently if there is no such template.

Getting ready

We will start again with the utils app that should be installed and is ready for custom template tags.

How to do it...

Template tags consist of two things: the function parsing the arguments of the template tag and the node class that is responsible for the logic of the template tag as well as for the output. Perform the following steps:

  1. First, let's create the function parsing the template-tag arguments:
    #utils/templatetags/utility_tags.py
    # -*- coding: UTF-8 -*-
    from django import template
    from django.template.loader import get_template
    register = template.Library()
    ### TAGS ###
    @register.tag
    def try_to_include(parser, token):
    """Usage: {% try_to_include "sometemplate.html" %}
    This will fail silently if the template doesn't exist.
    If it does, it will be rendered with the current context."""
    try:
    tag_name, template_name = token.split_contents()
    except ValueError:
    raise template.TemplateSyntaxError, \
    "%r tag requires a single argument" % \
    token.contents.split()[0]
    return IncludeNode(template_name)
  2. Then, we need the node class in the same file, as follows:
    class IncludeNode(template.Node):
    def __init__(self, template_name):
    self.template_name = template_name
    def render(self, context):
    try:
    # Loading the template and rendering it
    template_name = template.resolve_variable(
    self. template_name, context)
    included_template = get_template(template_name).\
    render(context)
    except template.TemplateDoesNotExist:
    included_template = ""
    return included_template

How it works...

The {% try_to_include %} template tag expects one argument, that is, template_name. So, in the try_to_include function, we are trying to assign the split contents of the token only to the tag_name variable (which is "try_to_include") and the template_name variable. If this doesn't work, the template syntax error is raised. The function returns the IncludeNode object, which gets the template_name field for later usage.

In the render method of IncludeNode, we resolve the template_name variable. If a context variable was passed to the template tag, then its value will be used here for template_name. If a quoted string was passed to the template tag, then the content within quotes will be used for template_name.

Lastly, we try to load the template and render it with the current template context. If that doesn't work, an empty string is returned.

There are at least two situations where we could use this template tag:

  • When including a template whose path is defined in a model, as follows:
    {% load utility_tags %}
    {% try_to_include object.template_path %}
  • When including a template whose path is defined with the {% with %} template tag somewhere high in the template context variable's scope. This is especially useful when you need to create custom layouts for plugins in the placeholder of a template in Django CMS:
    #templates/cms/start_page.html
    {% with editorial_content_template_path=
    "cms/plugins/editorial_content/start_page.html" %}
    {% placeholder "main_content" %}
    {% endwith %}
    #templates/cms/plugins/editorial_content.html
    {% load utility_tags %}
    {% if editorial_content_template_path %}
    {% try_to_include editorial_content_template_path %}
    {% else %}
    <div>
    <!-- Some default presentation of
    editorial content plugin -->
    </div>
    {% endif %

There's more...

You can use the {% try_to_include %} tag as well as the default {% include %} tag to include templates that extend other templates. This has a beneficial use for large-scale portals where you have different kinds of lists in which complex items share the same structure as widgets but have a different source of data.

For example, in the artist list template, you can include the artist item template as follows:

{% load utility_tags %}
{% for object in object_list %}
{% try_to_include "artists/includes/artist_item.html" %}
{% endfor %}

This template will extend from the item base as follows:

{# templates/artists/includes/artist_item.html #}
{% extends "utils/includes/item_base.html" %}
 {% block item_title %}
{{ object.first_name }} {{ object.last_name }}
{% endblock %}

The item base defines the markup for any item and also includes a Like widget, as follows:

{# templates/utils/includes/item_base.html #}
{% load likes_tags %}
<h3>{% block item_title %}{% endblock %}</h3>
{% if request.user.is_authenticated %}
{% like_widget for object %}
{% endif %}

See also

  •  The Creating a template tag to load a QuerySet in a template recipe
  • The Creating a template tag to parse content as a template recipe
  • The Creating a template tag to modify request query parameters recipe

Creating a template tag to load a QuerySet in a template

Most often, the content that should be shown in a web page will have to be defined in the view. If this is the content to show on every page, it is logical to create a context processor. Another situation is when you need to show additional content such as the latest news or a random quote on some specific pages, for example, the start page or the details page of an object. In this case, you can load the necessary content with the {% get_objects %} template tag, which we will implement in this recipe.

Getting ready

Once again, we will start with the utils app that should be installed and ready for custom template tags.

How to do it...

Template tags consist of function parsing arguments passed to the tag and a node class that renders the output of the tag or modifies the template context. Perform the following steps:

  1. First, let's create the function parsing the template-tag arguments, as follows:
    #utils/templatetags/utility_tags.py
    # -*- coding: UTF-8 -*-
    from django.db import models
    from django import template
    register = template.Library()
    ### TAGS ###
    @register.tag
    def get_objects(parser, token):
    """
    Gets a queryset of objects of the model specified by app
    and
    model names
    Usage:
    {% get_objects [<manager>.]<method> from
    <app_name>.<model_name> [limit <amount>] as
    <var_name> %}
    Example:
    {% get_objects latest_published from people.Person
    limit 3 as people %}
    {% get_objects site_objects.all from news.Article
    limit 3 as articles %}
    {% get_objects site_objects.all from news.Article
    as articles %}
    """
    amount = None
    try:
    tag_name, manager_method, str_from, appmodel,
    str_limit,\
    amount, str_as, var_name = token.split_contents()
    except ValueError:
    try:
    tag_name, manager_method, str_from, appmodel, str_as,\
    var_name = token.split_contents()
    except ValueError:
    raise template.TemplateSyntaxError, \
    "get_objects tag requires a following syntax: "
    "{% get_objects [<manager>.]<method> from "\
    "<app_ name>.<model_name>"\
    " [limit <amount>] as <var_name> %}"
    try:
    app_name, model_name = appmodel.split(".")
    except ValueError:
    raise template.TemplateSyntaxError, \
    "get_objects tag requires application name and "\
    "model name separated by a dot"
    model = models.get_model(app_name, model_name)
    return ObjectsNode(model, manager_method, amount, var_name)
  2. Then, we create the node class in the same file, as follows:
    class ObjectsNode(template.Node):
    def __init__(self, model, manager_method, amount, var_name):
    self.model = model
    self.manager_method = manager_method
    self.amount = amount
    self.var_name = var_name
    def render(self, context):
    if "." in self.manager_method:
    manager, method = self.manager_method.split(".")
    else:
    manager = "_default_manager"
    method = self.manager_method
    qs = getattr(
    getattr(self.model, manager),
    method,
    self.model._default_manager.none,
    )()
    if self.amount:
    amount = template.resolve_variable(self.amount,
    context)
    context[self.var_name] = qs[:amount]
    else:
    context[self.var_name] = qs
    return ""

How it works...

The {% get_objects %} template tag loads a QuerySet defined by the manager method from a specified app and model, limits the result to the specified amount, and saves the result to a context variable.

This is the simplest example of how to use the template tag that we have just created. It will load five news articles in any template using the following snippet:

{% load utility_tags %}
{% get_objects all from news.Article limit 5 as latest_articles %}
{% for article in latest_articles %}
<a href="{{ article.get_url_path }}">{{ article.title }}</a>
{% endfor %}

This is using the all method of the default objects manager of the Article model, and will sort the articles by the ordering attribute defined in the Meta class.

A more advanced example would be required to create a custom manager with a custom method to query objects from the database. A manager is an interface that provides database query operations to models. Each model has at least one manager called objects by default. As an example, let's create the Artist model, which has a draft or published status, and a new manager, custom_manager, which allows you to select random published artists:

#artists/models.py
# -*- coding: UTF-8 -*-
from django.db import models
from django.utils.translation import ugettext_lazy as _
STATUS_CHOICES = (
('draft', _("Draft"),
('published', _("Published"),
)
class ArtistManager(models.Manager):
def random_published(self):
return self.filter(status="published").order_by('?')
class Artist(models.Model):
# ...
status = models.CharField(_("Status"), max_length=20,
choices=STATUS_CHOICES)
custom_manager = ArtistManager()

To load a random published artist, you add the following snippet to any template:

{% load utility_tags %}
{% get_objects custom_manager.random_published from artists.Artist
limit 1 as random_artists %}
{% for artist in random_artists %}
{{ artist.first_name }} {{ artist.last_name }}
{% endfor %}

Let's look at the code of the template tag. In the parsing function, there is one of two formats expected: with the limit and without it. The string is parsed, the model is recognized, and then the components of the template tag are passed to the ObjectNode class.

In the render method of the node class, we check the manager's name and its method's name. If this is not defined, _default_manager will be used, which is, in most cases, the same as objects. After that, we call the manager method and fall back to empty the QuerySet if the method doesn't exist. If the limit is defined, we resolve the value of it and limit the QuerySet. Lastly, we save the QuerySet to the context variable.

See also

  • The Creating a template tag to include a template if it exists recipe
  • The Creating a template tag to parse content as a template recipe
  • The Creating a template tag to modify request query parameters recipe

Creating a template tag to parse content as a template

In this recipe, we will create a template tag named {% parse %}, which allows you to put template snippets into the database. This is valuable when you want to provide different content for authenticated and non-authenticated users, when you want to include a personalized salutation, or when you don't want to hardcode media paths in the database.

Getting ready

No surprise, we will start with the utils app that should be installed and ready for custom template tags.

How to do it...

Template tags consist of two things: the function parsing the arguments of the template tag and the node class that is responsible for the logic of the template tag as well as for the output. Perform the following steps:

  1. First, let's create the function parsing the template-tag arguments, as follows:
    #utils/templatetags/utility_tags.py
    # -*- coding: UTF-8 -*-
    from django import template
    register = template.Library()
    ### TAGS ###
    @register.tag
    def parse(parser, token):
    """
    Parses the value as a template and prints it or saves to a
    variable
    Usage:
    {% parse <template_value> [as <variable>] %}
    Examples:
    {% parse object.description %}
    {% parse header as header %}
    {% parse "{{ MEDIA_URL }}js/" as js_url %}
    """
    bits = token.split_contents()
    tag_name = bits.pop(0)
    try:
    template_value = bits.pop(0)
    var_name = None
    if len(bits) == 2:
    bits.pop(0) # remove the word "as"
    var_name = bits.pop(0)
    except ValueError:
    raise template.TemplateSyntaxError, \
    "parse tag requires a following syntax: "\
    "{% parse <template_value> [as <variable>] %}"
    return ParseNode(template_value, var_name)
  2. Then, we create the node class in the same file, as follows:
    class ParseNode(template.Node):
    def __init__(self, template_value, var_name):
    self.template_value = template_value
    self.var_name = var_name
    def render(self, context):
    template_value = template.resolve_variable(
    self.template_value, context)
    t = template.Template(template_value)
    context_vars = {}
    for d in list(context):
    for var, val in d.items():
    context_vars[var] = val
    result = t.render(template.RequestContext(
    context['request'], context_vars))
    if self.var_name:
    context[self.var_name] = result
    return ""
    return result

How it works...

The {% parse %} template tag allows you to parse a value as a template and to render it immediately or to save it as a context variable.

If we have an object with a description field, which can contain template variables or logic, then we can parse it and render it using the following code:

{% load utility_tags %}
{% parse object.description %}

It is also possible to define a value to parse using a quoted string like this:

{% load utility_tags %}
{% parse "{{ STATIC_URL }}site/img/" as img_path %}
<img src="{{ img_path }}someimage.png" alt="" />

Let's have a look at the code of the template tag. The parsing function checks the arguments of the template tag bit by bit. At first, we expect the name parse, then the template value, then optionally the word as, and lastly the context variable name. The template value and the variable name are passed to the ParseNode class. The render method of that class at first resolves the value of the template variable and creates a template object out of it. Then, it renders the template with all the context variables. If the variable name is defined, the result is saved to it; otherwise, the result is shown immediately.

See also

  • The Creating a template tag to include a template if it exists recipe
  • The Creating a template tag to load a QuerySet in a template recipe
  • The Creating a template tag to modify request query parameters recipe

Creating a template tag to modify request query parameters

Django has a convenient and flexible system to create canonical, clean URLs just by adding regular expression rules in the URL configuration files. But there is a lack of built-in mechanisms to manage query parameters. Views such as search or filterable object lists need to accept query parameters to drill down through filtered results using another parameter or to go to another page. In this recipe, we will create a template tag named {% append_to_query %}, which lets you add, change, or remove parameters of the current query.

Getting ready

Once again, we start with the utils app that should be set in INSTALLED_APPS and should contain the templatetags package.

Also, make sure that you have the request context processor set for the TEMPLATE_CONTEXT_PROCESSORS setting, as follows:

#settings.py
TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"django.core.context_processors.static",
"django.core.context_processors.tz",
"django.contrib.messages.context_processors.messages",
"django.core.context_processors.request",
)

How to do it...

For this template tag, we will be using the simple_tag decorator that parses the components and requires you to define just the rendering function, as follows:

#utils/templatetags/utility_tags.py
# -*- coding: UTF-8 -*-
import urllib
from django import template
from django.utils.encoding import force_str
register = template.Library()
### TAGS ###
@register.simple_tag(takes_context=True)
def append_to_query(context, **kwargs):
""" Renders a link with modified current query parameters """
query_params = context['request'].GET.copy()
for key, value in kwargs.items():
query_params[key] = value
query_string = u""
if len(query_params):
query_string += u"?%s" % urllib.urlencode([
(key, force_str(value)) for (key, value) in
query_params. iteritems() if value
]).replace('&', '&amp;')
return query_string

How it works...

The {% append_to_query %} template tag reads the current query parameters from the request.GET dictionary-like QueryDict object to a new dictionary named query_params, and loops through the keyword parameters passed to the template tag updating the values. Then, the new query string is formed, all spaces and special characters are URL-encoded, and ampersands connecting query parameters are escaped. This new query string is returned to the template.

To read more about QueryDict objects, refer to the official Django documentation:

https://docs.djangoproject.com/en/1.6/ref/request-response/#querydict-objects

Let's have a look at an example of how the {% append_to_query %} template tag can be used. If the current URL is http://127.0.0.1:8000/artists/?category=fine-art&page=1, we can use the following template tag to render a link that goes to the next page:

{% load utility_tags %}
<a href="{% append_to_query page=2 %}">2</a>

The following is the output rendered, using the preceding template tag:

<a href="?category=fine-art&amp;page=2">2</a>

Or we can use the following template tag to render a link that resets pagination and goes to another category:

{% load utility_tags i18n %}
<a href="{% append_to_query category="sculpture" page="" %}">{% trans "Sculpture" %}</a>

The following is the output rendered, using the preceding template tag:

<a href="?category=sculpture">Sculpture</a>

See also

  • The Creating a template tag to include a template if it exists recipe
  • The Creating a template tag to load a QuerySet in a template recipe
  • The Creating a template tag to parse content as a template recipe

Summary

In this article showed you how to create and use your own template filters and tags, as the default Django template system is quite extensive, and there are more things to add for different cases.

Resources for Article:


Further resources on this subject:


You've been reading an excerpt of:

Web Development with Django Cookbook

Explore Title