In the previous Understand Django article, we looked at the fundamentals of using views in Django. This article will focus on templates. Templates are your primary tool in a Django project for generating a user interface. Let's see how templates hook into views and what features Django provides with its template system.
Set Up Templates
We need a place for templates to live. Templates are static files that Django will fill in with data. In order to use those files, we must instruct Django on where to find them.
Like most parts of Django, this configuration is in your project's settings file. After you use startproject
, you can find a section in your settings file that will be called TEMPLATES
. The section should look something like:
# project/settings.py
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',
],
},
},
]
Django's template system can use multiple template backends. The backends dictate how your templates will work. I would recommend sticking with the default Django template language. This language has the tightest integration with the framework and the strongest support.
The next thing to notice is APP_DIRS
with its value of True
. For the Django template language, setting this value to True
will cause Django to look for template files within a templates
directory in each Django application in your project. Note that this also includes any third party applications so you should probably leave this to True
.
So, where should your templates go? There are different schools of thought in the Django community. Some developers believe in having all templates within applications. Others ascribe to having all your project's templates in a single directory. I'm in this second category of developers. I find it valuable to keep all of the templates for my entire project within a single directory.
From my perspective, keeping templates in a single directory makes it very clear where all the layout and UI in your system will live. To use that pattern, we must set the DIRS
variable with the directory that we want Django to include. I recommend keeping a templates
directory at the root of your project. If you do that, your DIRS
value will change to something like:
# project/settings.py
TEMPLATES = [
...
"DIRS": [os.path.join(BASE_DIR, "templates")],
...
]
Finally, there is OPTIONS
. Each backend can accept a variety of options. startproject
set a number of context processors. We'll come back to context processors later in this article.
With your templates set up, you're ready to go!
Using Templates With Render
Django builds your user interface by rendering a template. The idea behind rendering is that dynamic data is combined with a static template file to produce a final output.
To produce an HttpResponse
that contains rendered output, we use the render
function. Let's see an example.
# application/views.py
from django.shortcuts import render
def a_template_view(request):
context = {'name': 'Johnny'}
return render(request, 'hello.txt', context)
In this example, the view would use a template located in templates/hello.txt
which could contain:
Hello {{ name }}
When this view responds to a request, a user would see "Hello Johnny" in their browser. There are some interesting things to note about this example.
- The template can be any plain text file type. Most often we will use HTML to make a user interface so you will often see
some_template.html
, but the Django template system can render on any type. - In the process of rendering, Django took the context data dictionary and used its keys as variable names in the template. Because of special double curly brace syntax, the template backend swapped out
{{ name }}
for the literal value of "Johnny" that was in the context.
This idea of mixing context and static layout is the core concept of working with templates. The rest of this article builds on this root concept and shows what else is possible in the Django template language.
From the last article, you may recall seeing the TemplateView
. In those examples, we provided a template name, and I declared that Django would take care of the rest. Now you can start to understand that Django takes the template name and calls code similar to render
to provide an HttpResponse
. Those examples were missing context data to combine with the template. A fuller example to replicate what is above would look like:
# application/views.py
from django.views.generic.base import TemplateView
class HelloView(TemplateView):
template_name = 'hello.txt'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['name'] = 'Johnny'
return context
This example uses get_context_data
so that we can insert our "dynamic" data into the rendering system to give us the response we want.
In a real application, a lot of the code that we need to write focuses on building up a truly dynamic context. I'm using static data in these examples to keep the mechanics of the template system clear. When you see me use context, try to imagine more complex data building to create a user interface.
Those are the fundamentals of rendering. We'll now turn our attention to what the Django template language is capable of.
Templates In Action
When using templates, we take context data and insert it into the placeholders within the template.
This most basic form of filling placeholders with context are template variables. The previous section showed an example by using the name
variable. The context dictionary contained a name
key and double curly braces like {{ name }}
are where the name
value is used.
We can also use a dot access when the context data is more complex. Let's say your template gets context like:
context = {
'address': {
'street': '123 Main St.',
'city': 'Beverly Hills',
'state': 'CA',
'zip_code': '90210',
}
}
Your Django template won't work if you try to access this context data like a regular dictionary (e.g., {{ address['street'] }}
). Instead, you would use dot notation to get to the data in the dictionary.
The address is:
{{ address.street }}
{{ address.city }}, {{ address.state }} {{ address.zip_code}}
This would render as:
The address is:
123 Main St.
Beverly Hills, CA 90210
Django templates also try to be flexible with the types of context data. You could also pass in a Python class instance like an Address
class with attributes that are the same as the keys in our previous dictionary. The template would work the same.
The core template language also includes some standard programming logic keywords by using tags. Template tags look like {% some_tag %}
whereas template variables look like {{ some_variable }}
. Variables are meant to be placeholders to fill in, but tags offer more power.
We can start with two core tags, if
and for
.
The if
tag is for handling conditional logic that your template might need.
{% if user.is_authenticated %}
<h1>Welcome, {{ user.username }}</h1>
{% endif %}
This example will only include this welcome message HTML header tag when the user is logged in to the application. We started the example with an if
tag. Observe that the if
tag requires a closing endif
tag. Templates must respect whitespace since your layout might depend on that whitespace. The template language can't use whitespace to indicate scope like it can with Python so it uses closing tags instead. As you might guess, there are also else
and elif
tags that are accepted inside of an if
/endif
pair.
{% if user.is_authenticated %}
<h1>Welcome, {{ user.username }}</h1>
{% else %}
<h1>Welcome, guest</h1>
{% endif %}
In this case, only one of the header tags will render depending on whether the user is authenticated or not.
The other core tag to consider is the for
loop tag. A for
loop in Django templates behaves as you might expect.
<p>Prices:</p>
<ul>
{% for item in items %}
<li>{{ item.name }} costs {{ item.price }}.</li>
{% endfor %}
</ul>
Django will loop over iterables like lists and let users output template responses for each entry in an interable. If the example above had a list of items
in the context like:
items = [
{'name': 'Pizza', 'price': '$12.99'},
{'name': 'Soda', 'price': '$3.99'},
]
Then the output would look roughly like:
<p>Prices:</p>
<ul>
<li>Pizza costs $12.99.</li>
<li>Soda costs $3.99.</li>
</ul>
Occasionally, you may want to take some specific action on a particular element in the for loop. Python's built in enumerate
function isn't available directly in templates, but a special variable called forloop
is available inside of a for
tag. This forloop
variable has some attributes like first
and last
that you can use to make templates behave differently on certain loop iterations.
Counting:
{% for number in first_three_numbers %}
{{ number }}{% if forloop.last %} is last!{% endif %}
{% endfor %}
This example would produce:
Counting:
1
2
3 is last!
Equipped with variables, if
tags, and for
tags, you should now have the ability to make some fairly powerful templates, but there's more!
More Context On Context
In the setup of the templates settings, we glossed over context processors. Context processors are a valuable way to extend the context that is available to your templates when they are rendered.
Here's the set of context processors that Django's startproject
command brings in by default.
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
Context processors are functions (technically, callables, but let's focus on functions) that receive an HttpRequest
and must return a dictionary. The returned dictionary merges with any other context that will be passed to your template.
We can look at the actual definition of the request
context processor included in that default list.
# django/template/context_processors.py
def request(request):
return {'request': request}
That's it! Because of this context processor, the request
object will be available as a variable to any template in your project. That's super powerful.
Sidebar
Don't be afraid to look at the source code of the projects that you depend on. Remember that regular people wrote your favorite frameworks! You can learn valuable lessons from what they did. The code might be a little intimidating at first, but there is no magic going on!
The "dark side" of context processors is that they run for all requests. If you write a context processor that is slow and does a lot of computation, every request will suffer that performance impact. So use context processors carefully.
Reusable Chunks Of Templates
Now let's talk about one of the powerhouse features of the template system: reusable pieces.
Think about a website. Most pages have a similar look and feel. They do this by repeating a lot of the same HTML, which is Hypertext Markup Language that defines the structure of a page. These pages also use the same CSS, Cascading Style Sheets, which define the styles that shape the look of the page elements.
Imagine you're asked to manage a site and you need to create two separate pages. The home page looks like:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<h1>Hello from the Home page</h1>
</body>
</html>
And here is a page to learn about the company behind the website.
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<h1>Learn about our company</h1>
</body>
</html>
These examples are tiny amounts of HTML, but what if you're asked to change the stylesheet from styles.css
to a new stylesheet made by a designer called better_styles.css
? You would have to update both places. Now think if there were 2,000 pages instead of 2 pages. Making big changes quickly across a site would be virtually impossible!
Django helps you avoid this scenario entirely with a few tags. Let's make a new template called base.html
.
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
{% block main %}{% endblock %}
</body>
</html>
We've created a reusable template with the block
tag! We can fix up our home page to use this new template.
{% extends "base.html" %}
{% block main %}
<h1>Hello from the Home page</h1>
{% endblock %}
This new version of the home page extends the base template. All the template had to do was define its own version of the main
block to fill in the content. We could do the exact same thing with the about page.
If we revisit the task of replacing styles.css
with better_styles.css
, we can make the update in base.html
and have that change apply to any templates that extend it. Even if there were 2,000 pages that all extended from base.html
, changing the stylesheet would still be one line of code to change for an entire site.
That's the power of Django's template extension system.
Another powerful tool for reuse is the include
tag. The include
tag is useful when you want to extract some chunk of template that you want to use in multiple locations. You may want to use include
to:
- Keep templates tidy. You can break a large template up into small pieces that are more manageable.
- Use a template fragment in different parts of your site. Maybe you have a piece of template that should only appear on a few pages.
Coming back to our website example, imagine that base.html
grew to be 20,000 lines long. Navigating to the right part of the template to make changes is now harder. We can decompose the template into smaller pieces.
<!DOCTYPE html>
<html>
{% include "head.html" %}
<body>
{% include "navigation.html" %}
{% block main %}{% endblock %}
</body>
{% include "footer.html" %}
</html>
The include
tag can move those extra pieces around. By providing good name to your templates, if you needed to change the structure of some section like navigation, you could go to the template with the appropriate name. That template file would focus on only the element that you need to change.
block
, extends
, and include
are core tags for keeping your user interface code from sprawling all over the place with lots of duplication.
Next, let's talk about more of Django's built-in template tags that can supercharge your UI.
The Templates Toolbox
The Django documentation includes a large set of built-in tags that you can use in your projects. We aren't going to cover all of them, but I'll focus on a few tags to give you a flavor of what is available.
One of the most used built-in tags aside from what we've already covered is the url
tag. Recall from the article on URLs that you can get the URL to a named view by using the reverse
function. What if you wanted to use the URL in your template? You could do this:
# application/views.py
from django.shortcuts import render
from django.urls import reverse
def the_view(request):
context = {'the_url': reverse('a_named_view')}
return render(request, 'a_template.html', context)
While this works, it's tedious to have to route all URLs through the context. Instead, our template can directly create the proper URL. Here's what a_template.html
might look like instead:
<a href="{% url "a_named_view" %}">Go to a named view</a>
The url
tag is the template equivalent of the reverse
function. Like its reverse
counterpart, url
can accept args or kwargs for routes that expect other variables. url
is an incredibly useful tool and one that you will probably reach for many times as you build your user interface.
Another useful tag is the now
tag. now
is a convenient method to display information about the current time. Using what Django calls format specifiers, you can tell your template how to display the current time. Want to add a current copyright year to your website? No problem!
© {% now "Y" %} Your Company LLC.
One final built-in tag to consider is the spaceless
tag. HTML is partially sensitive to whitespace. There are some frustrating circumstances where this whitspace sensitivity can ruin your day when building a user interface. Can you make a pixel perfect navigation menu for your site with an unordered list? Maybe. Consider this:
<ul class="navigation">
<li><a href="/home/">Home</a></li>
<li><a href="/about/">About</a></li>
</ul>
The indented whitespace on those list items (or the new line characters that follow them) might cause you trouble when working with CSS. Knowing that the whitespace can affect layout, we can use spaceless
like so:
{% spaceless %}
<ul class="navigation">
<li><a href="/home/">Home</a></li>
<li><a href="/about/">About</a></li>
</ul>
{% endspaceless %}
This neat little template tag will remove all the spaces between HTML tags so your output looks like:
<ul class="navigation"><li><a href="/home/">Home</a></li>...</ul>
By removing the extra space, you may get a more consistent experience with your CSS styling and save yourself some frustration. (I had to trim the output to fit better on the screen.)
There is another kind of built-in that we have not looked at yet. These alternative built-in functions are called filters. Filters change the output of variables in your templates. The filter syntax is a bit interesting. It looks like:
Here's a filter example: {{ a_variable|some_filter:"filter arguments" }}
The important element is the pipe character directly after a variable. This character signals to the template system that we want to modify the variable with some kind of transformation. Also observe that filters are used between double curly braces instead of the {%
syntax that we've seen with tags.
A very common filter is the date
filter. When you pass a Python datetime
instance in the context, you can use the date
filter to control the format of the datetime. The date
documentation shows what options you can use to modify the format.
{{ a_datetime|date:"Y-m-d" }}
If a_datetime
was an instance of April Fools' Day, then it could return a string like 2020-04-01
. The date
filter has many specifiers that will enable you to produce most date formatting outputs that you could think of.
default
is a useful filter for when your template value evaluates to False
. This is perfect when you've got a variable with an empty string. The example below outputs "Nothing to see here" if the variable was Falsy.
{{ a_variable|default:"Nothing to see here." }}
length
is a simple filter for lists. {{ a_list_variable|length }}
will produce a number. It is the Django template equivalent to the len
function.
I like the linebreaks
filter a lot. If you create a form (which we'll explore in the next article) and accept a text area field where the user is allowed to provide newlines, then the linebreaks
filter is great if you want to display those newlines later when rendering the user's data. By default, HTML will not show new line characters as intended. The linebreaks
filter will convert \n
to a <br>
HTML tag. Handy!
Before moving on, let's consider two more.
pluralize
is a convenient tag for the times when your text considers counts of things. Consider a count of items.
{{ count_items }} item{{ count_items|pluralize }}
The pluralize
tag will do the right thing if there are zero, one, or more items in the list.
0 items
1 item
2 items
3 items
(and so on)
The final tag in our tour is the yesno
tag. yesno
is good for converting True|False|None
into a meaningful text message. Imagine we're making an application for tracking events and a person's attendance is one of those three values. Our template might look like:
{{ user.name }} has {{ user_accepted|yesno:"accepted,declined,not RSVPed" }}.
Depending on the value of user_accepted
, the template will display something meaningful to a reader.
There are so many built-ins that's it's really hard to narrow down my favorites. Check out the full list to see what might be useful for you.
What if the built-ins don't cover what you need? Have no fear, Django let's you make custom tags and filters for your own purposes. We'll see how next.
Build Your Own Lightsaber In Templates
When you need to build your own template tags or filters, Django gives you the tools to make what you need.
There are three major elements to working with custom tags:
- Defining your tags in a place that Django expects.
- Registering your tags with the template engine.
- Loading your tags in a template so they can be used.
The first step is to put the tags in the correct location. To do that, we need a templatetags
Python package inside of a Django application. We also need a module in that directory. Choose the module name carefully because it is what we will load in the template later on.
application
├── templatetags
│ ├── __init__.py
│ └── custom_tags.py
├── __init__.py
├── ...
├── models.py
└── views.py
Next, we need to make our tag or filter and register it. Let's start with a filter example.
# application/templatetags/custom_tags.py
import random
from django import template
register = template.Library()
@register.filter
def add_pizzazz(value):
pieces_of_flair = [' Amazing!', ' Wowza!', ' Unbelievable!']
return value + random.choice(pieces_of_flair)
Now, if we have a message
variable, we can give it some pizzazz. To use the custom filter, we must load our tags module into the template with the load
tag.
{% load custom_tags %}
{{ message|add_pizzazz }}
If our message was "You got a perfect score!", then our template should show the message and one of the three random choices like "You got a perfect score! Wowza!"
Writing basic custom tags is very similar to custom filters. Code will speak better than words here.
# application/templatetags/custom_tags.py
import random
from django import template
register = template.Library()
@register.simple_tag
def champion_welcome(name, level):
if level > 42:
welcome = f"Hello great champion {name}!"
elif level > 20:
welcome = f"Greetings noble warrior {name}!"
elif level > 5:
welcome = f"Hello {name}."
else:
welcome = "Oh, it's you."
return welcome
We can load the custom tags and use our tag like any other built-in tag.
{% load custom_tags %}
{% champion_welcome "He-Man" 50 %}
This silly welcome tag will respond to multiple input variables and vary depending on the provided level. The example usage should display "Hello great champion He-Man!"
We're only looking at the most common kinds of custom tags in our examples. There are some more advanced custom tagging features which you can explore in the Django custom template tags documentation.
Summary
Now we've seen templates in action! We've looked at:
- How to set up templates for your site
- Ways to call templates from views
- How to use data
- How to handle logic
- Built-in tags and filters available to templates
- Customizing templates with your own code extensions
In the next article, we are going to examine how users can send data to a Django application with HTML forms. Django has tools to make form building quick and effective. We're going to see:
- The
Form
class that Django uses to handle form data in Python - Controling what fields are in forms
- How forms are rendered to users by Django
- How to do form validation
If you'd like to follow along with the series, please feel free to sign up for my newsletter where I announce all of my new content. If you have other questions, you can reach me online on Twitter where I am @mblayman.
This article first appeared on mattlayman.com.
Top comments (2)
The switch to
Pathlib
will throw an error unless youimport os
when defining the template directory. It appears this can just be changed to'DIRS': [BASE_DIR / "templates"],
to work appropriately in thePathlib
parlance.Thanks for pointing that out! Times, they are a changin'!