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.