call¶
The call
template tag allows functions to be called from within a template.
Note
Make sure to install dj_angles
and include {% load dj_angles %}
in your template if "dj_angles.templatetags.dj_angles"
is not added to template built-ins.
Example¶
Let’s say you have a Book
model and you want to list all of the books, but add an icon if the book has been read by the current user.
Models¶
# models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
def is_read(self, user):
if user.is_anonymous:
return False
return self.readers.filter(user=user).exists()
class Reader(models.Model):
user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
book = models.ForeignKey('Book', related_name='readers', on_delete=models.CASCADE)
Template¶
There are a few different ways to show an icon if a book has been read.
Note
All of these examples probably result in n+1 queries 🤪, but the code is kept simple just as an example.
Add an attribute to the model in the view¶
This feels a little clunky and adds an implicit attribute to the model which is not very discoverable.
# views.py
from django.shortcuts import render
from book.models import Book
def index(request):
books = Book.objects.all()
# Loop over all books and add an attribute
for book in books:
book.current_user_read = book.is_read(request.user)
return render(request, 'index.html', {'books': books})
<!-- index.html -->
{% for book in books %}
<div>
{{ book.title }}
{% if book.current_user_read %}
✅
{% else %}
❌
{% endif %}
</div>
{% endfor %}
Make a custom template tag¶
This encapsulates template logic in Python code so it can be tested. However, it requires extra code for every use case and can feel like additional work especially for “pass-through” template tags which just call a function.
# views.py
from django.shortcuts import render
from book.models import Book
def index(request):
books = Book.objects.all()
return render(request, 'index.html', {'books': books})
# templatetags/book_tags.py
from django import template
register = template.Library()
@register.simple_tag(takes_context=True)
def current_user_read(context, book):
request = context.get("request")
if request and not request.user.is_anonymous:
return book.is_read(request.user)
return False
<!-- index.html -->
{% load book_tags %}
{% for book in books %}
<div>
{{ book.title }}
{% if current_user_read book %}
✅
{% else %}
❌
{% endif %}
</div>
{% endfor %}
Use the call
template tag¶
This is basically like a custom template tag, but without creating it every time.
# views.py
from django.shortcuts import render
from book.models import Book
def index(request):
books = Book.objects.all()
return render(request, 'index.html', {'books': books})
<!-- index.html -->
{% for book in books %}
<div>
{{ book.title }}
{% call book.is_read(request.user) as current_user_read %}
{% if current_user_read %}
✅
{% else %}
❌
{% endif %}
</div>
{% endfor %}
Calling functions¶
The template tag function argument tries to look as similar to normal Python code as possible. If it is followed by the word “as” and then a variable name, the result of the function will be stored in the context for later use.
<!-- index.html -->
{% call slugify('Hello Goodbye') as slug %}
{{ slug }} <!-- hello-goodbye -->
The call
template tag can only call functions that are available in the context. So, the slugify
function needs to be added to the context in the view.
# views.py
from django.shortcuts import render
from django.utils.text import slugify
def index(request):
return render(request, 'index.html', {'slugify': slugify})
If an “as” is not used, a stringified result of the function will be output directly in the template.
<!-- index.html -->
{% call slugify('Hello Goodbye') %} <!-- hello-goodbye -->
Traversing¶
Objects¶
# views.py
from django.shortcuts import render
import django
def index(request):
return render(request, 'index.html', {'django': django})
{% call django.utils.text.slugify('Hello Goodbye') as slug %}
{{ slug }} <!-- hello-goodbye -->
Dictionaries¶
# views.py
from django.shortcuts import render
import django
def index(request):
data = {"functions": {"slugify": django.utils.text.slugify}}
return render(request, "index.html", {"data": data})
<!-- index.html -->
{% call data.functions.slugify('Hello Goodbye') as slug %}
{{ slug }} <!-- hello-goodbye -->
Supported argument types¶
Most Python primitives are supported as arguments, e.g. strings, ints, lists, dictionaries, etc.
# views.py
from django.shortcuts import render
def index(request):
return render(request, 'index.html', {'add': lambda a, b: a + b})
<!-- index.html -->
{% call add(2, 3) as result %}
{{ result }} <!-- 5 -->
Datetimes¶
There are a few helper methods available to parse datetimes from strings in the django.utils.dateparse module.
# views.py
from django.shortcuts import render
from datetime import timedelta
from django.utils.dateparse import parse_datetime
def index(request):
return render(request, 'index.html', {'add_day': lambda dt: parse_datetime(dt) + timedelta(days=1)})
<!-- index.html -->
{% call add_day("2025-03-14") as next_day %}
{{ next_day|date:"r" }} <!-- Wed, 13 Mar 2025 00:00:00 -->
Template variables¶
Django template variables can be used for args or kwargs (as long as they are available in the context).
# views.py
from django.shortcuts import render
def index(request):
return render(request, 'index.html', {'add': lambda a, b: a + b, 'number_one': 1, 'number_two': 2})
<!-- index.html -->
{% call add(number_one, number_two) as result %}
{{ result }} <!-- 3 -->
Note
If a variable is referred to, but is not available in the context a VariableDoesNotExist
error will be raised.
Unpacking¶
Lists and dictionaries can be unpacked using the *
and **
operators.
Args¶
# views.py
from django.shortcuts import render
def index(request):
return render(request, 'index.html', {'add': lambda a, b: a + b, 'numbers': [2, 3]})
<!-- index.html -->
{% call add(*[2, 3]) as result %}
<!-- using template variable -->
{% call add(*numbers) as result %}
<!-- without unpacking -->
{% call add(2, 3) as result %}
{{ result }} <!-- 5 -->
Kwargs¶
# views.py
from django.shortcuts import render
def make_name(first_name, last_name):
return f"{first_name} {last_name}"
def index(request):
return render(request, 'index.html', {'make_name': make_name, 'name': {'first_name': 'Alice', 'last_name': 'Smith'}})
<!-- index.html -->
{% call make_name(**{'first_name': 'Alice', 'last_name': 'Smith'}) as result %}
<!-- using template variable -->
{% call make_name(**name) as result %}
<!-- without unpacking -->
{% call make_name(first_name='Alice', last_name='Smith') as result %}
{{ result }} <!-- Alice Smith -->
Querysets¶
# views.py
from django.shortcuts import render
from book.models import Book
def index(request):
books = Book.objects.all()
return render(request, 'index.html', {'books': books})
<!-- index.html -->
{% call books.filter(published__gte='2020-01-01').order_by('name') as books %}
<ul>
{% for book in books %}
<li>{{ book }}</li>
{% endfor %}
</ul>
How does this work?¶
The call
template tag is a custom template tag which parses the first argument into Python AST and then evaluates it. After evaluation, the result is stored in the context with the name specified.
Note
If your first thought is “parsing a string into the AST is probably slow”, then you might be right. However, usually network latency and database performance will be the actual bottleneck for most applications. However, as always, it is up to each individual to decide if the potential performance implications are worth the trade-off of having less custom code.