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:
🧵 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:
Jinja2 (FastAPI) vs Django Template Language
Jinja2
Method calls with parentheses and arguments are allowed directly in the template.
Django Template Language
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
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:
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
{% 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.
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.
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.
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.