Skip to main content

Building Layouts with Inheritance

Template inheritance is a powerful mechanism for creating modular and reusable UI structures. By using the BlockNode, ExtendsNode, and IncludeNode classes, you can define a base layout and override specific sections in child templates without repeating boilerplate code.

This tutorial walks you through building a multi-level layout similar to the one used in the Django admin interface.

Prerequisites

To follow this tutorial, you should have a basic Django project structure where templates are loaded via the standard engine. The logic described here is implemented in django/template/loader_tags.py.

Step 1: Define the Base Layout

First, you need a base template that defines the skeleton of your site. You do this using the {% block %} tag, which is represented internally by the BlockNode class.

Create a file named base.html:

<!-- base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My Site{% endblock %}</title>
</head>
<body>
<div id="header">
{% block branding %}<h1>Default Header</h1>{% endblock %}
</div>
<div id="content">
{% block content %}{% endblock %}
</div>
</body>
</html>

How it works: Each {% block %} tag creates a BlockNode. When this template is rendered directly, the BlockNode.render() method simply renders the content inside the block. If a child template extends this one, these blocks act as placeholders that the child can fill.

Step 2: Extend the Base Template

Now, create a child template that "fills in" the blocks defined in base.html. This uses the {% extends %} tag, which is handled by the ExtendsNode class.

Create a file named child.html:

<!-- child.html -->
{% extends "base.html" %}

{% block title %}Child Page{% endblock %}

{% block content %}
<p>This is the main content of the child page.</p>
{% endblock %}

How it works: The ExtendsNode has a strict requirement: must_be_first = True. This means {% extends %} must be the very first tag in your template. When ExtendsNode.render() is called, it:

  1. Locates the parent template (base.html).
  2. Creates a BlockContext to manage the stack of blocks.
  3. Collects all BlockNode instances from the child and adds them to the BlockContext.
  4. Renders the parent template, which now uses the child's blocks instead of its own.

Step 3: Preserve Parent Content with block.super

Sometimes you don't want to completely replace a block, but rather add to it. You can achieve this using {{ block.super }}.

Update child.html to include a custom script while keeping the parent's title:

<!-- child.html -->
{% extends "base.html" %}

{% block title %}{{ block.super }} | Dashboard{% endblock %}

{% block content %}
<p>Welcome to the dashboard.</p>
{% endblock %}

How it works: The BlockNode.super() method checks the BlockContext for the next version of the block in the inheritance chain. If found, it renders that version and returns the safe HTML.

Note: If you use {{ block.super }} in a base template that doesn't extend anything, BlockNode will raise a TemplateSyntaxError because there is no parent context to reference.

Step 4: Modularize with Includes

For smaller, reusable components like sidebars or navigation menus, use the {% include %} tag. This is handled by the IncludeNode class.

Create a file named sidebar.html:

<!-- sidebar.html -->
<div class="sidebar">
<h3>Links for {{ user_name }}</h3>
<ul>
<li><a href="/profile/">Profile</a></li>
</ul>
</div>

Then, include it in your child.html:

<!-- child.html -->
{% extends "base.html" %}

{% block content %}
{% include "sidebar.html" with user_name="Alex" %}
<p>Main content area.</p>
{% endblock %}

How it works: The IncludeNode resolves the template name and renders it. By default, it shares the current context. However, you can pass specific variables using the with keyword.

If you want to isolate the included template so it only has access to the variables you explicitly pass, use the only keyword:

{% include "sidebar.html" with user_name="Alex" only %}

In this case, IncludeNode sets isolated_context=True and creates a fresh context for the sub-template.

Summary of Results

By combining these nodes, you have built a layout where:

  1. ExtendsNode manages the high-level hierarchy.
  2. BlockContext tracks which version of a BlockNode to show.
  3. IncludeNode handles small, repeatable fragments with controlled context.

This structure ensures that changes to base.html automatically propagate to all child templates, maintaining a DRY (Don't Repeat Yourself) codebase.