The template language
Django's template language is deliberately small. It displays data and makes simple decisions; real logic belongs in Python, in your views.
Every example starts with the same few lines of setup, which a project's settings normally provide.
Filters
A filter changes a value before it's shown, written after a |:
import django
from django.conf import settings
settings.configure(TEMPLATES=[{"BACKEND": "django.template.backends.django.DjangoTemplates"}])
django.setup()
import datetime
from django.template import Context, Template
t = Template("""{{ name|title }}
{{ name|upper }} has {{ courses|length }} course{{ courses|length|pluralize }}: {{ courses|join:", " }}
Joined {{ joined|date:"j F Y" }}
Price: {{ price|floatformat:2 }}
Bio: {{ bio|default:"No bio yet" }}
{{ summary|truncatewords:6 }}""")
print(t.render(Context({
"name": "asha verma",
"courses": ["Python", "SQL"],
"joined": datetime.date(2024, 7, 1),
"price": 499,
"bio": "",
"summary": "A practical course that takes you from zero to building real programs.",
})))| Filter | Does |
|---|---|
title, upper, lower, capfirst | change capitals |
length | the number of items, or characters |
pluralize | adds "s" unless the value is 1 |
join:", " | joins a list with a separator |
date:"j F Y" | formats a date |
floatformat:2 | rounds to 2 decimal places |
default:"..." | a fallback for empty or false values |
truncatewords:6 | cuts text to 6 words, adding "…" |
Filters chain from left to right: {{ courses|length|pluralize }}.
if
import django
from django.conf import settings
settings.configure(TEMPLATES=[{"BACKEND": "django.template.backends.django.DjangoTemplates"}])
django.setup()
from django.template import Context, Template
t = Template("""{% if score >= 90 %}Excellent{% elif score >= 60 %}Passed{% else %}Try again{% endif %}
{% if user and not user.banned %}Welcome, {{ user.name }}{% endif %}
{% if "SQL" in courses %}You're taking SQL{% endif %}""")
for score in [95, 72, 40]:
print(t.render(Context({"score": score, "user": {"name": "Leo", "banned": False}, "courses": ["SQL"]})))
print("--"){% if %} supports elif, else, and, or, not, in and the comparison operators.
for
import django
from django.conf import settings
settings.configure(TEMPLATES=[{"BACKEND": "django.template.backends.django.DjangoTemplates"}])
django.setup()
from django.template import Context, Template
t = Template("""{% for lesson in lessons %}{{ forloop.counter }}. {{ lesson.title }}{% if lesson.done %} (done){% endif %}{% if not forloop.last %}, {% endif %}{% empty %}No lessons yet.{% endfor %}""")
print(t.render(Context({"lessons": [{"title": "Variables", "done": True}, {"title": "Loops", "done": False}, {"title": "Functions", "done": False}]})))
print(t.render(Context({"lessons": []})))| Inside a for loop | Means |
|---|---|
forloop.counter | 1, 2, 3... |
forloop.counter0 | 0, 1, 2... |
forloop.first, forloop.last | true on the first or last pass |
{% empty %} | what to show when the list is empty |
with and comments
import django
from django.conf import settings
settings.configure(TEMPLATES=[{"BACKEND": "django.template.backends.django.DjangoTemplates"}])
django.setup()
from django.template import Context, Template
t = Template("""{# a one-line comment: never sent to the browser #}
{% with total=items|length %}{{ total }} item{{ total|pluralize }} in your basket{% endwith %}
{% comment %}A longer comment,
over several lines.{% endcomment %}""")
print(t.render(Context({"items": ["pen", "notebook", "bag"]})).strip()){% with %} names a value for a block of template, which avoids repeating a long expression.
Automatic escaping
import django
from django.conf import settings
settings.configure(TEMPLATES=[{"BACKEND": "django.template.backends.django.DjangoTemplates"}])
django.setup()
from django.template import Context, Template
comment = '<script>alert("hacked")</script> Great course!'
print(Template("{{ comment }}").render(Context({"comment": comment})))
print(Template("{{ comment|safe }}").render(Context({"comment": comment})))Django escapes every variable by default: <, >, & and quotes become entities, so text from users is shown rather than run. This blocks cross-site scripting without any effort on your part. |safe turns escaping off for one value: use it only for HTML you produced and trust, never for user input.
Keep logic in the view
Templates can't call functions with arguments, do arithmetic beyond simple filters, or run queries with conditions, and that's on purpose. Prepare the data in the view:
def dashboard(request):
courses = request.user.courses.filter(active=True)
context = {
"courses": courses,
"completed": courses.filter(progress=100).count(),
}
return render(request, "progress/dashboard.html", context)The template then only displays {{ completed }}. This keeps templates readable for designers, and logic testable in Python.