Leveraging Custom Filters in Django for Effective Data Rendering
Tags: django, python, webdev, backend
Published: 2023-07-09 17:28:37

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:

```html
{{ 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:

```html
{{ 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:

```python
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:

```html
{% 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`).


```python
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:


```html
{% 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.


```python
@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:

```html
<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:

```html
<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.


![Done](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xl6s5ewdcelxl1x0pe6u.png)