The Flask Mega-Tutorial, Part II: Templates (2012)

Posted by
on under

(Great news! There is a new version of this tutorial!)

This is the second article in the series where I document my experience writing web applications in Python using the Flask microframework.

The goal of the tutorial series is to develop a decently featured microblogging application that demonstrating total lack of originality I have decided to call microblog.

NOTE: This article was revised in September 2014 to be in sync with current versions of Python and Flask.

Here is an index of all the articles in the series that have been published to date:

Recap

If you followed the previous chapter you should have a fully working, yet very simple web application that has the following file structure:

    microblog\
      flask\
        <virtual environment files>
      app\
        static\
        templates\
        __init__.py
        views.py
      tmp\
      run.py

To run the application you execute the run.py script and then open the http://localhost:5000 URL on your web browser.

We are picking up exactly from where we left off, so you may want to make sure you have the above application correctly installed and working.

Why we need templates

Let's consider how we can expand our little application.

We want the home page of our microblogging app to have a heading that welcomes the logged in user, that's pretty standard for applications of this kind. Ignore for now the fact that we have no way to log a user in, I'll present a workaround for this issue in a moment.

An easy option to output a nice and big heading would be to change our view function to output HTML, maybe something like this:

from app import app

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

Give the application a try to see how this looks in your browser.

Since we don't have support for users yet I have resorted to using a placeholder user object, sometimes called fake or mock object. This allows us to concentrate on certain aspects of our application that depend on parts of the system that haven't been built yet.

I hope you agree with me that the solution used above to deliver HTML to the browser is very ugly. Consider how complex the code will become if you have to return a large and complex HTML page with lots of dynamic content. And what if you need to change the layout of your web site in a large app that has dozens of views, each returning HTML directly? This is clearly not a scalable option.

Templates to the rescue

If you could keep the logic of your application separate from the layout or presentation of your web pages 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 site's behaviors in Python. Templates help implement this separation.

Let's write our first template (file app/templates/index.html):

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

As you see above, we just wrote a mostly standard HTML page, with the only difference that there are some placeholders for the dynamic content enclosed in {{ ... }} sections.

Now let's see how we use this template from our view function (file app/views.py):

from flask import render_template
from app import app

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

Try the application at this point to see how the template works. Once you have the rendered page in your browser you may want to view the source HTML and compare it against the original template.

To render the template we had to import a new function from the Flask framework called render_template. This function takes a template filename and a variable list of template arguments and returns the rendered template, with all the arguments replaced.

Under the covers, the render_template function invokes the Jinja2 templating engine that is part of the Flask framework. Jinja2 substitutes {{...}} blocks with the corresponding values provided as template arguments.

Control statements in templates

The Jinja2 templates also support control statements, given inside {%...%} blocks. Let's add an if statement to our template (file app/templates/index.html):

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

Now our template is a bit smarter. If the view function forgets to define a page title then instead of showing an empty title the template will provide its own title. Feel free to remove the title argument in the render_template call of our view function to see how the conditional statement works.

Loops in templates

The logged in user in our microblog application will probably want to see recent posts from followed users in the home page, so let's see how we can do that.

To begin, we use our handy fake object trick to create some users and some posts to show (file app/views.py):

def index():
    user = {'nickname': 'Miguel'}  # fake user
    posts = [  # fake array of posts
        { 
            'author': {'nickname': 'John'}, 
            'body': 'Beautiful day in Portland!' 
        },
        { 
            'author': {'nickname': 'Susan'}, 
            'body': 'The Avengers movie was so cool!' 
        }
    ]
    return render_template("index.html",
                           title='Home',
                           user=user,
                           posts=posts)

To represent user posts we are using a list, where each element has author and body fields. When we get to implement a real database we will preserve these field names, so we can design and test our template using the fake objects without having to worry about updating it when we move to a database.

On the template side we have to solve a new problem. The list can have any number of elements, it will be up to the view function to decide how many posts need to be presented. The template cannot make any assumptions about the number of posts, so it needs to be prepared to render as many posts as the view sends.

So let's see how we do this using a for control structure (file app/templates/index.html):

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

Simple, right? Give it a try, and be sure to play with adding more content to the posts array.

Template inheritance

We have one more topic to cover before we close for the day.

Our microblog web application will need to have a navigation bar at the top of the page with a few links. Here you will get the link to edit your profile, to login, logout, etc.

We can add a navigation bar to our index.html template, but as our application grows we will be needing to implement more pages, and this navigation bar will have to be copied to all of them. Then you will have to keep all these identical copies of the navigation bar in sync, and that could become a lot of work if you have a lot of pages and templates.

Instead, we can use Jinja2's template inheritance feature, which allows us to move the parts of the page layout that are common to all templates and put them in a base template from which all other templates are derived.

So let's define a base template that includes the navigation bar and also the bit of title logic we implemented earlier (file app/templates/base.html):

<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 we use the block control statement to define the place where the derived templates can insert themselves. Blocks are given a unique name, and their content can be replaced or enhanced in derived templates.

And now what's left is to modify our index.html template to inherit from base.html (file app/templates/index.html):

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

Since the base.html template will now take care of the general page structure we have removed those elements from this one and left only the content part. The extends block establishes the inheritance link between the two templates, so that Jinja2 knows that when it needs to render index.html it needs to include 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 into one. When we get to write new templates we will also create them as extensions to base.html.

Final words

If you want to save time, the microblog application in its current state is available here:

Download microblog-0.2.zip.

Note that the zip file does not include the flask virtual environment, you will need to create it following the instructions in the first chapter in the series before you can run the application.

If you have any questions or comments feel free to leave them below.

In the next chapter of the series we will be looking at web forms. I hope to see you then.

Miguel

Become a Patron!

Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!

