Build HTML with the Django Template Language — variables, filters, the if and for tags, the static and url tags, template inheritance, and your own custom filters.
Where: by default Django looks in a templates/ folder inside each app. Namespacing it as blog/templates/blog/ prevents two apps from clashing over a name like list.html. Your view then renders "blog/post_list.html".
blog/
└── templates/
└── blog/ # namespaced by app
├── base.html
├── post_list.html
└── post_detail.htmlWhy: {{ }} prints a value from the context. The dot tries attribute, then dictionary key, then list index — so post.title, post.author.username, and even method calls like post.get_absolute_url all use the same dot syntax (no parentheses).
<h1>{{ post.title }}</h1>
<p>by {{ post.author.username }}</p>
<p>{{ post.created_at }}</p>
<!-- dot also calls methods, with no () -->
<a href="{{ post.get_absolute_url }}">Read more</a>Why: filters transform a value for display with the | pipe — formatting dates, providing fallbacks, truncating text, escaping or counting. You can chain them. Note: they never change the underlying data, only how it is shown.
{{ post.created_at|date:"M d, Y" }} <!-- Jun 19, 2026 -->
{{ post.subtitle|default:"No subtitle" }} <!-- fallback if empty -->
{{ post.body|truncatewords:30 }} <!-- first 30 words -->
{{ post.title|upper }}
{{ post.tags.all|length }} <!-- chain-friendly -->
{{ post.body|linebreaks }} <!-- newlines → <p> -->Why: put the shared shell (header, nav, footer) in one base.html with {% block %} holes, then each page {% extends %} it and fills the blocks. Note: this is the single most important template feature — it kills copy-pasted layout.
<!-- blog/templates/blog/base.html -->
<!DOCTYPE html>
<html>
<head><title>{% block title %}My Blog{% endblock %}</title></head>
<body>
<header>My Blog</header>
<main>{% block content %}{% endblock %}</main>
</body>
</html><!-- blog/templates/blog/post_list.html -->
{% extends "blog/base.html" %}
{% block title %}All posts{% endblock %}
{% block content %}
{% for post in posts %}
<h2>{{ post.title }}</h2>
{% endfor %}
{% endblock %}When {% include %}: reuse a fragment (a post card, a pagination bar) across pages, passing variables with "with". Note: {# #} is a one-line comment and {% comment %} blocks out several lines — neither reaches the browser.
{% include "blog/_post_card.html" with post=post %}
{# a short, one-line comment — never sent to the browser #}
{% comment %}
This whole block is ignored, handy for disabling markup.
{% endcomment %}