Chapter 1: Django Template Variables
Template variables — that is: everything that happens between {{ and }}
Most beginners think “it’s just printing a variable”… but there is a whole little language inside those double curly braces. Understanding it properly will make your templates 5× cleaner, faster to write, and much less error-prone.
Let’s go slowly — like I’m sitting next to you, typing in VS Code together, explaining every single syntax variation you’ll actually use in real projects.
We’ll use your existing polls app (Question + Choice models) as the playground.
1. The Most Basic Form (What Everyone Knows)
|
0 1 2 3 4 5 6 7 |
<h1>Welcome, {{ user.username }}</h1> <p>Question: {{ question.question_text }}</p> |
→ Prints the value → Automatically escapes HTML/JS (safe by default)
2. Dot Notation – Accessing Attributes & Dictionary Keys
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
{{ question.pub_date }} <!-- DateTime object → calls str() --> {{ question.pub_date.year }} <!-- attribute access --> {{ question.category }} <!-- field value --> {{ question.category.title }} <!-- method call without () --> {{ question.was_published_recently }} <!-- your custom model method --> {{ question_dict.question_id }} <!-- if context has dict --> |
Golden rule: Django tries in this order:
- Dictionary lookup → dict[“key”]
- Attribute / property lookup → obj.attribute
- Method call (without arguments) → obj.method()
- List-index lookup → list[0]
So {{ question.was_published_recently }} actually calls the method!
3. Filters – The Real Power Inside {{ }}
Filters are small functions applied with
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
{{ question.pub_date|date:"d F Y" }} <!-- 31 January 2026 --> {{ question.pub_date|date:"SHORT_DATE_FORMAT" }} <!-- 31/01/2026 (depends on locale) --> {{ question.pub_date|timesince }} <!-- "2 days ago" --> {{ question.question_text|truncatewords:8 }} <!-- first 8 words + … --> {{ question.question_text|upper }} <!-- all uppercase --> {{ question.question_text|default:"No title" }} <!-- if empty or None --> {{ question.vote_count|pluralize:"vote,votes" }} <!-- vote / votes --> {{ question.vote_count|add:10 }} <!-- +10 --> {{ question.is_active|yesno:"Yes,No,Maybe" }} <!-- Yes / No / Maybe --> {{ question.category|title }} <!-- General → General --> {{ question.category|capfirst }} <!-- general → General --> |
Most used filters in real projects (keep this list handy)
- date:”FORMAT”
- time:”FORMAT”
- timesince / timeuntil
- truncatewords:N / truncatechars:N
- default:”value”
- pluralize:”s,es” or pluralize:”vote,votes”
- add:N / sub:N
- length / length_is:”5″
- lower / upper / capfirst / title
- safe (dangerous – only if you trust the content)
- linebreaksbr (turns \n into )
- striptags (removes HTML tags)
- floatformat:”-2″ (2 decimal places)
4. Chaining Filters (Very Common)
|
0 1 2 3 4 5 6 7 8 9 10 |
{{ question.pub_date|date:"d M Y"|upper }} <!-- 31 JAN 2026 --> {{ question.question_text|truncatewords:10|capfirst }} {{ question.vote_count|add:5|pluralize:"vote,votes" }} |
Order matters — filters run left to right.
5. Variables in Loops & Conditions
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
{% for question in latest_questions %} <li> {{ forloop.counter }}. {{ question.question_text }} ({{ question.vote_count|default:"0" }} {{ question.vote_count|pluralize:"vote,votes" }}) </li> {% empty %} <li>No questions yet…</li> {% endfor %} |
Special loop variables:
- {{ forloop.counter }} → 1,2,3…
- {{ forloop.counter0 }} → 0,1,2…
- {{ forloop.first }} / {{ forloop.last }} → boolean
- {{ forloop.revcounter }} → reverse count
6. Accessing Context Variables Safely
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
{{ request.user.username|default:"Guest" }} {{ messages }} <!-- list of messages if you use messages framework --> {{ perms.polls.add_question }} <!-- True/False – permission check --> {{ settings.DEBUG }} <!-- True in development (via context processor) --> |
7. Custom Filters & Tags (When Built-ins Are Not Enough)
Create polls/templatetags/poll_extras.py:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
from django import template from django.utils.safestring import mark_safe register = template.Library() @register.filter def color_category(value): colors = {"fun": "green", "politics": "red"} color = colors.get(value, "gray") return mark_safe(f'<span style="color:{color};">{value.title()}</span>') @register.simple_tag def vote_badge(count): if count >= 10: return mark_safe('<span style="background:#28a745;color:white;padding:2px 6px;border-radius:4px;">Hot!</span>') return count |
In template:
|
0 1 2 3 4 5 6 7 8 9 10 |
{% load poll_extras %} {{ question.category|color_category }} {{ question.vote_count|vote_badge }} |
Quick Syntax Cheat-Sheet (Print & Keep)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{{ variable }} → print escaped value {{ variable.field }} → attribute / dict key {{ variable.method }} → method call (no args) {{ variable|filter }} → apply filter {{ variable|filter1|filter2 }} → chain filters {{ variable|filter:"arg1":"arg2" }} → filter with arguments {{ variable|default:"N/A" }} → fallback value {% url 'app:name' arg=var %} → reverse URL {% if variable %} ... {% endif %} → condition {{ forloop.counter }} → 1-based loop index |
Your Quick Task Right Now
Open one of your templates (index.html or detail.html) and try these:
- {{ question.pub_date|date:”d F Y, l” }} → day name too
- {{ question.vote_count|pluralize:”vote,votes” }}
- {{ question.question_text|truncatewords:12|capfirst }}
- Add {{ forloop.counter }}. before each question in the list
- Try {{ question.category|default:”Uncategorized” }}
Tell me what feels next:
- Which part of template variables is still confusing? (filters? chaining? custom filters?)
- Want to see more filter examples (date, numbers, strings)?
- Want to practice custom filters/tags together?
- Or ready for the next big topic: Forms + Voting + POST + F() expression
You’re getting really strong with Django syntax — this is the foundation everything else builds on.
Keep asking, keep typing — you’re doing great! 🚀🇮🇳
