2017-12-12T16:29:38Z

The Flask Mega-Tutorial Part II: Templates

In this second installment of the Flask Mega-Tutorial series, I'm going to discuss how to work with templates.

For your reference, below is a list of the articles in this series.

Note 1: If you are looking for the legacy version of this tutorial, it's here.

Note 2: If you would like to support my work on this blog, or just don't have patience to wait for weekly articles, I am offering the complete version of this tutorial packaged as an ebook or a set of videos. For more information, visit courses.miguelgrinberg.com.

After you complete Chapter 1, you should have a fully working, yet simple web application that has the following file structure:

microblog\
  venv\
  app\
    __init__.py
    routes.py
  microblog.py

To run the application you set the FLASK_APP=microblog.py in your terminal session, and then execute flask run. This starts a web server with the application, which you can open by typing the http://localhost:5000/ URL in your web browser's address bar.

In this chapter you will continue working on the same application, and in particular, you are going to learn how to generate more elaborate web pages that have a complex structure and many dynamic components. If anything about the application or the development workflow so far isn't clear, please review Chapter 1 again before continuing.

The GitHub links for this chapter are: Browse, Zip, Diff.

What Are Templates?

I want the home page of my microblogging application to have a heading that welcomes the user. For the moment, I'm going to ignore the fact that the application does not have the concept of users yet, as this is going to come later. Instead, I'm going to use a mock user, which I'm going to implement as a Python dictionary, as follows:

user = {'username': 'Miguel'}

Creating mock objects is a useful technique that allows you to concentrate on one part of the application without having to worry about other parts of the system that don't exist yet. I want to design the home page of my application, and I don't want the fact that I don't have a user system in place to distract me, so I just make up a user object so that I can keep going.

The view function in the application returns a simple string. What I want to do now is expand that returned string into a complete HTML page, maybe something like this:

app/routes.py: Return complete HTML page from view function

from app import app

@app.route('/')
@app.route('/index')
def index():
    user = {'username': 'Miguel'}
    return '''
<html>
    <head>
        <title>Home Page - Microblog</title>
    </head>
    <body>
        <h1>Hello, ''' + user['username'] + '''!</h1>
    </body>
</html>'''

If you are not familiar with HTML, I recommend that you read HTML Markup on Wikipedia for a brief introduction.

Update the view function as shown above and give the application a try to see how it looks in your browser.

Mock User

I hope you agree with me that the solution used above to deliver HTML to the browser is not good. Consider how complex the code in this view function will become when I have the blog posts from users, which are going to constantly change. The application is also going to have more view functions that are going to be associated with other URLs, so imagine if one day I decide to change the layout of this application, and have to update the HTML in every view function. This is clearly not an option that will scale as the application grows.

If you could keep the logic of your application separate from the layout or presentation of your web pages, then things would be much better organized, don't you think? You could even hire a web designer to create a killer web site while you code the application logic in Python.

Templates help achieve this separation between presentation and business logic. In Flask, templates are written as separate files, stored in a templates folder that is inside the application package. So after making sure that you are in the microblog directory, create the directory where templates will be stored:

(venv) $ mkdir app/templates

Below you can see your first template, which is similar in functionality to the HTML page returned by the index() view function above. Write this file in app/templates/index.html:

app/templates/index.html: Main page template

<html>
    <head>
        <title>{{ title }} - Microblog</title>
    </head>
    <body>
        <h1>Hello, {{ user.username }}!</h1>
    </body>
</html>

This is a mostly standard, very simply HTML page. The only interesting thing in this page is that there are a couple of placeholders for the dynamic content, enclosed in {{ ... }} sections. These placeholders represent the parts of the page that are variable and will only be known at runtime.

Now that the presentation of the page was offloaded to the HTML template, the view function can be simplified:

app/routes.py: Use render\_template() function

from flask import render_template
from app import app

