The Flask Mega-Tutorial, Part 2: Templates

Original author: Miguel Grinberg
  • Transfer
  • Tutorial
This is the second article in a series where I describe my experience writing a Python web application using the Flask microframework.

The purpose of this guide is to develop a fairly functional microblogging application, which I decided to name for a complete lack of originality microblog.



Brief repetition


If you followed the instructions in the first part, then you should have a fully working, but still very simple application with this file structure:

microblog\
  flask\
    <файлы виртуального окружения>
  app\
    static\
    templates\
    __init__.py
    views.py
  tmp\
  run.py


To run the application, you run the run.py script, then open the url http: // localhost: 5000 in your browser.

We will continue from where we left off, so you should make sure that the above application is installed and working correctly.

Why do we need templates


Consider how we can expand our small application.
We want the main page of our microblogging application to have a header that welcomes the user who is logged in, which is very standard for this kind of application. For now, ignore the fact that we do not have users in the application, I will provide a solution to this problem at the right time.

A simple means of displaying a large and beautiful header would be to change our presentation function to html, something like this:
from app import app
@app.route('/')
@app.route('/index')
def index():
    user = { 'nickname': 'Miguel' } # выдуманный пользователь
    return '''

  
    Home Page

Hello, ''' + user['nickname'] + '''

'''

Try to see what the application looks like in your browser.

While we do not have user support, however, I turned to the use of prototypes of the user model, which are sometimes called fake or imaginary prototypes. This allows us to concentrate on some aspects of our application, depending on parts of the system that have not yet been written.

I hope you agree with me that the solution above is very ugly. Imagine how complicated the code becomes if you have to return a bulky HTML page with lots of dynamic content. And what if you need to change the layout of your website in a large application with many views that return HTML directly? This is obviously not a scalable solution.

Templates to the rescue


Have you ever thought that if you could keep the logic of your application and layout separate, or the presentation of your pages would be much better organized? You can even hire a web designer to create a stunning website while you program its [site] behavior using Python. Templates will help make this separation.

Let's write our first template (file app/templates/index.html):
{{title}} - microblog

Hello, {{user.nickname}}!



As you can see above, we just wrote a standard HTML page, but with one difference: placeholders for dynamic content are enclosed in the {{...}} section.

Now we will consider the use of the template in 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' } # выдуманный пользователь
    return render_template("index.html",
        title = 'Home',
        user = user)


Launch the application at this stage to see how the templates work. If a page is rendered in your browser, then you can compare its source code with the original template.

To render the page, we need to import from Flask a new function called render_template. This function accepts the name of the template and a list of variable arguments to the template, and returns a ready-made template with replaced arguments.

Under the hood: the render_template function calls the Jinja2 template engine, which is part of the Flask framework. Jinja2 replaces the {{...}} blocks with the corresponding values ​​passed as template arguments.

Control Operators in Templates


Jinja2 templates, among other things, support control statements passed within {% ...%} blocks. Let's add an if statement to our template (file app/templates/index.html):

  
    {% if title %}
    {{title}} - microblog
    {% else %}
    Welcome to microblog
    {% endif %}
  
  
      

Hello, {{user.nickname}}!



Now our template is slightly smarter. If we forget to define the page name in the presentation function, then in return for the exception, the template will provide us with its own name. You can safely remove the header argument from the render_template call in our view function to see how the if statement works.

Pattern cycles


The user who logs into our application will probably want to see recent entries from users from his contact list on the main page, let's see how to do this.

At the beginning, we’ll do the trick to create some fake users and some records to display (file app/views.py):
def index():
    user = { 'nickname': 'Miguel' } # выдуманный пользователь
    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 display custom entries, we use a list where each element will have its author and main fields. When we get to the implementation of the real database, we will save these field names, so that we can design and test our template using fake objects without worrying about updating them when we go to the database.

From the template side, we have to solve a new problem. An existing list can contain any number of elements and you need to decide how many messages will be submitted. The template cannot make any assumptions about the number of messages, so it should be ready to display as many messages as the view sends.

Let's see how to do this using the control structure (file app/templates/index.html):

  
    {% if title %}
    {{title}} - microblog
    {% else %}
    microblog
    {% endif %}
  
  
    

Hi, {{user.nickname}}!

{% for post in posts %}

{{post.author.nickname}} says: {{post.body}}

{% endfor %}


Not so hard, right? We’ll check the application and be sure to check the addition of new content to the list of entries.

Template Inheritance


We’ll cover one more topic before finishing for today.

Our microblogging application needs a navigation bar with several links at the top of the page. There will be links to edit your profile, exit, etc.

We can add a navigation bar to our template index.html, but as soon as our application grows, we will need more templates, and the navigation panel will need to be copied to each of them. Then you should keep all these copies the same. This can be a tedious task if you have many templates.

Instead, we can use inheritance in Jinja2 templates, which allows us to move parts of the page layout to a common basic template from which all other templates will be inherited.

Now we define the basic template, which includes the navigation bar, as well as the header logic that we implemented earlier (file app/templates/base.html):

  
    {% if title %}
    {{title}} - microblog
    {% else %}
    microblog
    {% endif %}
  
  
    
Microblog: Home

{% block content %}{% endblock %}


In this template, we used a control statement blockto determine where child templates can be inserted. Blocks are given unique names.

Now it remains to change ours index.htmlso that it inherits from base.html(file app/templates/index.html):
{% extends "base.html" %}
{% block content %}

Hi, {{user.nickname}}!

{% for post in posts %}

{{post.author.nickname}} says: {{post.body}}

{% endfor %} {% endblock %}


Now only the template base.htmlis responsible for the overall structure of the page. We removed those elements from here and left only the part with the contents. The block extendsestablishes a hereditary relationship between the two templates, so Jinja2 knows: if we need to give index.html, then we need to include it in base.html. Two templates have matching operators blockwith a name content, which is why Jinja2 knows how to combine two templates into one. When we write new templates, we will also begin to create them as extensionsbase.html

Final words


If you want to save time, the microblogging application in the current stage is available here:
Download microblog-0.2.zip

Note that the zip file does not contain the flask virtual environment. Create it yourself, following the instructions in the first part, after which you can start the application.

In the next part of the series we take a look at the forms. Hope to see you again.

Miguel

Also popular now: