Skip to main content

Django Custom Template Tags

Django's template language provides a powerful way to display data in your HTML templates. However, you'll often need functionality that goes beyond what's available out of the box. This is where Django's custom template tags come in - they allow you to extend the template language with your own functionality.

Introduction to Custom Template Tags

Django templates are designed to be simple, focusing on presentation rather than complex logic. While this separation of concerns is generally a good practice, sometimes you need to perform operations that aren't covered by Django's built-in template tags. Custom template tags let you write Python code that can be called from your templates, keeping your views cleaner and your templates more expressive.

There are three types of custom template tags you can create:

  1. Simple tags - Process input and return a string
  2. Inclusion tags - Return a rendered template
  3. Assignment tags - Set a value to a variable in the template

Setting Up Your Template Tags

Before we dive into creating template tags, we need to set up the correct file structure:

  1. Create a templatetags package in your Django app:
bash
myproject/
myapp/
__init__.py
models.py
views.py
templatetags/ # Create this directory
__init__.py # Make it a Python package
custom_tags.py # This will contain your custom tags
  1. Make sure the templatetags directory is a Python package by adding an empty __init__.py file.

Simple Template Tags

Let's start with a simple template tag that formats a number as a currency:

python
# myapp/templatetags/custom_tags.py
from django import template

register = template.Library()

@register.simple_tag
def format_currency(amount, currency="$"):
"""
Format a number as currency.
Example: {% format_currency 50.25 "€" %}
"""
return f"{currency}{amount:.2f}"

To use this in your template:

django
{% load custom_tags %}

<p>Product price: {% format_currency product.price "€" %}</p>

If product.price is 49.99, the output would be:

html
<p>Product price: €49.99</p>

Filters vs. Tags

You might be wondering when to use a custom filter versus a template tag. As a general rule:

  • Use filters for simple text transformations
  • Use template tags for more complex operations or when you need to process multiple arguments

Inclusion Tags

Inclusion tags are used when you want to render a template with some context data. They're perfect for reusable UI components like widgets or cards:

python
# myapp/templatetags/custom_tags.py
from django import template

register = template.Library()

@register.inclusion_tag('myapp/tags/user_profile_card.html')
def user_profile_card(user):
"""
Renders a user profile card with the given user's information.
Example: {% user_profile_card user %}
"""
return {
'username': user.username,
'full_name': user.get_full_name(),
'date_joined': user.date_joined,
'avatar_url': user.profile.avatar_url if hasattr(user, 'profile') else None,
}

Now create the template for this component:

django
{/* myapp/templates/myapp/tags/user_profile_card.html */}
<div class="profile-card">
<img src="{{ avatar_url|default:'default-avatar.png' }}" alt="{{ username }}" />
<h3>{{ full_name }}</h3>
<p>@{{ username }}</p>
<p>Member since: {{ date_joined|date:"F j, Y" }}</p>
</div>

And use it in any template:

django
{% load custom_tags %}

<div class="dashboard">
<h2>User Information</h2>
{% user_profile_card request.user %}
</div>

