DEV Community

Cover image for Leveraging Custom Filters in Django for Effective Data Rendering
Abdelrahman hassan hamdy
Abdelrahman hassan hamdy

Posted on • Edited on

Leveraging Custom Filters in Django for Effective Data Rendering

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 }}
Enter fullscreen mode Exit fullscreen mode

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" }}
Enter fullscreen mode Exit fullscreen mode

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})
Enter fullscreen mode Exit fullscreen mode

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 %}
Enter fullscreen mode Exit fullscreen mode

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}"
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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}"
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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.

Done

Top comments (0)