45 comments
  • #1 Bob said

    I don't have anything templatized under index.html, but I do have templatized code under some javascripts that gets included under <head> of index.html. How do I render_template on these javascripts have index.html read them?

  • #2 Miguel Grinberg said

    You can easily work with javascript templates. Just put your javascript file in the app's template directory and add your template substitutions like you would on an HTML file. Then create a view function that renders this template. The view will be associated with a route, like a regular html based view. Finally, in the script tag that imports this javascript set the src argument to the javascript route instead of using a static javascript filename. I hope this helps!

  • #3 Bob said

    This helps, but I'm totally new to Flask. A short extension to the current tutorial would definitely help, both me and other.

    Thanks

  • #4 Miguel Grinberg said

    Well, the thing is I'm not really sure there is a good reason to use javascript templates, and that doesn't really fit well with this tutorial. It seems to me you could move the dynamic sections of your javascript to the parent HTML and then pass these as arguments to javascript functions.
    But in any case, if you ask this question in stackoverflow.com and send me the link I'll be happy to go into more detail regarding how to implement it.

  • #5 Catherine Penfold said

    Hi Miguel,
    I have changed the <title> tags to <h1> as they don't seem to work. Is this a browser issue?
    Thanks

  • #6 Miguel Grinberg said

    Catherine, the <title> tags work on all browsers. What is it that you think doesn't work? Browsers typically show the page title in the window's title bar, this is not part of the page.

  • #7 Catherine Penfold said

    Hi Miguel,
    Ah yes, I see it now. Did you have any suggestions re the flask-wtf?
    thanks

  • #8 Catherine said

    Oh, and I worked out the issue with the ./run.py not working, it's because you need to change the file permissions (chmod 755) - you might want to add that to your tutorial

  • #9 Miguel Grinberg said

    I have added the chmod step for the run.py script, I've missed that. As far as the flask-wtf I don't know. You should not need to move anything, the pip installer should be putting everything in the right location. If you are invoking the Python interpreter from the virtual environment (by having the shebang line point to flask/bin/python) then all the installed modules should be reachable.

  • #10 Anthony said

    Thanks for the tutorial series. I was a web developer in PHP/ASP a long time past, and I've been trying to get back into it for a project. It's been difficult jumping back in when so much has changed (yay, standards! yay, css3!).

    This is slowly getting me back on the path to production.

  • #11 Tony said

    Miguel, I think you should continue to polish this and self-publish a book or get together with O'Reilly, O'rly or whatever and make a book on this.

    Perhaps make a github / sphinx release of your tutorial so it's not stuck on a blog. Thanks for this.

  • #12 Josh said

    It's awesome how close jinja2 is to twig.

  • #13 Emily said

    I am new to flask and am finding these posts very informative and interesting! I have a quick question about the if statements in flask. In python elif is supported, can this also be used in flask or should nested if statements be used to accomplished this?

  • #14 Miguel Grinberg said

    @Emily: you can certainly use elif, Flask does not restrict the language at all.

  • #15 Bruce said

    Just wanted to say I wanted to learn Flask, and your tutorials are really helping. All the examples worked first time for me too. On to part 3!

  • #16 daleng said

    Miguel ,thanks a lot,I'm a chinese newr to flask,my English is so poor that "pocoo.org"'s document is too hard for me.But I think I can understand your words.Thank you again!

  • #17 Scott Robinett said

    Miquel, I have been looking at Mako templates and see that you can "include" a template, as well as utilize 'def' within a template. I realize this is a different approach, but do you believe Mako is a better solution overall?

    I have programmed with coldfusion for many years and trying to stretch myself a bit. I love the tutorials and I think I'm catching on, but have always struggled with the separation of logic.

    thanks again, I'm looking forward to the book.

  • #18 Miguel Grinberg said

    @Scott: Separation of logic is a key concept. You don't want logic in your templates, and you don't want HTML stuff in your view functions. You can achieve this separation with both template engines, so in my view the choice is more about the syntax than the features of each engine. Also note that Jinja2 is well integrated with Flask, where "current_app", "request" and others are made available to templates automatically. If you go with Mako you will need to write some of that integration yourself.

  • #19 PeterD said

    Miguel, One good learning tool for everyone is scatter instructions in your tutorials to have the readers use their web brower's "view source" menu option so they can see how the template engine has been combining templates and inserting variable values into the final output html that is delivered to client web browser.

  • #20 David Branner said

    Typo: "we use of our" => "we use our"

  • #21 Gery said

    g-e-n-i-u-s

  • #22 Manish T said

    Hi, I am getting this error:

    manish@debian:~/Desktop/microblog$ ./run.py
    * Running on http://127.0.0.1:5000/
    Traceback (most recent call last):
    File "./run.py", line 3, in <module>
    app.run(debug = True)
    File "/home/manish/Desktop/microblog/flask/local/lib/python2.7/site-packages/flask/app.py", line 739, in run
    run_simple(host, port, self, *options)
    File "/home/manish/Desktop/microblog/flask/local/lib/python2.7/site-packages/werkzeug/serving.py", line 706, in run_simple
    test_socket.bind((hostname, port))
    File "/usr/lib/python2.7/socket.py", line 224, in meth
    return getattr(self._sock,name)(
    args)
    socket.error: [Errno 98] Address already in use

  • #23 Miguel Grinberg said

    @Manish: you have two instances of the same application, or two different applications trying to use the same port for the server.

  • #24 James said

    Hi Miguel,

    I followed your tutorial for creating a template, but when I launch the app it says template not found. In views I have, render_template(~/tempaltes/index.html), running python 2.7 on ubuntu.

  • #25 Miguel Grinberg said

    @James: try putting the templates in a "templates" folder next to your main script, then pass "index.html" to render_template.