This is the second article in the series where I describe my experience of writing a Python web application using the Flask mic framework.
The purpose of this guide is to develop a fairly functional microblog application, which I decided to call
microblog
in the absence of originality.
')
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 launch 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 properly.
Why do we need templates
Consider how we can expand our little application.
We want a microblogging header on the main page of our application that welcomes the logged-in user, which is very standard for applications of this kind. For now, ignoring the fact that we have no 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 function of views to issue html, like this:
from app import app @app.route('/') @app.route('/index') def index(): user = { 'nickname': 'Miguel' }
Try to see how the application looks in your browser.
As long as we do not have user support, however, I turned to using prototypes of user models, which are sometimes referred to as fake or imaginary prototypes. This allows us to concentrate on some aspects of our application that depend on parts of the system that have not yet been written.
I hope you agree with me that the decision above is very ugly. Imagine how complex the code will be if you have to return a bulky HTML page with a lot 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 rush to the rescue
Have you ever thought that if you could keep the logic of your application and the layout, or the presentation of your pages, much better organized? You can even hire a web designer to create a stunning website while you program it [site] behavior using Python. Templates will help make this separation.
Let's write our first template (
app/templates/index.html
file):
<html> <head> <title>{{title}} - microblog</title> </head> <body> <h1>Hello, {{user.nickname}}!</h1> </body> </html>
As seen above, we simply wrote a standard HTML page, but with one difference: the placeholders for dynamic content are enclosed in the {{...}} section
Now 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' }
Run the application at this stage to see how the templates work. If a page is drawn 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 takes the name of the template and the list of variable arguments of the template, and returns the finished template with the arguments replaced.
Under the hood: The render_template function calls the Jinja2 templating engine, which is part of the Flask framework. Jinja2 replaces the {{...}} blocks with their corresponding values, which are passed as template arguments.
Control statements in templates
Jinja2 templates, among other things, support control operators that are passed 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 little wiser. If we forget to define the name of the page in the view function, then instead of an exception, the template will give us our own name. You can safely remove the header argument from the call to render_template in our view function to see how the if statement works.
Looping in patterns
A user who has entered our application will certainly want to see recent posts from users from his contact list on the main page, let's see how to do this.
In the beginning, we will do a trick to create several fake users and several entries to display (file
app/views.py
):
def index(): user = { 'nickname': 'Miguel' }
To display custom entries, we use a list where each element will have the author and main part of the field. When we get to the implementation of the real database, we will save these field names, so that we can develop and test our template using fake objects without worrying about updating them when we go to the database.
From the side of the template, we have to solve a new problem. The existing list may contain any number of items 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 presentation will send.
Let's see how to do this using the control structure (file
app/templates/index.html
):
<html> <head> {% if title %} <title>{{title}} - microblog</title> {% else %} <title>microblog</title> {% endif %} </head> <body> <h1>Hi, {{user.nickname}}!</h1> {% for post in posts %} <p>{{post.author.nickname}} says: <b>{{post.body}}</b></p> {% endfor %} </body> </html>
Not so hard, right? Check the application and be sure to check the addition of new content to the list of records.
Pattern Inheritance
We will cover another topic before you finish today.
Our microblogging application needs a navigation bar with several links on top of the page. There will be links to edit your profile, exit, etc.
We can add a navigation bar to our
index.html
template, but as soon as our application grows, we will need more templates, and we will need to copy the navigation panel into each of them. Then you must keep all these copies the same. This can be a time consuming task if you have a lot of templates.
Instead, we can use inheritance in Jinja2 templates, which allows us to move portions of the page layout to a common base template for everyone, from which all other templates will be inherited.
Now we define the base template, which includes the navigation bar, as well as the header logic that we implemented earlier (file
app/templates/base.html
):
<html> <head> {% if title %} <title>{{title}} - microblog</title> {% else %} <title>microblog</title> {% endif %} </head> <body> <div>Microblog: <a href="/index">Home</a></div> <hr> {% block content %}{% endblock %} </body> </html>
In this pattern, we used the
block
control statement to determine where the child patterns could be inserted. Blocks are given unique names.
Now it remains to change our
index.html
so that it
base.html
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 %}
Now only the
base.html
template
base.html
responsible for the overall structure of the page. We removed those elements from here and left only a part with the contents. The
extends
block establishes a hereditary relationship between 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
block
statements named
content
, which is why Jinja2 knows how to combine two templates into one. When we write new templates, we will also create them as extensions
base.html
Final words
If you want to save time, the microblog application in the current stage is available here:
Download
microblog-0.2.zipNote that the zip file does not contain the flask virtual environment. Create it yourself, following the instructions in the first part, then you can run the application.
In the next part of the series, we take a look at the forms. Hope to see you.
Miguel