On this episode, we look at templates, the primary tool that Django provides to build user interfaces in your Django app.
Listen at Spotify.
Last Episode
On the previous episode, we talked about views and how views handle requests to create HTTP responses for users.
Set Up
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.
# 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',
],
},
},
]
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")],
...
]
Using 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.
# application/views.py
from django.shortcuts import render
def a_template_view(request):
context = {'first_name': 'Johnny'}
return render(request, 'hello.html', context)
<h1>Hello {{ first_name }}</h1>
There are some interesting things to note about this example.
- The template can be any plain text file type.
- In the process of rendering, Django took the context data dictionary and used its keys as variable names in the template.
This idea of mixing context and static layout is the core concept of working with templates.
Reconsider TemplateView
from the last episode.
# application/views.py
from django.views.generic.base import TemplateView
class HelloView(TemplateView):
template_name = 'hello.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['first_name'] = 'Johnny'
return context
Template Core Elements
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.
context = {
'address': {
'street': '123 Main St.',
'city': 'Beverly Hills',
'state': 'CA',
'zip_code': '90210',
}
}
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
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.
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.
{% 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.
<p>Prices:</p>
<ul>
{% for item in items %}
<li>{{ item.name }} costs {{ item.price }}.</li>
{% endfor %}
</ul>
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>
The 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 %}
Context Processors
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',
],
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.
Reusable Templates
Now let’s talk about one of the powerhouse features of the template system: reusable pieces.
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<h1>Hello from the Home page</h1>
</body>
</html>
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>
Imagine 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>
{% extends "base.html" %}
{% block main %}
<h1>Hello from the Home page</h1>
{% endblock %}
That’s the power of Django’s template extension system.
Another powerful tool for reuse is the include
tag.
What happens when a template file is super long?
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.
<!DOCTYPE html>
<html>
{% include "head.html" %}
<body>
{% include "navigation.html" %}
{% block main %}{% endblock %}
</body>
{% include "footer.html" %}
</html>
block
, extends
, and include
are core tags
for keeping your user interface code
from sprawling all over the place
with lots of duplication.
Built-in Features
The Django documentation includes a large set of built-in tags that you can use in your projects.
One of the most used built-in tags
aside from what we’ve already covered
is the url
tag.
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)
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.
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.
Here's a filter example: {{ a_variable|some_filter:"filter arguments" }}
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
.
{{ a_variable|default:"Nothing to see here." }}
If you create a form
(which we’ll explore in the next episode)
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!
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)
yesno
is good for converting True|False|None
into a meaningful text message.
{{ 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.
Custom Tags
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
# 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 }}
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 %}
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
Next Time
In the next episode, our focus will turn to forms. Forms are the primary tool that web pages use to accept data from users. We’ll see how Django helps us make forms quickly so our sites can interact with users.
You can follow the show on Spotify. Or follow me or the show on X at @mblayman or @djangoriffs.
Please rate or review on iTunes, Spotify, or from wherever you listen to podcasts. Your rating will help others discover the podcast, and I would be very grateful.
Django Riffs is supported by listeners like you. If you can contribute financially to cover hosting and production costs, please check out my Patreon page to see how you can help out.