Template Inheritance in Django

Including templates using include tag

include tag allows us to include the contents of a template inside another template. Here is the syntax of the include tag:

{% include template_name %}

The template_name could be a string or a variable.

Lets take an example:

Create a new file named nav.html for the blog app in the directory blog/templates/blog and add the following code to it:

<nav>
    <a href="#">Home</a>
    <a href="#">blog</a>
    <a href="#">Contact</a>
    <a href="#">Career</a>
</nav>

Say you want to include the content of nav.html in the datetime.html file. To do this add
{% include 'blog/nav.html' %} to the datetime.html as follows:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {% include 'blog/nav.html' %}

    {# This is a comment #}

    {# check the existence of now variable in the template using if tag #}

    {% if now %}   
        <p>Current date and time is {{ now }}</p>
    {% else %}               
        <p>now variable is not available</p>
    {% endif %}

    <p>Current scheme {{ request.scheme }}</p>

</body>
</html>

Open your browser and visit http://127.0.0.1:8000/blog/time/, you should be able see a simple navigation bar at the top of the page navigation like this:

Notice that in the include tag we are using the same convention to specify template name that we have used in the get_template() and render() method. Instead of harcoding the template name directly in the template we can also pass it using a context variable.

Open views.py file inside blog app. At this point today_is() view function should look like this:

def today_is(request):
    now = datetime.datetime.now()
    return render(request, 'blog/datetime.html', {'now': now })

Modify today_is() view function as follows:

def today_is(request):
    now = datetime.datetime.now()
    return render(request, 'blog/datetime.html', {'now': now, 'template_name': 'blog/nav.html' })

Open datetime.html and replace {% include "blog/nav.html" %} with {% include template_name %}.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {% include template_name %}

    {# This is a comment #}

    {# check the existence of now variable in the template using if tag #}

    {% if now %}   
        <p>Current date and time is {{ now }}</p>
    {% else %}               
        <p>now variable is not available</p>
    {% endif %}

    <p>Current scheme {{ request.scheme }}</p>

</body>
</html>

Refresh the page at http://127.0.0.1:8000/blog/time/ and the page should display current date and time just as before.

What if include fails to find the template ??

In that case Django will do one the following two things:

1) If DEBUG mode is set to True then Django template system will throw TemplateDoesNotExist exception.

2) If DEBUG is set to False then it will fail silently displaying nothing in the place of include tag.

Template Inheritance

So far we have been creating very simple templates but in the real world this is rarely the case. To give a common look and feel across all the pages of a website some HTML code is repeated. Typically header, navigation, footer and sidebar remains the same throughout the site. The problem arises when we want to modify some part of the page. To better understand the issue let's take an example.

Consider we have two pages namely home.html and contact.html.

home.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
</head>
<body>
    
    <nav>
        <a href="#">Home</a>
        <a href="#">blog</a>
        <a href="#">Contact</a>
        <a href="#">Career</a>
    </nav>

    <aside>
        <h3>Ut enim ad minim veniam</h3>
        <p>
            Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod            
            quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo.
        </p>
    </aside>

    <article>
        <p>
            Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
            tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam
        </p>
    </article>

    <footer> &copy; 2017. All rights reserved </footer>

</body>
</html>

contact.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Contact</title>
</head>
<body>
    
    <nav>
        <a href="#">Home</a>
        <a href="#">blog</a>
        <a href="#">Contact</a>
        <a href="#">Career</a>
    </nav>

    <aside>
        <h3>Ut enim ad minim veniam</h3>
        <p>
            Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod            
            quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo.
        </p>
    </aside>

    <article>
        <p>
            <form>
                 Name: <input type="text" name="name" required> <br>
                 Email: <input type="email" name="email" required> <br>
                 Query: <textarea required></textarea> <br>
                 <input type="submit" name="submit" value="Send">
            </form>
        </p>
    </article>

    <footer> &copy; 2017. All rights reserved </footer>

</body>
</html>

Say at the time of deployment the company decided to change anchor text of a link in the navigation from Career to Jobs. To make this change we have to manually visit each and every page. What if we had 50 or 100 pages ? These days it is very common for sites to have hundreds or even thousands of pages. As you can see this method doesn't scale very well.

If you are reading all these pages carefully, you might say "Hey why don't we use include tag ?". Yes, sure we can use include tag to solve this particular problem very easily but as we will see Template Inheritance provides a much more powerful and elegant method to solve this issue.

Site wide templates

Django Templates which we are going to create in the upcoming section are not specific to any app. In fact they can be used by all the installed apps in the project. As already discussed, by default Django looks for templates in the templates directory of every installed app. But Django doesn't have any default location folder to look for templates that are not specific to any app or. Therefore we can store these sitewide templates almost anywhere.

To keep thigs simple and organized we will store our sitewide templates in the templates directory inside the project root directory (i.e where manage.py file is located). Create a new directory called templates at TGDB/django_project.

Now we have to inform Django about this directory. Open settings.py file located at TGDB/django_project/django_project and find TEMPLATES variable. This variable contains every setting related to the templates. If you haven't changed anything it should look like this:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

The setting we are concerned here is the DIRS key. Currently it is set to empty list. We have to provide DIRS the absolute path to the directory containing sitewide templates.

On my PC the absolute path to templates is C:/Users/Q/TGDB/django_project/templates. Change the DIRS key so that it looks like this.

'DIRS': ['C:/Users/Q/TGDB/django_project/templates']

You should replace Q with your username.

There is one problem though, if you send your Django project to someone else it will not work unless you and that person share exactly same username, drive letter and directory structure. The problem is that we are hardcoding paths. Hardcoding paths makes our project less portable - meaning it might not run on other computers.

The solution is to use dynamic paths.

Notice the BASE_DIR variable at the top of the settings.py file. This variable stores the path to Django project root directory. In my case it points to C:/Users/Q/TGDB/django_project. On your machine it might be different. Now we have access to Django project root directory in the form of BASE_DIR. As a result, it becomes very easy to refer to other directories. Our sitewide templates directory is inside the directory pointed to by BASE_DIR.

We can now use python's os.path.join() function to join multiple paths. Update 'DIRS' value so that it looks like this:

'DIRS': [ os.path.join(BASE_DIR, 'templates'), ],

From now on Django will search this directory for templates.

I know some of you might be thinking "How do I print the value of variable BASE_DIR variable ?". Open views.py in the blog app and modify the file as follows.

from django.http import HttpResponse
from django.shortcuts import render_to_response, render
from django.conf import settings
import datetime

...

def today_is(request):
    now = datetime.datetime.now()
    return render(request, 'blog/datetime.html', {
                                    'now': now,
                                    'template_name': 'blog/nav.html' ,
                                    'base_dir': settings.BASE_DIR }
                                )

Open datetime.html and append following <p> tag at the end of the file.

<p>BASE_DIR : {{ base_dir }}</p>

Open the browser and visit http://127.0.0.1:8000/blog/time/, you should see a page like this:

However, If you want a quick and dirty method to print BASE_DIR without modifying views and templates then open settings.py and add the following print() statement just after the BASE_DIR variable.

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

print("BASE_DIR  = ",  BASE_DIR)

Save the file. Start the development sever if not already running and you will see value of BASE_DIR variable in the command prompt or terminal as follows:

Creating Site Wide Templates

To use Template Inheritance we must first create a base template. A base template is just simple skeleton that we will be used as a base for all other pages on our site. Here is the most important thing to remember about base template - A base template defines areas where content can be inserted into. In Django we define this area using the block tag. Create a new file named base.html with the following code inside the sidewide templates directory (not inside the blog app's templates directory).

<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}The Great Django Blog{% endblock %}</title>
</head>
<body>
    
    <nav>
        <a href="#">Home</a>
        <a href="#">blog</a>
        <a href="#">Contact</a>
        <a href="#">Career</a>
    </nav>

    {% block content %}

    {% endblock %}

    <hr>

    {% block footer %}
        This is footer
    {% endblock %}

</body>
</html>

This is our base template. Here we are using a new template tag that you haven't encountered yet. The block tag accepts a single argument, block_name to specify the name of the block. Here we have created three blocks namely title, content and footer. The block tag defines a block that can be overidden by the child templates. What is a child template ? A template which uses base.html as it base or more formally speaking a child template is a template which extends the base template. A child template can override, add or leave alone the contents of the parent block.

To provide some default content to the child templates add the content between {% block %} and {% endblock %}.

To make use of the base template we have to first create child template. Open datetime.html delete everything and then add the following code.

{% extends "base.html" %}

{% block content %}

    {% if now %}
        <p>Current date and time is {{ now }}</p>
    {% else %}
        <p>now variable is not available</p>
    {% endif %}

    <p>Current scheme: {{ request.scheme }}</p>
    <p>BASE_DIR : {{ base_dir }}</p>

</body>
</html>

Here is how it works:

In line 1, {% extends "base.html" %} tells Django template engine that the current template is a child template and inherits from base.html. This must be first line in the child template otherwise it won't work. The engine then immediately loads the parent template i.e base.html in our case.

The engine then replaces the blocks of content in parent template with the blocks of content of same name defined in the child template. If a block of matching name is not found in the child template then content from the parent template is used. Notice that we are not overriding title and footer blocks, so the default content from parent template will be be used in the child template.

Start the server if not already running and visit http://127.0.0.1:8000/blog/time/, you should get the following output:

To change the title in the child template modify datatime.html as follows:

{% extends "base.html" %}

{% block title %}
    Title from child template
{% endblock %}

{% block content %}

    {% if now %}  
        <p>Current date and time is {{ now }}</p>
    {% else %}              
        <p>now variable is not available</p>
    {% endif %}       

{% endblock %}

Refresh the page and you will get following output:

Say you have overidden a block in the child template but you still want default content from the parent template. To do that use {{ block.super }}. Modify datatime.html as follows:

{% extends "base.html" %}

{% block title %}
    Title from child template
{% endblock %}

{% block content %}

    {% if now %}  
        <p>Current date and time is {{ now }}</p>
    {% else %}              
        <p>now variable is not available</p>
    {% endif %}       

{% endblock %}

{% block footer %}
    {{ block.super }}
    <p>All rights reserved</p>
{% endblock %}

Although we are overriding footer in the child template, {{ block.super }} allows us to refer to the default content from the parent template inside child template.

Refresh the page and you will get the following output: