Templates

A template is an HTML file with a few extra pieces of syntax for dropping in Python values and light logic. It's how Django keeps HTML out of your view functions and view logic out of your HTML.

Rendering a template

Templates live in a templates/ folder inside an app, and a view uses render() instead of building an HttpResponse by hand:

HTML blog/templates/home.html
<h1>{{ page_title }}</h1>
<p>We have {{ post_count }} posts.</p>
Python blog/views.py
from django.shortcuts import render

def home(request):
    return render(request, 'home.html', {
        'page_title': 'Welcome',
        'post_count': 12,
    })
Rendered HTML sent to the browser
<h1>Welcome</h1>
<p>We have 12 posts.</p>

The third argument to render() is the context — a dictionary whose keys become variables the template can use with {{ }}. Django substitutes each one directly into the output.

Tags: loops and conditionals

Anything that isn't a plain variable — loops, if-statements, template inheritance — uses {% %} instead:

HTML blog/templates/post_list.html
<ul>
{% for post in posts %}
    <li>{{ post.title }}{% if post.featured %} — Featured{% endif %}</li>
{% endfor %}
</ul>

{% for %} and {% if %} both need an explicit closing tag ({% endfor %}, {% endif %}) — there's no indentation-based block like in Python itself.

Template inheritance

Most pages on a site share a header, navigation, and footer. A base template defines that shared layout with named blocks that child templates fill in:

HTML blog/templates/base.html
<html>
<body>
  <nav>My Site</nav>
  {% block content %}{% endblock %}
</body>
</html>
HTML blog/templates/home.html
{% extends "base.html" %}

{% block content %}
  <h1>{{ page_title }}</h1>
{% endblock %}

{% extends %} must be the very first line of a child template. Everything the child puts inside {% block content %} gets inserted into the matching block in base.html — so every page that extends it automatically shares the same nav bar without repeating it.

Note: the template language deliberately doesn't let you run arbitrary Python — no assignment, no calling functions with arguments, only a fixed set of tags and simple attribute/method lookups like post.title. This is intentional: it keeps real logic in views, where it belongs, instead of scattered across HTML files.