@app.route('/')
@app.route('/index')
def index():
    user = {'username': 'Miguel'}
    return render_template('index.html', title='Home', user=user)

This looks much better, right? Try this new version of the application to see how the template works. Once you have the page loaded in your browser, you may want to view the source HTML and compare it against the original template.

The operation that converts a template into a complete HTML page is called rendering. To render the template I had to import a function that comes with the Flask framework called render_template(). This function takes a template filename and a variable list of template arguments and returns the same template, but with all the placeholders in it replaced with actual values.

The render_template() function invokes the Jinja2 template engine that comes bundled with the Flask framework. Jinja2 substitutes {{ ... }} blocks with the corresponding values, given by the arguments provided in the render_template() call.

Conditional Statements

You have seen how Jinja2 replaces placeholders with actual values during rendering, but this is just one of many powerful operations Jinja2 supports in template files. For example, templates also support control statements, given inside {% ... %} blocks. The next version of the index.html template adds a conditional statement:

app/templates/index.html: Conditional statement in template

<html>
    <head>
        {% if title %}
        <title>{{ title }} - Microblog</title>
        {% else %}
        <title>Welcome to Microblog!</title>
        {% endif %}
    </head>
    <body>
        <h1>Hello, {{ user.username }}!</h1>
    </body>
</html>

Now the template is a bit smarter. If the view function forgets to pass a value for the title placeholder variable, then instead of showing an empty title the template will provide a default one. You can try how this conditional works by removing the title argument in the render_template() call of the view function.

Loops

The logged in user will probably want to see recent posts from connected users in the home page, so what I'm going to do now is extend the application to support that.

Once again, I'm going to rely on the handy fake object trick to create some users and some posts to show:

app/routes.py: Fake posts in view function

from flask import render_template
from app import app

@app.route('/')
@app.route('/index')
def index():
    user = {'username': 'Miguel'}
    posts = [
        {
            'author': {'username': 'John'},
            'body': 'Beautiful day in Portland!'
        },
        {
            'author': {'username': 'Susan'},
            'body': 'The Avengers movie was so cool!'
        }
    ]
    return render_template('index.html', title='Home', user=user, posts=posts)

To represent user posts I'm using a list, where each element is a dictionary that has author and body fields. When I get to implement users and blog posts for real I'm going to try to preserve these field names as much as possible, so that all the work I'm doing to design and test the home page template using these fake objects will continue to be valid when I introduce real users and posts.

On the template side I have to solve a new problem. The list of posts can have any number of elements, it is up to the view function to decide how many posts are going to be presented in the page. The template cannot make any assumptions about how many posts there are, so it needs to be prepared to render as many posts as the view sends in a generic way.

For this type of problem, Jinja2 offers a for control structure:

app/templates/index.html: for-loop in template

<html>
    <head>
        {% if title %}
        <title>{{ title }} - Microblog</title>
        {% else %}
        <title>Welcome to Microblog</title>
        {% endif %}
    </head>
    <body>
        <h1>Hi, {{ user.username }}!</h1>
        {% for post in posts %}
        <div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
        {% endfor %}
    </body>
</html>

Simple, right? Give this new version of the application a try, and be sure to play with adding more content to the posts list to see how the template adapts and always renders all the posts the view function sends.

Mock Posts

Template Inheritance

Most web applications these days have a navigation bar at the top of the page with a few frequently used links, such as a link to edit your profile, to login, logout, etc. I can easily add a navigation bar to the index.html template with some more HTML, but as the application grows I will be needing this same navigation bar in other pages. I don't really want to have to maintain several copies of the navigation bar in many HTML templates, it is a good practice to not repeat yourself if that is possible.

Jinja2 has a template inheritance feature that specifically addresses this problem. In essence, what you can do is move the parts of the page layout that are common to all templates to a base template, from which all other templates are derived.

