An important part of Flask is its templating language - Jinja. It allows us to do some magical stuff like writing the 'boilerplate' code once, and then just insert it everywhere it's needed with one simple line of code. This is a huge improvement over static HTML, where you need the code for the entire page, on every single page, even though the only thing that probably changes is the content.
Jinja's other super-power is that it also lets us write Python-like code in our pages to iterate through collections and make decisions about what to display, how to display it, and when - more on that later.
Firstly though, we'll use it to separate the stuff that probably doesn't change much from the stuff that probably does.
<!doctype html>
<html>
<head>
<title>My Site</title>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
{% include 'header.html' %}
{% include 'nav.html' %}
{% block content %}{% endblock %}
{% include 'footer.html' %}
</body>
</html>
This is the only place and time you need to write this, regardless of how many pages you have. Each page in the site from now on will use layout.html. And it means you just have one file to change if you want to update the overall page design.
For this to work, we will also need the 4 items in the red section: header.html, nav.html and footer.html (which will likely not change all that often, but are separated here to show how it can be done) and content, which we'll get to shortly.
<header>
<h1>My Site!</h1>
</header>
<nav>
<a href="/">HOME</a> | <a href="/about">ABOUT</a>
</nav>
<footer>
<h2>© 2021 SuperMegaMrDCorp</h2>
</footer>
Note: Each of these is just a small part of the code that make up the overall page. Those three snippets could just be included in layout.html, rather than including them from these separate files, if you prefer.
The one last piece of the puzzle is the actual content of a page. This is different for each route (routes are discussed more fully on the Flask basics page - but are controlled by your Python code).
For example, the home route ('/') in your Flask app might typically look like this:
@app.route('/')
def home():
return render_template('home.html')
There isn't much to that route - it simply detects when someone is going to the root (home page) of the website, and renders a page based on the content of home.html:
{% extends 'layout.html' %}
{% block content %}
<h2>Welcome to my site!</h2>
<p>This is my site and its pretty amazeballs!</p>
{% endblock %}
When loaded by render_template(), Jinja will first get (and subsequently build) layout.html - because the first line says that what follows is just an extension of layout.html
layout.html itself has several includes in it - so it will first replace each with the relevant code from the relevant file.
for example, the line:
{% include 'header.html' %}
is replaced by the contents of header.html:
<header>
<h1>My Site!</h1>
</header>
Once the includes are complete, the content block is replaced with the one from home.html - So:
{% block content %}{% endblock %}
becomes
<h2>Welcome to my site!</h2>
<p>This is my site and its pretty amazeballs!</p>
And that's it, job done - render_template() has assembled a page based on all the various bits of HTML that it needs, and Flask sends it off to the browser.
The final rendered page code should look something like this, although, in reality, the indenting will probably be all over the place as render_template() does not do any human-readable tidying.
<!doctype html>
<html>
<head>
<title>My Site</title>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<header>
<h1>My Site!</h1>
</header>
<nav>
<a href="/">HOME</a> | <a href="/about">ABOUT</a>
</nav>
<main>
<h2>Welcome to my site.</h2>
<p>This is my site and its pretty amazeballs!</p>
</main>
<footer>
<h2>© 2021 SuperMegaMrDCorp</h2>
</footer>
</body>
</html>
Once this basic functionality is in place, you can start to do some more interesting things. Maybe we want to make a comma-separated list of items on the screen from a list of items? Say the Flask route is /fruit:
@app.route('/fruit')
def fruit():
fruits = ['apple', 'pear', 'durian']
return render_template('fruit.html', fruits=fruits)
And in your templates:
{% extends 'layout.html' %}
{% block content %}
<h2>Fruit List</h2>
{% if fruits|length > 0 %} {# no point showing them if there are none #}
{% for fruit in fruits %}
{{ fruit }}{% if loop.last %}.{% else %}, {% endif %}
{% endfor %}
{% endif %}
{% endblock %}
The output of this code should be:
apple, pear, durian.
There is a bit going on here, so let's unpack it and see what it does.
{% if fruits|length > 0 %}
The length filter (in Jinja - the '|' character is used to implement 'filters') checks to see if the list 'fruits' contains anything - there is no point bothering to print the list if its empty. Notice the matching {% endif %} on the second to last line - just like HTML where we have to close any open tags, in Jinja you have to close the code blocks you open.
{% for fruit in fruits %}
This is just like Python - we're starting a loop to go through the list of 'fruits' and putting each one, in turn, into a variable called 'fruit'. Note: just like if statements, for loops need an {% endfor %}.
{{ fruit }}
In Jinja, double-squiggly-brackets means display the content of the variable 'fruit'. Any time you see {{ variable }}, Jinja will take the content of the variable (if it exists) and display it. Generally, if the variable doesn't exist or is empty it will just display nothing and throw no error.
One useful extension here could be to use Jinja filters on the output - for example {{ fruit|capitalize }} would output Apple, Pear, Durian. Other filters include |upper and |lower.
{% if loop.last %}
This is a condition that will be true only if we're on the last run through of the loop - quite handy when making a plain-English readable list of things - as you can see here it puts a comma after each fruit to separate the list until the last time through when it uses a full stop instead. Note: again, it's a statement so it has an {% endif %}.
Loops in Jinja can also give you the current loop number:
{% for fruit in fruits %}
{{ loop.index }} - {{ fruit }}<br />
{% endfor %}
which would output:
1 - apple
2 - pear
3 - durian
You might have noticed that this index counter starts at 1, which is unusual for a programming language - but useful when making lists of things for humans. You can also use loop.index0 to begin from 0. Of course, this output could actually be achieved using an HTML ordered list (<ol>) - but it's just an example of how you might use it.
Filters are also very useful - we saw above how you could use |capitalize, |upper and |lower - these simply transform the existing data - you might also have noticed |length being used which actually returns the length of the list. There is a comprehensive list of all the available filters in Jinja here: https://jinja.palletsprojects.com/en/3.0.x/templates/#builtin-filters
Note: The code used on this page is available at https://replit.com/@Mr_D/Fruit-List-Jinja-Demo