1. Code
  2. Python

Templating With Jinja2 in Flask: Essentials

Scroll to top
7 min read
20
This post is part of a series called Templating With Jinja2 in Flask .
Templating With Jinja2 in Flask: Advanced

Jinja2 is a template engine written in pure Python. It provides a Django-inspired non-XML syntax but supports inline expressions and an optional sandboxed environment. It is small but fast, apart from being an easy-to-use standalone template engine. Flask is a Python-based micro web framework which allows you to write your web applications quickly and efficiently.

In this three-part series, I will start with the basics of Jinja2 templating from the perspective of Flask. In the subsequent parts of this series, I will cover advanced templating topics while learning how to lay out templates in a Flask-based application in a modular and extensible design.

I assume that you have a basic understanding of Flask and environment setup best practices using virtualenv to be followed while developing a Python application.

Installing Packages

Flask comes packaged with Jinja2, and hence we just need to install Flask. For this series, I recommend using the development version of Flask, which includes much more stable command-line support, among many other features and improvements to Flask in general.

1
pip install https://github.com/mitsuhiko/flask/tarball/master

Need for a Templating Engine?

In Flask, we can write a complete web application without the need for any third-party templating engine. Let's have a look at a small Hello World app below:

1
from flask import Flask
2
3
app = Flask(__name__)
4
5
@app.route('/')
6
@app.route('/hello')
7
@app.route('/hello/<user>')
8
def hello_world(user=None):
9
    user = user or 'Shalabh'
10
    return '''

11
    <html>

12
        <head>

13
            <title>Templating in Flask</title>

14
        </head>

15
        <body>

16
            <h1>Hello %s!</h1>

17
            <p>Welcome to the world of Flask!</p>

18
        </body>

19
    </html>''' % user
20
       
21
if __name__ == '__main__':
22
    app.run()

It is obvious that the above pattern of writing an application would not be feasible in a real web application where the HTML, CSS, and JS can reach thousands of lines of code. Here, templating saves us because we can structure our view code by keeping our templates separate. Flask provides support for Jinja2 by default, but any other templating engine can also be used as suited.

Laying Out Templates

By default, Flask expects the templates to be placed in a folder named templates at the application root level. Flask then automatically reads the contents by making this folder available for use with the render_template() method. I will demonstrate the same by restructuring the trivial Hello World application shown above.

The application structure would be as shown below.

1
flask_app/
2
    my_app.py    
3
    templates/
4
        - index.html

The Application Itself

flask_app/my_app.py

1
from flask import Flask, render_template, request
2
3
app = Flask(__name__)
4
5
@app.route('/')
6
@app.route('/hello')
7
@app.route('/hello/<user>')
8
def hello_world(user=None):
9
    user = user or 'Shalabh'
10
    return render_template('index.html', user=user)

flask_app/templates/index.html

1
<html>
2
  <head>
3
    <title>Templating in Flask</title>
4
  </head>
5
  <body>
6
    <h1>Hello {{ user }}!</h1>
7
    <p>Welcome to the world of Flask!</p>
8
  </body>
9
</html>

To run the application, just execute the following command on the command line:

1
flask --app=my_app run

Open up http://127.0.0.1:5000/ in a browser to see the application in action. The result would be the same in the case of http://127.0.0.1:5000/hello as well.

The default landing page

Try opening the URL with your name as the last part of it. So if your name is John, the URL would be http://127.0.0.1:5000/hello/John. Now the page would look like this:

Accessing the landing page with a custom name

It is pretty straightforward that in the method hello_world, the last part of the URL after hello is fetched from the request and passed to the context of the template being rendered using render_template(). This value is then parsed from the template context using the Jinja2 placeholder {{ user }}. This placeholder evaluates all the expressions that are placed inside it, depending on the template context.

Understanding Blocks and Inheritance in Templates

Usually, any web application will have a number of web pages that will be different from each other. Code blocks such as headers and footers will be the same in almost all the pages throughout the site. Likewise, the menu also remains the same. In fact, usually, just the center container block changes, and the rest usually remains the same. For this, Jinja2 provides a great way of inheritance among templates. It's a good practice to have a base template where we can structure the basic layout of the site along with the header and footer.

I will create a small application to showcase a list of products under different categories. For styling, I will use the Bootstrap framework to give a basic design to the templates. The application structure is now as shown below.

1
flask_app/
2
    my_app.py    
3
    templates/
4
        - base.html
5
        - home.html
6
        - product.html
7
    static/
8
        css/
9
            - main.css

The rest of the application code is demonstrated below.

flask_app/my_app.py