So what I'm going to do now is define a base template called base.html that includes a simple navigation bar and also the title logic I implemented earlier. You need to write the following template in file app/templates/base.html:

app/templates/base.html: Base template with navigation bar

<html>
    <head>
      {% if title %}
      <title>{{ title }} - Microblog</title>
      {% else %}
      <title>Welcome to Microblog</title>
      {% endif %}
    </head>
    <body>
        <div>Microblog: <a href="/index">Home</a></div>
        <hr>
        {% block content %}{% endblock %}
    </body>
</html>

In this template I used the block control statement to define the place where the derived templates can insert themselves. Blocks are given a unique name, which derived templates can reference when they provide their content.

With the base template in place, I can now simplify index.html by making it inherit from base.html:

app/templates/index.html: Inherit from base template

{% extends "base.html" %}

{% block content %}
    <h1>Hi, {{ user.username }}!</h1>
    {% for post in posts %}
    <div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
    {% endfor %}
{% endblock %}

Since the base.html template will now take care of the general page structure, I have removed all those elements from index.html and left only the content part. The extends statement establishes the inheritance link between the two templates, so that Jinja2 knows that when it is asked to render index.html it needs to embed it inside base.html. The two templates have matching block statements with name content, and this is how Jinja2 knows how to combine the two templates into one. Now if I need to create additional pages for the application, I can create them as derived templates from the same base.html template, and that is how I can have all the pages of the application sharing the same look and feel without duplication.

Template Inheritance

