Views & Templates: Django's MVT Model
Website Rebuild with Django
Chapter 4 · Views & Templates: Django's MVT Model
Chapter 2 built the Model; Chapter 3 wired the routing that finds one. This chapter is the other two letters of MVT — the View and the Template — and Django's own answer to what Website Rebuild with Next.js 4 solved with React components. The mechanism is genuinely different: no JSX, no component props, and — deliberately — no arbitrary code running inside a template file at all.
Template Inheritance: extends and block
base.html defines the page's overall shell — the same job Next.js Rebuild 4's own shared Layout component did — with named {% block %} slots left open. page_detail.html extends it and only fills in the specific blocks it actually needs; everything else in the shell is inherited unchanged.
The Django Template Language: Deliberately Not a Programming Language
{{ variable }} outputs a value, auto-escaped by default — the same automatic XSS protection JSX gives Next.js, just via a different mechanism. {% if %}/{% for %} and a limited set of filters (|truncatewords, |date, and the rest) cover most real templating needs. What DTL deliberately doesn't have is arbitrary function calls, real expressions, or general-purpose logic — no way to write a genuine loop-inside-a-loop tree walk, or call an arbitrary Python function with arguments, directly inside a template file.
Building the Breadcrumb: Logic in the View, Not the Template
Walking node.parent upward — Chapter 2's own adjacency list, not the materialized full_path Chapter 3 used for the lookup — happens entirely in Python, producing an already-flat list before the template ever sees it. The template's own job is trivial by comparison:
{% for %} in the template — is Django's own "thin templates" philosophy made concrete. Every genuinely tricky part of the breadcrumb (walking an unknown number of levels upward, in the correct order) lives in ordinary, testable Python, exactly where Chapter 2's own model design intended the adjacency list to be used. The template only ever iterates a list that already exists — it was never capable of doing that walk itself, and that's a deliberate design choice, not a missing feature: a template language that could run arbitrary logic is also one where a template-injection-style bug becomes possible in the first place.
{{ page.body }} alone would auto-escape any HTML inside page.body, turning real markup into visible <p> text on the page — not what's wanted, since body genuinely stores HTML. |safe disables that escaping, and it's the right call here specifically because page.body is written exclusively by the authenticated site admin (Chapter 9), never submitted directly by an anonymous visitor. Applying |safe to anything a visitor could actually type into a form would reopen exactly the XSS risk Django's own auto-escaping exists to close.
Context Processors: Site-Wide Data Without Repeating Yourself
Once registered, {{ current_year }} is available in every template, site-wide, with no view ever needing to pass it explicitly — the same "shared, always-available data" goal a Next.js layout component solves by simply always rendering, just reached through Django's own request-scoped context mechanism instead.
Django's Templates vs. Next.js's React Components
| Next.js | Django | |
|---|---|---|
| Composition unit | A React component — real JavaScript, JSX, props, children | A template file — {% %}/{{ }} tags only |
| Shared layout | A shared Layout component wrapping page content | Template inheritance — {% extends %} + named {% block %} slots |
| Logic inside the view layer | Full JavaScript, directly inside the component | Deliberately restricted — real logic belongs in the view (Python), not the template |
Hands-On Exercises
Explain why the breadcrumb is built by walking page.parent in the view (Python) rather than attempting that same walk directly inside the template with DTL tags. What does this illustrate about Django's own "thin templates" philosophy?
Explain why {{ page.body|safe }} is used instead of plain {{ page.body }}, and why this specific use of |safe is considered acceptable rather than a security risk.
Explain what a context processor does, using the current_year example, and why it's a better fit here than passing current_year explicitly from every individual view function.
Chapter 4 Quick Reference
{% extends %}/{% block %}— template inheritance; Django's own answer to a shared Next.js layout component- DTL is deliberately limited —
{{ }}/{% %}tags and filters only, no arbitrary code execution - Breadcrumb built in the view — walking
page.parentin Python, handed to the template as an already-flat list - "Thin templates" — real logic belongs in views/models, not templates; a deliberate separation-of-concerns choice
|safe— disables auto-escaping; only appropriate for trusted, admin-authored content, never for visitor-submitted input- Context processors — make data available to every template automatically, without passing it from every view
- Next chapter: Styling — Dark Theme