Creating Custom Template Tags and Filters in Django
Setting Up the Environment
Custom template tags and filters in Django must reside within a specific directory structure inside one of your project's apps. You need to create a new directory named templatetags at the same level as your models.py and views.py files.
Here is an example of the required directory layout for a app named store:
store/
__init__.py
models.py
templatetags/
__init__.py
store_extras.py
views.py
To use your custom tags and filters in a template, you must first load them using the {% load %} tag. For the example above, the template would include:
{% load store_extras %}
For the {% load %} tag to function correctly, the app containing your templatetags directory must be included in the INSTALLED_APPS setting of your project's settings.py file.
Each Python file within the templatetags directory acts as a separate library of tags and filters. You can have multipple files, and the {% load %} tag will import the specific module you name.
Every custom tag or filter library must be registered. This is done by creating a Library instance at the top of your module.
from django import template
register = template.Library()
Writing Custom Template Filters
A custom filter is a Python function that takes one or two arguments:
- The value of the variable (the input) — this does not have to be a string.
- An optional argument value.
For example, in the template expression {{ var|my_filter:"arg" }}, the my_filter function will receive the value of var and the string "arg" as its arguments.
Let's create a custom filter named format_price that formats a numeric value as a currency string. This filter will accept an optional currency symbol argument.
def format_price(value, symbol="$"):
"""
Formats a number as a currency string.
Example: 1234.56 -> $1,234.56
"""
try:
# Ensure the value is a number
numeric_value = float(value)
return f"{symbol}{numeric_value:,.2f}"
except (ValueError, TypeError):
# Return the original value if it cannot be converted to a float
return value
Next, let's create another filter, truncate_chars, which truncates a string to a specified number of characters.
def truncate_chars(value, length=50):
"""
Truncates a string to a specified number of characters.
If the string is longer than the length, it appends '...'.
"""
if not isinstance(value, str):
value = str(value)
if len(value) > length:
return value[:length] + "..."
return value
Registering Custom Filters
There are two primary ways to register a filter with your Library instance.
Method 1: Using the register.filter() Method
You can pass the filter function and its desired name as arguments to the register.filter() method.
register.filter('format_price', format_price)
register.filter('truncate_chars', truncate_chars)
Method 2: Using the register.filter Decorator
This is the more common and concise approach. You can use the register.filter decorator directly above your function definition. You can optionally specify the filter name as an argument to the decorator.
@register.filter(name='format_price')
def format_price(value, symbol="$"):
# ... function body ...
@register.filter
def truncate_chars(value, length=50):
# ... function body ...
Comlpete Example
Here is a full, working example demonstrating the creation and use of custom filters.
1. The Custom Filter Module (store/templatetags/store_extras.py)
from django import template
register = template.Library()
@register.filter
def format_price(value, symbol="$"):
"""Formats a number as a currency string."""
try:
numeric_value = float(value)
return f"{symbol}{numeric_value:,.2f}"
except (ValueError, TypeError):
return value
@register.filter
def truncate_chars(value, length=50):
"""Truncates a string to a specified number of characters."""
if not isinstance(value, str):
value = str(value)
if len(value) > length:
return value[:length] + "..."
return value
2. The View (store/views.py)
from django.shortcuts import render
def filter_demo(request):
context = {
'product_price': 199.99,
'product_description': "This is a very long and detailed description of a fantastic product that we are selling in our online store. It has many features and benefits that you will surely love."
}
return render(request, 'store/filter_demo.html', context)
3. The Template (store/templates/store/filter_demo.html)
{% extends "base.html" %}
{% load store_extras %}
{% block content %}
<h1>Product Details</h1>
<p><strong>Price:</strong> {{ product_price|format_price }}</p>
<p><strong>Description:</strong> {{ product_description|truncate_chars:100 }}</p>
{% endblock %}
4. Expected Output
When the template is rendered, you would see the following output:
Product Details
Price: $199.99
Description: This is a very long and detailed description of a fantastic product that we are selling in our online store. It has many features and bene...