1
from flask import Flask, render_template, abort
2
3
app = Flask(__name__)
4
5
PRODUCTS = {
6
    'iphone': {
7
        'name': 'iPhone 5S',
8
        'category': 'Phones',
9
        'price': 699,
10
    },
11
    'galaxy': {
12
        'name': 'Samsung Galaxy 5',
13
        'category': 'Phones',
14
        'price': 649,
15
    },
16
    'ipad-air': {
17
        'name': 'iPad Air',
18
        'category': 'Tablets',
19
        'price': 649,
20
    },
21
    'ipad-mini': {
22
        'name': 'iPad Mini',
23
        'category': 'Tablets',
24
        'price': 549
25
    }
26
}
27
28
@app.route('/')
29
@app.route('/home')
30
def home():
31
    return render_template('home.html', products=PRODUCTS)
32
33
@app.route('/product/<key>')
34
def product(key):
35
    product = PRODUCTS.get(key)
36
    if not product:
37
        abort(404)
38
    return render_template('product.html', product=product)

In this file, I have hardcoded the product list to make the application simpler and focus only on the templating part. I have created two endpoints, home and product, where the former serves the purpose of listing all the products and the latter opens up the individual page.

flask_app/static/css/main.css

1
body {
2
  padding-top: 50px;
3
}
4
.top-pad {
5
  padding: 40px 15px;
6
  text-align: center;
7
}

This file holds a bit of custom CSS that I added to make the templates more legible. Let's look at the templates now.

flask_app/templates/base.html

1
<!DOCTYPE html>
2
<html lang="en">
3
  <head>
4
    <meta charset="utf-8">
5
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
    <meta name="viewport" content="width=device-width, initial-scale=1">
7
    <title>Jinja2 Tutorial - Tutsplus</title>
8
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
9
    <link href="{{ url_for('static', filename='css/main.css') }}" rel="stylesheet">
10
  </head>
11
  <body>
12
    <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
13
      <div class="container">
14
        <div class="navbar-header">
15
          <a class="navbar-brand" href="{{ url_for('home')}}">Tutsplus - Jinja2 Tutorial</a>
16
        </div>
17
      </div>
18
    </div>
19
    <div class="container">
20
      {% block container %}{% endblock %}
21
    </div>
22
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
23
    
24
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
25
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
26
27
  </body>
28
</html>

Note the use of url_for() for creating URLs for static files and other links. It is a very handy tool which is provided by Flask. Read more about this in the documentation. Another important point to note here is the usage of {% block container %}{% endblock %}, which is a crucial component of Jinja2 working towards making the templates modular and inheritable. The next couple of files will make this clearer.

Control Flow

Python uses if, elif, else, and for loop statements to control program flow based on whether a condition is True or False. You can also incorporate these conditions in your Flask application to loop over the dictionary of products and display them on the home page. These conditional statements are put inside {%  %} blocks. Update the home.html page as follows.

flask_app/templates/home.html

1
{% extends 'base.html' %}
2
 
3
{% block container %}
4
  <div class="top-pad">
5
    {% for id, product in products.items() %}
6
      <div class="well">
7
        <h2>
8
          <a href="{{ url_for('product', key=id) }}">{{product['name']}}</a>
9
          <small>$ {{ product['price']}}</small>
10
        </h2>
11
      </div>
12
    {% endfor %}
13
  </div>
14
{% endblock %}

See how this template extends base.html and provides the contents of {% block container %}. {% for %} behaves just like a normal for loop in any language which we are using here to create a list of products.

flask_app/templates/product.html

1
{% extends 'home.html' %}
2
3
{% block container %}
4
  <div class="top-pad">
5
    <h1>{{ product['name'] }}
6
      <small>{{ product['category'] }}</small>
7
    </h1>
8
    <h3>$ {{ product['price'] }}</h3>
9
  </div>
10
{% endblock %}

The above template implements the individual product page.

Now run the app by executing the following command.

1
flask --app=my_app run

The running app would look like the one shown in the screenshot below. Just open http://127.0.0.1:5000/home in the browser.

The application running with the Bootstrap framework

Click on any of the products to see the individual product page.

An individual product page

Filters

Filters can be applied to variables just as you would use Python methods. A pipe character separates filters in the Jinja templating language. For example, suppose you needed to capitalize the product's name on the home page; you would apply the upper filter as follows.

1
{% extends 'base.html' %}
2
 
3
{% block container %}
4
  <div class="top-pad">
5
    {% for id, product in products.items() %}
6
      <div class="well">
7
        <h2>
8
          <a href="{{ url_for('product', key=id) }}">{{product['name']| upper }}</a>
9
          <small>$ {{ product['price']}}</small>
10
        </h2>
11
      </div>
12
    {% endfor %}
13
  </div>
14
{% endblock %}

Now, if you run the server, you should notice that the Product name is in ALLCAPS.

Conclusion

In this tutorial, we have seen how to lay out the template structure in a Flask-based application using Jinja2. We also saw how blocks can be used to leverage the inheritance in templates.

In the next part of this series, we will see how to write a custom filter, a custom context processor, and a macro.

This post has been updated with contributions from Esther Vaati. Esther is a software developer and writer for Envato Tuts+.

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.