Custom Django template filters: Range & HTML

One common hurdle for developers is that the Django template system typically doesn't allow Python native functions (like range(), len(), or complex regex) directly within the HTML. This is by design to keep logic separate from presentation, but it can feel restrictive when you need simple functionality.

Custom template filters are the fastest way to bridge this gap. They allow you to bring that missing Python power into your templates without cluttering your views. Filters are especially useful because they keep views thin while enabling reusable presentation logic directly where it belongs.

Here are two highly practical filters to solve common Django headaches—a numeric loop and a content extractor.

⚙️ Critical Setup (Do This First!)

For Django to recognize your custom tags, you must have two things in place:

  1. A folder named templatetags inside your Django app.
  2. A blank file named __init__.py inside the templatetags folder.

This ensures your filters can be loaded in any template across your entire project, once the app is added to INSTALLED_APPS.


1. The Clean Numeric Loop: Implementing range()

Need to loop a fixed or variable number of times (for example, star ratings or placeholder items)? Since Python's range() function isn't available in templates, we expose it using a filter named times.

your_app/templatetags/my_filters.py

from django import template

register = template.Library()

@register.filter(name='times')
def times(number):
    '''Allows looping 'number' times.'''
    try:
        return range(int(number))
    except (ValueError, TypeError):
        return []

Template Usage

{% load my_filters %}

<h3>Product Rating: 4/5</h3>

<!-- The loop runs exactly 4 times -->
{% for _ in 4|times %}
    <i class='fa fa-star text-warning'></i>
{% endfor %}

2. The Content Extractor: Getting the First <p> Tag

When generating summaries, manually cutting HTML is messy. This filter extracts the inner content of the very first <p> tag, producing clean HTML suitable for summaries and list previews.

your_app/templatetags/my_filters.py (Add to the same file)

import re
from django.utils.safestring import mark_safe

# ... (The 'times' filter is here) ...

@register.filter(name='first_paragraph')
def first_paragraph(html_string):
    '''Extracts and returns the inner content of the first <p> tag.'''
    if not html_string:
        return ''
        
    # This regex handles content and safely ignores surrounding tags/attributes.
    match = re.search(r'<p[^>]*>(.*?)</p>', html_string, re.IGNORECASE | re.DOTALL)
    
    if match:
        return mark_safe(match.group(1).strip())
    
    return ''

⚠️ Note on HTML parsing:
This approach is appropriate when the HTML is trusted (for example, content authored within your own CMS). For untrusted or user-generated HTML, consider using a proper HTML parser such as BeautifulSoup to avoid edge cases with malformed markup.

Template Usage

{% load my_filters %}

<!-- Summary Intro: Cleans up the full HTML content -->
<div class='summary-intro'>
    {{ post.content|first_paragraph }}
</div>

Final Takeaway

By dedicating a few minutes to creating custom template filters, you eliminate repetitive logic and make your Django templates cleaner, more expressive, and easier to maintain across your entire project. Happy coding!