Template Inheritance & Includes

The task list template from the last lesson only showed a <ul> of tasks — a real TaskFlow page also needs a navigation bar, a page title, maybe a footer, and that same scaffolding has to appear identically on every single page in the app. Copy-pasting that HTML into every template is exactly the kind of repetition Django templates are built to avoid, through inheritance and includes.

The Copy-Paste Problem Inheritance Solves

Without inheritance, updating TaskFlow's navigation bar would mean editing the same HTML in task_list.html, task_detail.html, project_list.html, and every other template — and forgetting one leaves that page looking subtly broken. Template inheritance flips the relationship: one template defines the shared page skeleton once, and every other template fills in only the part that's actually different.

Building a Base Template with a Block

A base template is ordinary HTML with one or more {% block %} regions marking the spots a child template is allowed to override. Anything outside a block is fixed and appears on every page unchanged.

{# taskflow/templates/base.html #}
<!DOCTYPE html>
<html>
<head>
  <title>TaskFlow</title>
</head>
<body>
  <nav>
    <a href="/">TaskFlow</a>
    <a href="/tasks/">Tasks</a>
    <a href="/projects/">Projects</a>
  </nav>

  <main>
    {% block content %}
    {% endblock %}
  </main>

  <footer>© TaskFlow</footer>
</body>
</html>

The {% block content %}{% endblock %} pair marks a named, overridable region called content. Left empty here, it's just a placeholder waiting for a child template to supply something.

Extending the Base Template in a Child

A child template starts with {% extends %} naming the base template, then defines a {% block %} with a matching name — only the content inside that block replaces the base's version. Everything else in base.html (the nav, the footer, the surrounding HTML) is inherited automatically.

{# taskflow/templates/taskflow/task_list.html #}
{% extends 'base.html' %}

{% block content %}
  <h1>Your Tasks</h1>
  <ul>
  {% for task in tasks %}
    <li>{{ task.title }}</li>
  {% empty %}
    <li>No tasks yet.</li>
  {% endfor %}
  </ul>
{% endblock %}

{% extends %} must be the very first tag in the file — nothing, not even a comment, can come before it. Anything in task_list.html outside of a {% block %} is simply ignored, since the base template controls the overall page structure.

Multiple Named Blocks in One Base Template

A base template isn't limited to one block. Adding a second block for the page's <title> lets each child control the browser tab title without touching anything else:

{# taskflow/templates/base.html #}
<head>
  <title>{% block title %}TaskFlow{% endblock %}</title>
</head>
<body>
  <main>
    {% block content %}
    {% endblock %}
  </main>
</body>
{# taskflow/templates/taskflow/task_list.html #}
{% extends 'base.html' %}

{% block title %}Your Tasks - TaskFlow{% endblock %}

{% block content %}
  <h1>Your Tasks</h1>
  {# ... task list markup ... #}
{% endblock %}

Note that {% block title %}TaskFlow{% endblock %} in the base template supplies a default — a child template that doesn't override title at all still gets "TaskFlow" as the page title, since an un-overridden block just renders its own content.

Reusable Fragments with {% include %}

Inheritance solves whole-page structure, but a single task also shows up as a small, self-contained card in more than one place — the task list page and, later, a dashboard page. Rebuilding that markup with {% extends %} doesn't make sense, since a task card isn't a full page. {% include %} pulls in a smaller template fragment right where it's referenced:

{# taskflow/templates/partials/task_card.html #}
<div class="task-card">
  <strong>{{ task.title }}</strong>
  <p>{{ task.description|truncatewords:15 }}</p>
  <span>{{ task.due_date|date:"M d, Y"|default:"No due date" }}</span>
</div>
{# taskflow/templates/taskflow/task_list.html #}
{% extends 'base.html' %}

{% block content %}
  <h1>Your Tasks</h1>
  {% for task in tasks %}
    {% include 'partials/task_card.html' %}
  {% endfor %}
{% endblock %}

The distinction to keep straight: extends is for whole-page inheritance — a child template fills in gaps in one parent page structure, and a template can only extend one base. include is for a reusable snippet — pulling one smaller template into another, as many times, and in as many different templates, as needed.

Passing Extra Context to an Include

Inside the loop above, {% include %} already has access to task, since included templates share the surrounding template's context by default. When the variable name doesn't line up — say, a dashboard page tracks a single highlighted task under the name current_task instead of task — with maps it explicitly:

{# taskflow/templates/taskflow/dashboard.html #}
{% extends 'base.html' %}

{% block content %}
  <h2>Today's Focus</h2>
  {% include 'partials/task_card.html' with task=current_task %}
{% endblock %}

task_card.html still refers to task internally and needs no changes at all — the with task=current_task clause is what bridges the naming gap between the two templates.

Next up: The |truncatewords and |date filters used throughout this lesson all ship with Django. Custom Template Tags & Filters shows how to add your own when TaskFlow needs a formatting rule Django doesn't provide.