106 comments

  • #76 matt said 2019-10-31T20:15:18Z

    Hi Miguel, Great tutorial, thank you.

  • #77 Kevin Chemutai said 2019-11-15T10:02:09Z

    Well summarized. Makes it soo easy to follow along if u have ever been in another tutorial which was not so elaborate. Looking forward to what the future holds in the course

  • #78 나는야쓰레기 said 2019-12-30T09:58:58Z

    Really thank you!! Your tutorial saved my day THX!!!

  • #79 Pinkie said 2020-01-19T15:30:11Z

    how to change the color of comments? im talking about this part: 'author': {'username': 'John'}, 'body': 'Nice cats!' like purple phrase 'Nice cats!'

  • #80 Miguel Grinberg said 2020-01-19T18:39:20Z

    @Pinkie: if you want to render text in purple, then you have to modify the HTML template to set that color using CSS. Maybe something like

    comment here

    .

  • #81 Agiamah Precious said 2020-01-24T21:52:34Z

    After changing return 'Hello World' to: user = {'username': 'Precious'} return render_template('index.html', title='Home', user=user)

    I get an error message saying NameError: 'render_template' is not defined

    How do i solve this place.

  • #82 Miguel Grinberg said 2020-01-25T00:28:27Z

    @Agiamah: you need to add an import at the top of the routes.py file for the render_template function. You must have missed it, please review this article again.

  • #83 Abhinay Reddy said 2020-01-27T00:08:39Z

    Wonderful! Just started my FLASK journey with this and the teaching is smooth.

  • #84 Nicolas Gonzalez said 2020-02-26T22:54:32Z

    Amazing tutorial. Thank you so much!

  • #85 chandan said 2020-03-22T09:40:16Z

    You are awesome and after reading your article I hate django more - likely serves the macho types but is way too bulky and unnecessary big learning curve , had tried doing something meaningful with it for months but since there is no such a clearly explained baby steps tutorial at all each time left half way in dismay. Flask is way for me and I gotta buy your book.

  • #86 Kyle said 2020-03-23T05:58:09Z

    You are honestly awesome, I have looked around and experienced other tutorials and yours is one of the best if not the best. I think you are doing a great job, and its cool you are part of the pallets project. I am going to work through all of these and I want to support your work, I am going to purchase the "MicroPython and the Internet of Things" when I get the $$. Class sounds awesome and I am sure since its coming from you its worth it. Thanks again!

  • #87 SHARUN E RAJEEV said 2020-03-28T06:33:03Z

    Hi,Miguel Thanks for such a wonderful tutorial on Flask. In that inheritance part, the address we must provide must be the html address of the page or the local address of the file in our system? I couldn't redirect to my Webpage using the about method. Can you explain that redirecting part? Thanks, Sharun

  • #88 Miguel Grinberg said 2020-03-28T13:59:09Z

    @Sharun: template inheritance is only between template files. Jinja does not know anything about URLs or web pages, just template files.

  • #89 VIDYA said 2020-04-04T05:46:47Z

    Great work Miguel :)

  • #90 Guy said 2020-04-19T11:11:04Z

    Hey Miguel,

    Thanks for the tutorial - best one I've used.

    I'm trying to create a navigation system which shows a different navigation bar based on the 'mode' the user selects. I want the user to be able to switch between modes by clicking on a button saying 'switch to seller/buyer mode'. Once the user switches to a specific mode, the navigation bar should change to display the links I want to show for that mode. I thought I might be able to do this with a Flask Form but not sure how to pass the form value to my base.html template.

    Can you point me in the right direction on how to set this up?

    Thanks very much! Guy

  • #91 Miguel Grinberg said 2020-04-19T14:25:18Z

    @Guy: you can store the selected mode in the user session, and that use that in the base template to decide which of the bars to render.

  • #92 Vince said 2020-04-20T08:40:19Z

    This seems kinda backwards to me. I think index.html should be the base and there should be an includes keyword rather than extends. Then, a content.html could defines the block containing the posts. That way, it would be much easier to look at one file and see all the other files that go into what is finally rendered.

  • #93 Miguel Grinberg said 2020-04-20T10:37:08Z

    @Vince: not sure what you are saying is different. If you rename index.html to base.html and content.html to index.html then you have exactly what we are doing in this tutorial, I think. If you like your names better, then by all means rename the files.

  • #94 xtian said 2020-04-24T16:28:42Z

    "Creating mock objects is a useful technique that allows you to concentrate on one part of the application without having to worry about other parts of the system that don't exist yet."

    This advise is very useful. I've programmed my own CLI scripts. My goal is to learn Flask to create a web application version of some of them.

    Each tutorial only takes me so far towards learning what I need to do. Combining lessons is difficult task on it's own.

    The one technique which seems to gather the most momentum is mocking objects + git branches. I have branches for forms, for blueprint + factory, database & models. As I learn more, I improve each branch and learn the best practices (or the practices which make the most sense for what I know and my goals).

    Mocking is absolutely essential to keep each of these branches in a working state.

  • #95 RR said 2020-04-29T08:36:48Z

    You have a gift for explaining things super clearly. I followed the official flask tutorial from start to finish and was able to get it up and running but immediately struggled trying to apply some of that stuff to what I wanted to do for my own projects.

    After just the first 2 lessons in this tutorial I can already move forward confidently on my own work.

    Thanks man. RR

  • #96 Jo said 2020-05-02T14:23:40Z

    I dont even know whether you are still active or not but i just want to say thank you because this is one of the best tutorial i have ever read!

  • #97 Anup Goswami said 2020-05-22T00:32:35Z

    Awesome tutorial - simple and informative. Thanks so much!!!

  • #98 Vivek Patel said 2020-06-02T22:55:25Z

    Hey Miguel, I am trying to learn to make website for a project. But I keep getting Error 500 The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application after I run it.

  • #99 Miguel Grinberg said 2020-06-03T10:24:25Z

    @Vivek: look for a stack trace for your error in the terminal where you are running the application. That should give you more information on the problem.

  • #100 Abhinav said 2020-07-04T08:09:27Z

    I want to add one navigation bar to index and login pages while a different navigation bar when the user is logged in. Can you explain how to do this. I searched the web but couldn't find an answer.

    Thanks for this awesome tutorials. Its really helping me a lot.

Leave a Comment