Templates

Django Fundamentals
Course 1 ยท Chapter 4 ยท Templates

๐ŸŽจ Templates

render() from the last chapter needs an actual template file to render. Django ships its own templating engine โ€” the Django Template Language (DTL) โ€” which looks a lot like Jinja2 (the templating engine FastAPI projects commonly reach for) but is deliberately more restricted: DTL can't call arbitrary Python, on purpose.

Where Templates Live

By default, Django looks for templates inside each installed app's own templates/ directory โ€” and convention nests one more folder, named after the app, to avoid collisions between apps that both have a list.html:

catalog/ โ”œโ”€โ”€ templates/ โ”‚ โ””โ”€โ”€ catalog/ # the extra "catalog" folder prevents name clashes โ”‚ โ”œโ”€โ”€ base.html โ”‚ โ”œโ”€โ”€ book_list.html โ”‚ โ””โ”€โ”€ book_detail.html โ”œโ”€โ”€ views.py โ””โ”€โ”€ urls.py
# catalog/views.py from django.shortcuts import render def book_list(request): books = [{"title": "Dune"}, {"title": "1984"}] return render(request, "catalog/book_list.html", {"books": books})

๐Ÿงต Variables, Tags & Filters

DTL syntax will look immediately familiar if you've used Jinja2 โ€” double curly braces print a value, {% %} runs a tag, and | applies a filter:

<h1>{{ book.title }}</h1> <p>Published: {{ book.year|default:"unknown" }}</p> <p>{{ book.title|upper }}</p> <p>{{ book.description|truncatewords:20 }}</p>

Jinja2 (FastAPI) vs Django Template Language

Jinja2
{{ book.get_display_name() }} {% for book in books %} {{ book.title }} {% endfor %}

Method calls with parentheses and arguments are allowed directly in the template.

Django Template Language
{{ book.get_display_name }} {% for book in books %} {{ book.title }} {% endfor %}

No parentheses โ€” a no-argument method is called automatically without them; arbitrary Python calls with arguments aren't allowed at all.

Logic-Light by Design

DTL intentionally can't run arbitrary Python โ€” the idea is that business logic belongs in views.py and models, not scattered across templates.

Dot Lookup Does Everything

book.title tries, in order: dictionary lookup, attribute lookup, then a no-argument method call โ€” one syntax covers all three.

Control Flow Tags

<ul> {% if books %} {% for book in books %} <li>{{ book.title }} ({{ forloop.counter }} of {{ books|length }})</li> {% endfor %} {% else %} <li>No books yet.</li> {% endif %} </ul>

forloop.counter is a DTL-specific convenience โ€” a 1-indexed loop counter available automatically inside any {% for %}, with no separate variable to pass in from the view.

๐Ÿ›ก๏ธ Auto-Escaping

Every variable is HTML-escaped by default โ€” the same defense the XSS course covered as the primary fix for output-context injection, applied automatically here without you having to remember to call an escape function:

# If book.title == '<script>alert("xss")</script>' {{ book.title }} # renders as the literal text &lt;script&gt;alert("xss")&lt;/script&gt; โ€” inert, not executed {{ book.title|safe }} # โš ๏ธ opts OUT of escaping โ€” only use this for content you trust completely, # e.g. HTML you sanitized yourself with a library, per the XSS course's DOMPurify chapter

Template Inheritance: extends and block

Nearly every page shares a header, nav, and footer โ€” inheritance defines that shared shell once, in a base template, and lets each page override just the parts that differ:

A Base Template and a Child Page

<!-- catalog/templates/catalog/base.html --> <!DOCTYPE html> <html> <head> <title>{% block title %}My Library{% endblock %}</title> </head> <body> <nav>Home | Books</nav> {% block content %}{% endblock %} <footer>&copy; 2026</footer> </body> </html> <!-- catalog/templates/catalog/book_list.html --> {% extends "catalog/base.html" %} {% block title %}All Books{% endblock %} {% block content %} <ul> {% for book in books %} <li>{{ book.title }}</li> {% endfor %} </ul> {% endblock %}

{% extends %}

Must be the very first tag in a child template โ€” declares which base template this page fills in.

{% block %} / {% endblock %}

Marks a named region a child can override; a block left unfilled in the child just uses the base template's default content.

{% include %}

Pulls in a separate template fragment inline ({% include "catalog/_book_card.html" %}) โ€” for reusable partials rather than a whole-page shell.

Deep Inheritance Chains

A child template can itself be extended by a grandchild โ€” base.html โ†’ catalog/base.html โ†’ book_list.html is a common real pattern.

๐Ÿ’ป Coding Challenges

Challenge 1: Render a List With Filters

Write a template that loops over a books context variable, printing each book's title in uppercase and its description truncated to 15 words, with a fallback message if the list is empty.

Goal: Practice {% for %}/{% if %} tags together with the upper and truncatewords filters.

โ†’ Solution

Challenge 2: Build a Base Template + Two Child Pages

Write a base.html with a title block and a content block, then two child templates (book_list.html and book_detail.html) that each {% extends %} it and fill in both blocks differently.

Goal: Practice the extends/block inheritance pattern across more than one page.

โ†’ Solution

Challenge 3: Demonstrate Auto-Escaping

Pass a string containing <script> tags into a template context, render it two ways โ€” once as plain {{ value }} and once as {{ value|safe }} โ€” and explain what each one actually sends to the browser and why one is dangerous outside of trusted content.

Goal: Practice recognizing auto-escaping in action and understanding exactly what |safe turns off.

โ†’ Solution

โš ๏ธ Gotcha: A Typo Renders as Nothing, Not an Error

Reference {{ boook.title }} (misspelled) or a context variable that was never passed in, and DTL doesn't raise an exception โ€” it silently renders an empty string and moves on. A page that's missing text with no error in the console or logs is very often exactly this: a typo'd variable name in a template. When something looks blank that shouldn't be, check the exact variable name against what the view actually passed into render()'s context dict before assuming the bug is elsewhere.

๐ŸŽฏ What's Next

Templates can now display data โ€” the next chapter covers where that data actually comes from: Models & the ORM, defining database tables as Python classes, migrations, and querying with Django's QuerySet API.