This will insert the rendered user profile card into your template. Inclusion tags are great for:

  1. Reusable UI components
  2. Keeping your templates DRY (Don't Repeat Yourself)
  3. Hiding complex rendering logic

Assignment Tags

Assignment tags let you set a value to a template variable. In Django 1.9+, this functionality was added to simple_tag with the takes_context and assignment_tag attributes:

python
@register.simple_tag(takes_context=True, name="get_trending_posts")
def get_trending_posts(context, count=5):
"""
Fetch the most popular posts.
Example: {% get_trending_posts 3 as trending_posts %}
"""
return Post.objects.annotate(like_count=Count('likes')).order_by('-like_count')[:count]

To use it in your template:

django
{% load custom_tags %}

{% get_trending_posts 3 as trending_posts %}

<h2>Trending Posts</h2>
<ul>
{% for post in trending_posts %}
<li>{{ post.title }} ({{ post.like_count }} likes)</li>
{% endfor %}
</ul>

Taking Context as Input

Sometimes you need access to the template's context (like the current user or request). You can use takes_context=True for this:

python
@register.simple_tag(takes_context=True)
def is_current_page(context, url_name):
"""
Check if the current page matches the given URL name.
Example: {% is_current_page 'home' %}
"""
request = context['request']
return request.resolver_match.url_name == url_name

In your template:

django
{% load custom_tags %}

<a href="{% url 'home' %}" class="{% if is_current_page 'home' %}active{% endif %}">
Home
</a>

Real-World Example: Pagination Component

Let's create a reusable pagination component that can be used across your project:

python
# myapp/templatetags/pagination_tags.py
from django import template

register = template.Library()

@register.inclusion_tag('myapp/tags/pagination.html', takes_context=True)
def show_pagination(context, page_obj, page_range=5):
"""
Display a pagination component.
Example: {% show_pagination page_obj %}
"""
request = context['request']
current_page = page_obj.number
total_pages = page_obj.paginator.num_pages

# Calculate the range of page numbers to show
start_page = max(current_page - page_range // 2, 1)
end_page = min(current_page + page_range // 2, total_pages)

# Adjust if we're near the edges
if start_page <= 3:
end_page = min(start_page + page_range - 1, total_pages)
start_page = 1
if end_page >= total_pages - 2:
start_page = max(end_page - page_range + 1, 1)
end_page = total_pages

page_numbers = list(range(start_page, end_page + 1))

return {
'page_obj': page_obj,
'page_numbers': page_numbers,
'request': request,
'show_first': start_page > 1,
'show_last': end_page < total_pages,
}

Template for the pagination component:

django
{/* myapp/templates/myapp/tags/pagination.html */}
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page=1" class="pagination-link">&laquo; First</a>
<a href="?page={{ page_obj.previous_page_number }}" class="pagination-link">Previous</a>
{% endif %}

{% for num in page_numbers %}
{% if num == page_obj.number %}
<span class="pagination-link active">{{ num }}</span>
{% else %}
<a href="?page={{ num }}" class="pagination-link">{{ num }}</a>
{% endif %}
{% endfor %}

{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}" class="pagination-link">Next</a>
<a href="?page={{ page_obj.paginator.num_pages }}" class="pagination-link">Last &raquo;</a>
{% endif %}
</div>

Now you can use this pagination component in any template:

django
{% load pagination_tags %}

{/* Display your data */}
<div class="blog-posts">
{% for post in posts %}
<article>
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt }}</p>
</article>
{% endfor %}
</div>

{/* Show pagination */}
{% show_pagination posts %}

Performance Considerations

Custom template tags can impact performance if they're not designed carefully, especially if:

  1. They perform database queries
  2. They're used inside loops
  3. They do complex calculations

Best Practices:

  • Cache the results of expensive operations
  • Move complex logic out of templates and into your views when possible
  • Consider using template fragment caching for parts of templates that use expensive custom tags

Registering Multiple Tags

You can register multiple tags in the same file:

python
# myapp/templatetags/utility_tags.py
from django import template
from datetime import datetime

register = template.Library()

@register.simple_tag
def current_time(format_string="%H:%M:%S"):
return datetime.now().strftime(format_string)

@register.simple_tag
def get_setting(name):
from django.conf import settings
return getattr(settings, name, "")

@register.filter
def placeholder(value, placeholder_text):
"""
Returns the placeholder text if the value is empty.
Example: {{ user.bio|placeholder:"No bio available" }}
"""
return value if value else placeholder_text

Summary

Custom template tags are a powerful way to extend Django's template language. They help you keep your templates clean while adding functionality that's not available out of the box. We've covered:

  1. Simple tags: For basic operations that return a string
  2. Inclusion tags: For rendering reusable template components
  3. Assignment tags: For setting variables within templates
  4. Context-aware tags: For accessing the template context

By creating custom template tags, you can make your templates more expressive and maintainable, while keeping your views focused on preparing data rather than presentation logic.

Additional Resources

Exercises

  1. Create a custom template tag that formats a datetime object according to how long ago it was (e.g., "2 hours ago", "3 days ago").
  2. Create an inclusion tag that renders a "share on social media" component with links to share on different platforms.
  3. Create a template tag that fetches and displays weather data for a given city (you can use a weather API).
  4. Create a template tag that highlights all occurrences of a search term in a text.

With these exercises, you'll gain practical experience in creating different types of custom template tags that can enhance your Django applications!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)