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:

include-tag-exampletag.png

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 hardcoding 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.

Sitewide 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. Therefore, we can store these sitewide templates almost anywhere.

To keep things 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. The DIRS key specifies a list of paths where template engine should look for templates. To make Django search for sitewide templates in our newly created templates directory we have to provide DIRS the absolute path to it.

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.

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__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.

django_project_directory_structure

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 templates 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:

base_dir-value-GCRDKI.png

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:

base_dir-value-in-console.png

Creating Sitewide 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 sitewide 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 overridden 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 template from the blog app, delete everything in it and then add the following code.

blog/templates/blog/datetime.html

{% 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>

{% endblock %}

</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 in child template we are not overriding title and footer blocks, so the default content from parent template will be be used while rendering 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:

datetime-template-as-child-template-UOUTTN.png

To change the title in the child template add the title block in 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 %}

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

{% endblock %}

</body>
</html>

Refresh the page and you will get following output:

child-template-overriding-content-XDBMZG.png

Say you have overridden a block in the child template but you still want default content from the parent template. To do that use {{ block.super }}. Modify the footer block in 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 %}

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

{% 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 the child template.

Refresh the page and you will get the following output:

referring-to-parent-content-block-super.png