In this part we’ll be talking about templates in Flask, about rendering HTML, the Jinja templating engine, redirecting, and so on.
You will find the code for this part on Github.
To start with, let’s clean our code a little bit. We don’t need all the routes we created in the previous part anymore. Let’s just leave the basic index route. Our app.py file should look like this:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello World'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
As you can see, the index function returns a simple string. But what if we wanted it to return an HTML file?
Table of Contents
Templates Folder and Template Files
To keep the templates organized, let’s create a folder in the application root and name it templates:
For the application to know where to look for the templates, we have to pass the template_folder argument in the constructor set to the name of that folder:
app = Flask(__name__, template_folder='templates')
Now let’s create a basic index.html file inside the templates folder:
Let’s add some basic structure to it. This is what you get using the Emmet extension in Visual Studio Code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
Let’s edit the title and add an h1
tag inside the body
tag:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Basic Flask Application</title>
</head>
<body>
<h1>Welcome to our basic Flask application</h1>
</body>
</html>
In order to render an HTML template, we need the render_template function from flask, so let’s import it:
from flask import Flask, render_template
Now, to return the template we just created, all we have to do is use this function with the name of the template as an argument:
@app.route('/')
def index():
return render_template('index.html')
If you now run the app, you’ll see the HTML file rendered:
Dynamic HTML Content
We can dynamically change the content of the HTML file. For example, we can define some variables in the index function and then use them in the HTML file. To do that, we can use the power of the Jinja templating engine.
The Jinja Templating Engine
Jinja is a rich templating language. It’s very popular in the Python ecosystem, not just in Flask. We’re not going to dive deep into the details of Jinja because this would definitely require a series of its own, but let’s at least have a look at some basics. So, back to our example, let’s define some variables in the index
function and then use them in the HTML file:
@app.route('/')
def index():
app_name = 'Super App'
age_limit = 13
return render_template('index.html')
Here we added two variables, a string and an integer. Let’s feed them into the HTML file and render them there dynamically. To do that, we just have to add the appropriate keyword arguments to the method:
@app.route('/')
def index():
app_name = 'Super App'
age_limit = 13
return render_template('index.html', app_name=app_name, age_limit=age_limit)
Now we can access the variables in the HTML file using the Jinja templating engine. There are a couple delimiters available to separate Jinja code from HTML.
Jinja Delimiters
To use the variables we created, we use double curly braces:
<body>
<h1>Welcome to <strong>{{ app_name }}</strong> - our basic Flask application</h1>
<p>This app is not suitable for individuals under the age of {{ age_limit }}.</p>
</body>
The {{ }}
delimiter is used for printing content to the output. If we now run the app, we’ll see the values there:
For statements that don’t produce any output we use the {% %}
delimiter and for comments we use the {# #}
delimiter.
Jinja Conditionals
To add conditional code to your templates, you just put it in the delimiters. Let’s add a list numbers to our index
method in the app.py file:
@app.route('/')
def index():
app_name = 'Super App'
age_limit = 13
numbers = [3, 6]
return render_template('index.html', app_name=app_name, age_limit=age_limit, numbers=numbers)
Now, in the template, let’s display different content depending on the length of the list:
<body>
<h1>Welcome to <strong>{{ app_name }}</strong> - our basic Flask application</h1>
<p>This app is not suitable for individuals under the age of {{ age_limit }}.</p>
{% if numbers|length == 1 %}
<p>There's just one number in the list, {{ numbers[0] }}.</p>
{% elif numbers|length < 5 %}
<p>There are just {{ numbers|length }} numbers in the list.</p>
{% else %}
<p>There are lots of numbers in the list.</p>
{% endif %}
</body>
As you can see, we enclose the conditional code between the {% if %} and {% endif %} blocks. Besides, we use a length filter (after a pipe character, |) to read the length of the list. At this moment there are two elements in the list, so the {% elif %} block should take care of displaying the content:
Let’s remove the second element and leave just the number 3 in the list:
@app.route('/')
def index():
app_name = 'Super App'
age_limit = 13
numbers = [3]
return render_template('index.html', app_name=app_name, age_limit=age_limit, numbers=numbers)
Now only the {% if %}
block will be executed:
Finally, let’s add more elements to the list so that there are at least five:
@app.route('/')
def index():
app_name = 'Super App'
age_limit = 13
numbers = [3, 5, 7, 2, 9]
return render_template('index.html', app_name=app_name, age_limit=age_limit, numbers=numbers)
Now only the {% else %}
block will be executed:
Jinja Loops
In a similar way, we can loop through the list to display is elements in a list. We can use the {% for %}
and {% endfor %}
loops to do that.
Let’s implement it in the {% else %}
block so that not only do we get the very vague information that there are lots of elements, but we also see them all listed:
<body>
...
{% else %}
<p>There are lots of numbers in the list.</p>
<p>Here they are:</p>
<ol>
{% for number in numbers %}
<li>
number {{ number }}
</li>
{% endfor %}
</ol>
{% endif %}
</body>
This is how it’s rendered:
Template Inheritance
Templates can be inherited. We may want to repeat some of the stuff on each page in our application. We could achieve that by duplicating the code, but duplicating the code is not a good solution. A better way to approach this problem is to use the so-called base templates. These are templates other templates inherit from.
Let’s add a base.html file to the templates folder. In this file we can define the structure that we want to be shared by all the pages, let’s say something like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Default Title{% endblock %}</title>
</head>
<body>
<h1>Hey, welcome to our website.</h1>
{% block content %}{% endblock %}
</body>
</html>
Here the title is dynamic. It’s set depending on the actual page that inherits from the base template. To use dynamic values, we specify blocks with names that we can then refer to. Here, the section for the title is named title. We also need an end block. Then, in a page that inherits from the base template, we will use this name to fill in the section accordingly. Between the start and end block we can put the default value for the title that will be displayed if the title is not set.
In a similar way, we created another block in the body and named it content. This is the part that will be different for each inheriting page.
We also added an h1
header. As it’s outside any block, it will be displayed on each page.
So, with our base template in place, we can inherit from it. In other words, we can extend it in other pages. Let’s do it in the index page.
First, we have to add the code to extend the base template at the top of the index.html file and remove everything that is not going to be part of the content, so the structure of the HTML file.
Next, we have to set the blocks that we defined in the base template to fill them in, so the title block and the content block. Let’s set the former to Home and the latter to the actual content of the index page. The index.html file should now look like this:
{% extends 'base.html' %}
{% block title %}Home{% endblock %}
{% block content %}
<h1>Welcome to <strong>{{ app_name }}</strong> - our basic Flask application</h1>
<p>This app is not suitable for individuals under the age of {{ age_limit }}.</p>
{% if numbers|length == 1 %}
<p>There's just one number in the list, {{ numbers[0] }}.</p>
{% elif numbers|length < 5 %}
<p>There are just {{ numbers|length }} numbers in the list.</p>
{% else %}
<p>There are lots of numbers in the list.</p>
<p>Here they are:</p>
<ol>
{% for number in numbers %}
<li>
number {{ number }}
</li>
{% endfor %}
</ol>
{% endif %}
{% endblock %}
If we refresh the page in the browser, we’ll see the title (A), the header that will be displayed on each page because it’s part of the base template (B) and the actual content (C):
Let’s create another page that extends the base template. In the template folder, add the contact.html file and fill it in like so:
{% extends 'base.html' %}
{% block title %}Contact Us{% endblock %}
{% block content %}
<h1>Contact us</h1>
<p>email: {{ email }}</p>
<p>phone: {{ phone }}</p>
{% endblock %}
Now, in the app.py file, we need to add a route for this page:
@app.route('/contact')
def contact():
email = 'super.app@gmail.com'
phone = '111 2222 3333'
return render_template('contact.html', email=email, phone=phone)
If we now navigate to /contact
, we’ll see this:
As you can see, the header ‘Hey, welcome to our website.’ is still there, although it was not defined in the contact page. It’s inherited from the base template.
Filters
Another useful concept in the Jinja templating engine is filters. We already used a filter to determine the length of the list. But we can use filters to perform all kinds of operations.
To demonstrate how filters work, let’s create a new route and a new page that inherits from the base template. Here’s the route in the app.py file:
@app.route('/fun_stuff')
def fun_stuff():
word = 'hello'
return render_template('fun_stuff.html', word=word)
And now let’s add another page to the templates folder and name it fun_stuff.html. Here’s the code:
{% extends 'base.html' %}
{% block title %}Fun Stuff{% endblock %}
{% block content %}
<h1>Have some fun with the word {{ word }}...</h1>
{% endblock %}
If you navigate to the fun_stuff page, you’ll see the following:
This isn’t much fun, so let’s play with the word a bit. In Jinja we cannot use regular Python functions like len to determine the length of a sequence or upper to convert the text to uppercase. Instead, we use filters, which are represented by the pipe character, which is the vertical line (|) that you can find on the keyboard above the Enter key, at least on my laptop.
So, let’s use the length filter that we used before, and also the upper filter to convert the text to uppercase. Well, if we really want to have fun, let’s add some more filters. Their names are usually self-explanatory and if you have any doubts about how they might work, you can look up the Jinja filters documentation. So, here’s the code:
{% extends 'base.html' %}
{% block title %}Fun Stuff{% endblock %}
{% block content %}
<h1>Have some fun with the word {{ word }}...</h1>
<p>word length: {{ word|length }}</p>
<p>the uppercase version: {{ word|upper }}</p>
<p>some characters replaced: {{ word|replace('lo', 'p') }}</p>
<p>first letter: {{ word|first }}</p>
<p>list of characters: {{ word|list }}</p>
<p>in reverse: {{ word|reverse }}</p>
{% endblock %}
Here’s what it looks like in the browser:
You can also define your own filters. Let’s define a filter that will return every other letter in the word. We can do it in the app.py file by defining a method with the @app.template_filter
decorator:
@app.template_filter('every_other_letter')
def every_other_letter(word):
return word[::2]
Now, we’re ready to apply this filter:
{% block content %}
...
<p>every other character: {{ word|every_other_letter }}</p>
{% endblock %}
Here’s what it looks like:
That’s basically all we need to know about filters. Let’s now move on to the next feature, which may be very useful, especially if your URL may change over time. The feature I’m talking about is dynamic URL.
Dynamic URLs
Let’s say we want to have a link from the index page to the fun_stuff page to easily get some fun every now and then. There is a route to that page in the app.py file:
@app.route('/fun_stuff')
def fun_stuff():
word = 'hello'
return render_template('fun_stuff.html', word=word)
Here the name of the endpoint is fixed, /fun_stuff
. If this fixed endpoint name is fine, we can just add a regular link to it in the index page:
{% block content %}
...
<a href='/fun_stuff'>Take a break, have some fun!</a>
{% endblock %}
This will render like so:
If you click the link, you’ll navigate to the fun_stuff page.
But let’s create a dynamic link instead. This time we have to use the url_for
function and pass to it the name of the function we want to be called when this link is clicked. At this point the name of the function is identical as the route itself (fun_stuff
), but it doesn’t have to be the case. Anyway, here’s how we implement the dynamic URL in the link:
{% block content %}
...
<a href="{{ url_for('fun_stuff') }}">Have fun using this dynamic link!</a>
{% endblock %}
The link looks and works the same. It will even work if we change the route, for example like this:
@app.route('/real_fun')
def fun_stuff():
word = 'hello'
return render_template('fun_stuff.html', word=word)
Here the route is now /real_fun
. Now the problem is that we can navigate directly to this page by typing in the new route in the browser. But what about the links from the index page? Let’s click them one by one. Let’s click on the link on the left first – it’s the one with the fixed URL:
This link doesn’t work anymore:
But the link on the right, so the one with the dynamic URL, still works:
Let’s change the route back to /fun_stuff
so that both links work correctly.
Our last topic in this part of the series is redirecting.
Redirecting
Sometimes we don’t want to render any page in the browser, for example when a link is clicked. Instead, we may want to redirect the user to another specific page. To do that, we need to import the redirect function in the app.py file and use it in a route. We’ll create a new route to demonstrate it. So, here’s the imports section:
from flask import Flask, render_template, redirect
And here’s the new route:
@app.route('/go_home')
def go_home():
return redirect('/')
This will redirect us to the index page. Let’s add the following link in the fun_stuff page:
{% block content %}
...
<a href='/go_home'>Enough fun! Go home.</a>
{% endblock %}
You’ll see the link if you navigate to the fun_stuff page now:
And if you click it, you’ll be redirected to the index page again.
One thing to note… You can use dynamic URLs in the route as well. To do that, you have to import the url_for
function:
from flask import Flask, render_template, redirect, url_for
and use it in the route:
@app.route('/go_home')
def go_home():
return redirect(url_for('index'))
It will still work the same.