Chapter 10: Django Templates
Django: Templates!
It’s still that beautiful Hyderabad afternoon (January 31, 2026, around 4:00 PM IST), and you’ve already got:
- Project (mysite)
- App (polls)
- Views (function-based or class-based)
- URLs wired up
Now templates turn your raw data into beautiful (or at least readable) HTML pages. Templates are not Python code — they’re HTML with special tags and filters that let Django insert dynamic content.
Django’s template engine (called Django Template Language or DTL) is intentionally not full Python — it’s safe for designers/non-coders, prevents arbitrary code execution, and encourages clean separation.
In Django 6.0.1 (today’s version), templates got a very nice new feature: Template Partials (small reusable named fragments inside the same file). We’ll use that too!
Step 1: Understand Template Setup (Where Files Go)
Django looks for templates in specific places. Recommended structure (namespaced to avoid clashes):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
polls/ ├── templates/ │ └── polls/ ← important namespace folder! │ ├── base.html ← optional shared layout │ ├── index.html │ ├── detail.html │ └── results.html |
Why polls/ inside templates/?
- Prevents name collision if another app has index.html
- When you do render(request, ‘polls/index.html’) → Django knows exactly where
Add to mysite/settings.py (usually already there in new projects):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
TEMPLATES = [ { ... 'DIRS': [], # ← you can add BASE_DIR / 'templates' later for project-level 'APP_DIRS': True, # ← this enables app/templates/ lookup ... } ] |
APP_DIRS: True = Django auto-searches each installed app’s templates/ folder.
Step 2: First Simple Template (No Inheritance Yet)
In your polls/views.py (update index view):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from django.shortcuts import render from .models import Question # assume you have some questions def index(request): latest_questions = Question.objects.order_by('-pub_date')[:5] context = { 'latest_questions': latest_questions, 'app_name': 'Polls App', 'current_city': 'Hyderabad', } return render(request, 'polls/index.html', context) |
Create polls/templates/polls/index.html:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ app_name }} - Home</title> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 2rem auto; } h1 { color: #006400; } /* dark green for Hyderabad vibe */ ul { list-style: none; padding: 0; } li { margin: 1rem 0; font-size: 1.2rem; } </style> </head> <body> <h1>Welcome to {{ app_name }} in {{ current_city }}! 🚀</h1> <p>Latest polls as of January 31, 2026:</p> {% if latest_questions %} <ul> {% for question in latest_questions %} <li> {{ question.question_text }} (published {{ question.pub_date|date:"d M Y" }}) </li> {% endfor %} </ul> {% else %} <p style="color: red;">No polls available yet. Add some in the admin panel!</p> {% endif %} </body> </html> |
Key elements:
- {{ variable }} → outputs escaped value (safe from XSS)
- {% tag %} → logic/control (if, for, block…)
- Filters: |date:”d M Y” → formats date nicely (31 Jan 2026)
- {% if %} / {% for %} — no parentheses needed, very readable
Run server → http://127.0.0.1:8000/polls/ → dynamic list!
Step 3: Template Inheritance + base.html (Don’t Repeat Yourself!)
Almost every real site has a shared layout (navbar, footer, CSS links).
Create polls/templates/polls/base.html:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% block title %}My Polls App{% endblock %}</title> <style> body { font-family: system-ui, sans-serif; margin: 0; padding: 0; } header { background: #006400; color: white; padding: 1rem; text-align: center; } main { max-width: 900px; margin: 2rem auto; padding: 0 1rem; } footer { background: #f8f9fa; text-align: center; padding: 1rem; margin-top: 3rem; } </style> </head> <body> <header> <h1>Polls App – Built with Django 6.0</h1> <nav> <a href="{% url 'polls:index' %}" style="color: white; margin: 0 1rem;">Home</a> <!-- Add more links later --> </nav> </header> <main> {% block content %} <!-- Child templates fill this --> {% endblock %} </main> <footer> <p>© {{ year|default:"2026" }} – Webliance in Hyderabad</p> </footer> </body> </html> |
Now update index.html to extend it:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
{% extends "polls/base.html" %} {% block title %}Latest Polls{% endblock %} {% block content %} <h2>Latest Polls</h2> {% if latest_questions %} <ul> {% for question in latest_questions %} <li> <a href="{% url 'polls:detail' question.id %}"> {{ question.question_text }} </a> </li> {% endfor %} </ul> {% else %} <p>No polls yet!</p> {% endif %} {% endblock %} |
- {% extends “polls/base.html” %} — inherits everything
- {% block title %} / {% block content %} — override sections
- {% url ‘polls:detail’ question.id %} — safe named URL (no hardcoding!)
Step 4: New in Django 6.0 – Template Partials! (Very Cool for 2026)
Partials = small named reusable chunks inside the same template file — great for HTMX, repeated cards, modals, without extra files.
Example in detail.html:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
{% extends "polls/base.html" %} {% block content %} <h1>{{ question.question_text }}</h1> <!-- Define a partial for choice item --> {% partialdef choice_item %} <li style="margin: 0.8rem 0; padding: 0.8rem; background: #f0f8ff; border-radius: 6px;"> {{ choice.choice_text }} – <strong>{{ choice.votes }} vote{{ choice.votes|pluralize }}</strong> </li> {% endpartialdef %} <h3>Choices:</h3> <form action="{% url 'polls:vote' question.id %}" method="post"> {% csrf_token %} <ul> {% for choice in question.choice_set.all %} <label> <input type="radio" name="choice" value="{{ choice.id }}"> {% partial "choice_item" %} <!-- render the partial --> </label> {% empty %} <p>No choices available.</p> {% endfor %} </ul> <button type="submit">Vote!</button> </form> <!-- You can also reference by name: {% partial "#choice_item" %} --> {% endblock %} |
- {% partialdef name %} … {% endpartialdef %} — define
- {% partial “name” %} — render it (can pass context too in future)
- Perfect for repeating UI bits without custom tags
Step 5: Useful Built-in Tags & Filters (Daily Essentials)
- {% for %} / {% empty %} / {% endfor %}
- {% if %}, {% elif %}, {% else %}
- {% url ‘name’ arg1 arg2 %}
- Filters: {{ var|upper }}, {{ var|default:”N/A” }}, {{ var|length }}, {{ var|date:”SHORT_DATE_FORMAT” }}, {{ var|pluralize }}
- {% load static %} + {% static ‘css/style.css’ %} (for static files later)
Step 6: Quick Tips & Common Mistakes
- Always use render(request, ‘app/template.html’, context) — not manual loader
- Context must be a dict
- Templates auto-escape HTML → safe, but use |safe if trusted (careful!)
- Debug: {{ var }} → shows value; add {{ debug }} for full context
- No Python code in templates → that’s the point!
Homework / Next Steps
- Create detail.html and results.html using inheritance
- Add a link from index to detail using {% url %}
- Try the partial for choice display
- Add some questions/choices via admin → see dynamic data
Tell me:
- Did the page render? Any TemplateDoesNotExist error?
- Want to add static files (CSS/JS/images) next?
- Or forms / voting full flow?
- Or “explain {% load %} and custom tags”?
You’re building a real app now — templates make it shine! 🌟 Keep going, boss! 🚀
