Difficulty: Intermediate
Template inheritance is Django's most powerful tool for reducing template duplication. It allows you to build a base 'skeleton' template containing common elements (header, navigation, footer) and define blocks that child templates can override. This follows the DRY principle - common HTML is written once in the base template and inherited by all pages.
The base template uses {% block %} tags to define regions that child templates can fill or override. Each block has a name: {% block content %}{% endblock %}. The base template typically includes the HTML boilerplate, header, navigation, footer, and any shared CSS/JS resources. Blocks can have default content that child templates optionally override.
Child templates use {% extends %} as their very first tag (it must be the first template tag) to specify which template they inherit from. Then they define {% block %} tags to fill in the parent's blocks. Any content outside of block tags in a child template is ignored.
You can access the parent block's content using {{ block.super }}. This is useful when you want to add to the parent's content rather than replace it entirely. For example, a child template might add page-specific CSS to the parent's CSS block using {{ block.super }} followed by additional styles.
Template inheritance can be multi-level. A common pattern is three-level inheritance: base.html (site-wide layout) -> section_base.html (section-specific layout, like blog or shop) -> page.html (individual page content). Each level extends the one above and overrides specific blocks.
The {% include %} tag inserts another template at that point. Unlike {% extends %}, which uses the parent as a skeleton, {% include %} embeds a template fragment. It is ideal for reusable components like navigation bars, card widgets, pagination controls, and form snippets. You can pass context variables to included templates using the 'with' keyword.
Template configuration in settings.py determines where Django looks for templates. DIRS lists directories to search (typically a project-level templates/ directory). APP_DIRS, when True, tells Django to look in each app's templates/ directory. Templates in DIRS take priority over APP_DIRS, allowing project-level templates to override app-level templates.
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Site{% endblock %}</title>
{% block extra_css %}{% endblock %}
</head>
<body>
<header>
<nav>
<a href="{% url 'home' %}">Home</a>
<a href="{% url 'blog:post-list' %}">Blog</a>
{% if user.is_authenticated %}
<span>{{ user.username }}</span>
<a href="{% url 'logout' %}">Logout</a>
{% else %}
<a href="{% url 'login' %}">Login</a>
{% endif %}
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2024 My Site</p>
</footer>
{% block extra_js %}{% endblock %}
</body>
</html>
The base template defines the site-wide HTML structure. {% block %} tags mark regions that child templates can fill. Blocks like extra_css and extra_js allow child templates to inject page-specific resources.
<!-- templates/blog/article_list.html -->
{% extends 'base.html' %}
{% block title %}Blog - My Site{% endblock %}
{% block content %}
<h1>Blog Posts</h1>
{% for article in articles %}
<article>
<h2><a href="{{ article.get_absolute_url }}">{{ article.title }}</a></h2>
<p>{{ article.content|truncatewords:30 }}</p>
<small>{{ article.published_at|date:"M d, Y" }}</small>
</article>
{% empty %}
<p>No articles yet.</p>
{% endfor %}
{% if is_paginated %}
<nav>
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
<span>Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Next</a>
{% endif %}
</nav>
{% endif %}
{% endblock %}
The child template extends base.html and fills in the title and content blocks. Everything outside block tags is ignored. The parent's header, footer, and boilerplate are inherited automatically.
<!-- templates/base.html -->
<head>
{% block css %}
<link rel="stylesheet" href="/static/css/main.css">
{% endblock %}
</head>
<!-- templates/blog/base_blog.html -->
{% extends 'base.html' %}
{% block css %}
{{ block.super }}
<link rel="stylesheet" href="/static/css/blog.css">
{% endblock %}
{% block content %}
<div class="blog-layout">
<div class="blog-content">
{% block blog_content %}{% endblock %}
</div>
<aside>
{% include 'blog/_sidebar.html' %}
</aside>
</div>
{% endblock %}
<!-- templates/blog/article_detail.html -->
{% extends 'blog/base_blog.html' %}
{% block css %}
{{ block.super }}
<link rel="stylesheet" href="/static/css/article.css">
{% endblock %}
{% block blog_content %}
<h1>{{ article.title }}</h1>
<div>{{ article.content|safe }}</div>
{% endblock %}
Three-level inheritance: base.html -> base_blog.html -> article_detail.html. Each level extends the one above. {{ block.super }} includes the parent's block content, so CSS accumulates: main.css + blog.css + article.css.
<!-- templates/components/_card.html -->
<div class="card">
<h3>{{ card_title }}</h3>
<p>{{ card_body }}</p>
{% if card_link %}
<a href="{{ card_link }}">Read more</a>
{% endif %}
</div>
<!-- templates/home.html -->
{% extends 'base.html' %}
{% block content %}
<div class="grid">
{% for article in articles %}
{% include 'components/_card.html' with card_title=article.title card_body=article.excerpt card_link=article.get_absolute_url %}
{% endfor %}
{# Include with 'only' limits context to specified variables #}
{% include 'components/_card.html' with card_title="Subscribe" card_body="Get updates" only %}
</div>
{% endblock %}
{% include %} embeds a template fragment. The 'with' keyword passes variables. The 'only' keyword restricts the included template's context to only the specified variables, preventing access to the parent template's context.
extends, block, Base Template, include, Template Hierarchy