Chapter 2: Django Template Tags
Template tags — everything that lives inside {% %}
Many people learn {{ variable }} first and think “okay I can print stuff”, but real Django power comes from the tags. They control logic, loops, inheritance, loading extras, URL reversing, filters registration, etc.
Today I want to teach you template tags like a senior sitting next to you — not just listing them, but showing when, why, how and common beginner traps for each important one.
We’ll use your existing polls project as playground.
1. The Absolute Most Important Tag: {% extends %}
Almost every real template starts with this.
|
0 1 2 3 4 5 6 |
{% extends "base.html" %} |
- Tells Django: “use base.html as parent, I only want to fill some blocks”
- Must be the very first tag in the file (no spaces or text before it)
- Common beginner mistake → putting HTML or comments before it → breaks everything
Next lines are usually:
|
0 1 2 3 4 5 6 7 8 9 10 |
{% block title %}Latest Polls{% endblock %} {% block content %} … my page-specific HTML … {% endblock %} |
2. {% block %} + {{ block.super }} (the inheritance magic)
In child template:
|
0 1 2 3 4 5 6 7 8 9 10 11 |
{% block extra_head %} <!-- child adds something --> <style> h2 { color: darkgreen; } </style> {{ block.super }} <!-- ← keeps parent's extra_head content too --> {% endblock %} |
In parent base.html:
|
0 1 2 3 4 5 6 7 8 9 |
{% block extra_head %} <!-- global stuff --> <meta name="description" content="Polls app"> {% endblock %} |
→ Child can append to parent block (very useful for CSS/JS)
3. {% load %} – Bring extra tags/filters
You write this near the top (after {% extends %} if present)
|
0 1 2 3 4 5 6 7 8 |
{% load static %} {% load humanize %} {% load poll_extras %} <!-- your custom templatetags/poll_extras.py --> |
Common ones you will use every week:
- {% load static %} → for CSS/JS/images
- {% load i18n %} → translations
- {% load humanize %} → nicer numbers (1,234 → 1,234)
- {% load cache %} → fragment caching
4. {% url %} – Never hardcode links again
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
<a href="{% url 'polls:detail' slug=question.slug %}"> {{ question.question_text }} </a> <a href="{% url 'polls:index' %}">Back to home</a> <a href="{% url 'admin:polls_question_change' question.pk %}">Edit in admin</a> |
- First part = app_name:view_name
- Then keyword arguments matching the <slug:slug> / <int:pk> in path()
- Never write /polls/{{ question.slug }}/ — it breaks when URL pattern changes
5. {% for %} + {% empty %} + loop variables
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
{% for question in latest_questions %} <div> {{ forloop.counter }}. <a href="{% url 'polls:detail' slug=question.slug %}"> {{ question.question_text }} </a> ({{ question.vote_count|pluralize:"vote,votes" }}) </div> {% empty %} <p style="color:#666; font-style:italic;"> No active polls yet… Why not create one? </p> {% endfor %} |
Useful loop variables:
- forloop.counter → 1, 2, 3…
- forloop.counter0 → 0, 1, 2…
- forloop.first / forloop.last → True/False
- forloop.revcounter → countdown from end
- forloop.parentloop.counter → nested loops
6. {% if %}, {% elif %}, {% else %}
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
{% if question.is_active %} <span style="color:green;">Active</span> {% elif question.pub_date > today %} <span style="color:orange;">Upcoming</span> {% else %} <span style="color:gray;">Archived</span> {% endif %} |
Can also test:
- {% if not question.choices.exists %}
- {% if question.vote_count > 10 %}
- {% if user.is_authenticated %}
- {% if perms.polls.add_question %}
7. {% include %} – Reuse small template pieces
Create polls/templates/polls/_question_card.html:
|
0 1 2 3 4 5 6 7 8 9 |
<div class="card"> <h3><a href="{% url 'polls:detail' slug=question.slug %}">{{ question.question_text }}</a></h3> <p>{{ question.category|title }} • {{ question.pub_date|date }}</p> </div> |
Then in index.html:
|
0 1 2 3 4 5 6 7 8 |
{% for question in latest_questions %} {% include "polls/_question_card.html" with question=question %} {% endfor %} |
→ Very clean when cards are repeated in multiple places
8. {% static %} – Link to CSS/JS/images
|
0 1 2 3 4 5 6 7 8 9 |
{% load static %} <link rel="stylesheet" href="{% static 'css/style.css' %}"> <img src="{% static 'images/logo.png' %}" alt="Logo"> |
You need to run python manage.py collectstatic in production.
9. {% verbatim %} – Escape Django syntax (rare but useful)
|
0 1 2 3 4 5 6 7 8 |
{% verbatim %} This will print {{ variable }} literally, not replaced {% endverbatim %} |
Useful when showing Django code examples in your own docs page.
10. {% spaceless %} – Remove extra whitespace (sometimes needed)
|
0 1 2 3 4 5 6 7 8 9 10 |
{% spaceless %} <ul> {% for item in items %}<li>{{ item }}</li>{% endfor %} </ul> {% endspaceless %} |
→ Removes newlines between tags → cleaner HTML
Quick Reference Table – Tags You’ll Use Every Day
| Tag | Purpose | Most common use-case |
|---|---|---|
| {% extends %} | Inherit from parent template | Every page except base.html |
| {% block %} | Define replaceable section | title, content, extra_head, extra_js |
| {% load %} | Load custom tags/filters | static, humanize, your own poll_extras |
| {% url %} | Reverse URL | every and form action |
| {% for %} + {% empty %} | Loop over queryset/list | listing questions, choices, comments |
| {% if %} / {% elif %} | Conditional rendering | show/hide elements, active/inactive badges |
| {% include %} | Insert another template fragment | reusable cards, navbars, footers |
| {% static %} | Link to static files | CSS, JS, images |
| {% csrf_token %} | Security for POST forms | inside every <form method=”post”> |
Your Quick Practice Task (Do This Right Now)
Open your polls/templates/polls/index.html and try to add these:
- {% url ‘polls:detail’ slug=question.slug %} in the link
- {{ question.pub_date|date:”l, d F Y” }} (weekday + date)
- {{ question.vote_count|pluralize:”vote,votes” }}
- {% if forloop.first %}<strong>Featured:</strong>{% endif %}
- {% load humanize %} + {{ question.vote_count|intcomma }}
Tell me what you want next:
- Which tag is still confusing? (url? block? include?)
- Want more filter examples (especially date & number formatting)?
- Want to write your first custom template filter together?
- Or ready for the next big topic: Django Forms + Voting + POST + F()
You’re getting really strong with templates now — this is where the frontend starts to feel alive.
Keep going — you’re doing fantastic! 🚀🇮🇳
