In Django, filters allow us to modify variables within the template before they get rendered. This capability can be quite powerful, enabling you to tailor data presentation according to your needs. Django comes with a rich set of built-in filters, but you can also create custom ones if the built-in filters do not meet your requirements.
Built-in Filters in Django
First, let's examine how built-in filters work in Django templates. For example, the lower filter is used to render a value in lowercase. It is applied using the |
(pipe) operator inside a rendering block:
{{ value|lower }}
Some filters accept arguments, which can be passed by adding :
(colon) after the filter name. For instance, the date
filter takes an argument that specifies the output format:
{{ post.published_at|date:"M, d Y" }}
Creating a Django Template with Built-in Filters
Let's see built-in filters in action by creating a Django page that lists all blog posts.
First, update your views.py
to fetch all Post
objects in the system and send them to the index.html
template:
from django.utils import timezone
from blog.models import Post
def index(request):
posts = Post.objects.filter(published_at__lte=timezone.now())
return render(request, "blog/index.html", {"posts": posts})
Then, create a simple loop in the index.html
template to render each blog post:
{% block content %}
<h2>Blog Posts</h2>
{% for post in posts %}
<div class="row">
<div class="col">
<h3>{{ post.title }}</h3>
<small>By {{ post.author }} on {{ post.published_at|date:"M, d Y" }}</small>
<p>{{ post.summary }}</p>
<p>
({{ post.content|wordcount }} words)
<a href="#">Read More</a>
</p>
</div>
</div>
{% endfor %}
{% endblock %}
In this template, we are using two built-in filters:
- The
date
filter, which formats the publication date. - The
wordcount
filter, which counts the words in the post's content.
After adding some test data and starting the Django development server, you should see a list of blog posts on your webpage.
Creating Custom Filters in Django
Sometimes, built-in filters might not be enough to satisfy your needs. In such cases, you can create custom filters. These can contain more complex logic, which is often easier to manage than trying to write complex code inside your template.
A custom filter is just a function that takes one or more arguments and returns a string to be rendered in the template.
Let's create a simple custom filter named author_details
that will show more details about the author of a post.
First, we create a new Python file, blog_extras.py
, in the templatetags
directory of our Django app. The function author_details
takes a single argument: the author of the post
(which is a django.contrib.auth.models.User object
).
from django import template
from django.contrib.auth import get_user_model
register = template.Library()
user_model = get_user_model()
@register.filter
def author_details(author):
if not isinstance(author, user_model):
return ""
if author.first_name and author.last_name:
return f"{author.first_name} {author.last_name}"
else:
return f"{author.username}"
We also use the register.filter
decorator to register the function as a custom filter.
After defining the custom filter, we can use it in our template:
{% load blog_extras %}
<small>By {{ post.author|author_details }} on {{ post.published_at|date:"M, d Y" }}</small>
This custom filter will display the author’s first and last name instead of their username, if set.
Custom Filters with Arguments
Filters can also take arguments, as we have seen with the date
filter. Let's update our author_details
filter to return the string <strong>me</strong>
if a post is authored by the currently logged in user.
@register.filter
def author_details(author, current_user):
from django.utils.html import format_html
if not isinstance(author, user_model):
return ""
if author == current_user:
return format_html("<strong>me</strong>")
if author.first_name and author.last_name:
return f"{author.first_name} {author.last_name}"
else:
return f"{author.username}"
We pass the request.user
variable to the filter in the template:
<small>By {{ post.author|author_details:request.user }} on {{ post.published_at|date:"M, d Y" }}</small>
Now, if you are logged in as the author of a post, the author name will be replaced by the bolded word "me".
A Post Script on HTML in Template Tags
One of the key features of Django is its template system, designed to allow multiple people with different skills to contribute to a Django project. Those who have HTML, CSS, and other UX skills can work on the templates, while back-end Python developers build the views and models. Generating HTML in a template filter can disrupt this separation of concerns.
Finding where HTML is being generated can become challenging if it’s not in a template but is instead being generated with a template tag. However, doing this can reduce the amount of code and logic in a template, which can arguably make it easier to read. For comparison, here's how our author_details
code would look in a template:
<small>By
{% if post.author == request.user %}
<strong>me</strong>
{% else %}
{% if post.author.email %}
<a href="mailto:{{ post.author.email }}">
{% endif %}
{% if post.author.first_name and post.author.last_name %}
{{ post.author.first_name }} {{ post.author.last_name }}
{% else %}
{{ post.author.username }}
{% endif %}
{% if post.author.email %}
</a>
{% endif %}
{% endif %}
on {{ post.published_at|date:"M, d Y" }}
</small>
The use of {% if post.author.email %}
twice, once for the opening and once for the closing <a>
tag, can make the code harder to read. As with many things in programming, it depends on your particular situation, personal preference, and organizational needs. Just remember to consider these points when making your decision.
Conclusion
Django filters, both built-in and custom, offer a powerful way to manipulate template variables before rendering. They can simplify your templates and make them more readable. However, remember to use them judiciously, especially when generating HTML in template filters, as it can make it harder to locate where the HTML is being generated and reduce the separation of concerns between the template and the view.
Top comments (0)