The code for this lesson can be found here on the site Github repo.
Jinja2 is the default templating engine for Flask. If you are using the Anaconda installer, jinja2 is already installed. If not, you'll have to run this from the command line to install it:
$ pip install jinja2
So what is a templating engine? Remember how we used string.Template in past lessons?
from string import Template
my_template = Template("Hello, ${person_name}, how are you?")
for name in ['Jane', 'Bob', 'Dan']:
s = my_template.substitute(person_name=name)
print(s)
# Hello, Jane, how are you?
# Hello, Bob, how are you?
# Hello, Dan, how are you?
Or, when we're in a rush, the format() function that comes with every Python string?
xs = 'Hello, {name}, do you like {something}?'
s = xs.format(name='Booboo', something="honey")
print(s)
# Hello, Booboo, do you like honey?
The jinja2 template engine gives us more power and flexibility at the cost of having to learn a new syntax:
Here's a standalone (i.e., without Flask) basic usage of its API:
from jinja2 import Template
template = Template('Hello {{ name }}, do you like {{something}}?')
s = template.render(name='Jane Doe', something='radio')
print(s)
# Hello Jane Doe, do you like radio?
Except for a difference in naming conventions, doesn't look much different than string.Template
or format()
, right?
Well, first be aware that there's a whole different syntax for marking variable names: double-curly-braces instead of single:
Template('Hello {{ name }}')
So where jinja2 gets fun is how it let's us include a little bit of logic into our templates.
Consider counting to 5 and writing it as a HTML string (each number in its own paragraph tag). Here's the old-fashioned way of doing it:
mystring = ''
for i in range(1, 6):
mystring += "<p>{num}</p>".format(num=i)
print(mystring)
# <p>1</p><p>2</p><p>3</p><p>4</p><p>5</p>
And here's how to embed a for-loop into a jinja2 template:
from jinja2 import Template
mytemplate = Template("""
{% for num in numbers %}
<p>{{ num }}</p>
{% endfor %}""")
print(mytemplate.render(numbers=range(1,6)))
# <p>1</p>
#
# <p>2</p>
#
# <p>3</p>
#
# <p>4</p>
#
# <p>5</p>
Well, they aren't exactly equivalent. And now we have to learn jinja2's wonky syntax, e.g. {% for x in stuff %}
and {% endfor %}
– but jinja2 helps us write cleaner programatically-generated-HTML.
Create a new boilerplate app.py. But let's have it print some proper, nice looking HTML, with some CSS styling:
from flask import Flask
app = Flask(__name__)
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<style>
body{
width: 90%;
margin: auto;
font-family: "Helvetica Neue";
}
</style>
</head>
<body>
<h1>Hello world!</h1>
<p>Today is <strong>SOMEDATE</strong> and it is a great day!</p>
</body>
</html>
"""
@app.route("/")
def homepage():
return HTML_TEMPLATE
if __name__ == '__main__':
app.run(use_reloader=True, debug=True)
We'll fill out SOMEDATE
later; create the app.py and get it running. It should look like this:
It's a pretty HTML page, but it is an ugly app.py. So let's use jinja2 to move the HTML code out of app.py.
render_template()
Flask knows how to connect to jinja2. So we just need to include this import statement to have access to render_template()
:
from flask import render_template
What does the render_template() function do? It takes a string value that represents a filename containing a string template. In other words, the file that contains the value of HTML_TEMPLATE
.
/templates
conventionBut here's the catch: Flask expects there to be a directory relative to app.py
named: templates. So if we were to make this call:
render_template('homepage.html')
We would need to create the path, templates/homepage.html
And the contents of that file, for our purposes, is the raw HTML and any templating code – note the use of {{ the date }}
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<style>
body{
width: 90%;
margin: auto;
font-family: "Helvetica Neue";
}
</style>
</head>
<body>
<h1>Hello world!</h1>
<p>Today is <strong>{{ the_date }}</strong> and it is a great day!</p>
</body>
</html>
So, give it a try:
templates
subdirectorytemplates/homepage.html
Then, modify (i.e. slim it down) app.py like so:
from flask import Flask
from flask import render_template
app = Flask(__name__)
@app.route("/")
def homepage():
htmltxt = render_template('homepage.html')
return htmltxt
if __name__ == '__main__':
app.run(use_reloader=True, debug=True)
Note that there seems to be a missing word where we expected {{ the_date }}
; because we didn't pass arguments into render_template()
specifying what {{ the_date }}
is supposed to be, it simply leaves it blank.
The variables that you want to render can simply be passed in as extra arguments to render_template()
:
@app.route("/")
def homepage():
htmltxt = render_template('homepage.html', the_date="TODAY")
return htmltxt
Or, if you prefer sending in the real date as your machine discerns it:
from flask import Flask
from flask import render_template
from datetime import datetime
app = Flask(__name__)
@app.route("/")
def homepage():
htmltxt = render_template('homepage.html', the_date=datetime.now())
return htmltxt
if __name__ == '__main__':
app.run(use_reloader=True, debug=True)
OK, let's do something more fun. Let's add the counting of numbers to our template file, homepage.html
.
You can read up on the for-loop syntax here:
http://jinja.pocoo.org/docs/dev/templates/#for
And here it is in homepage.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<style>
body{
width: 90%;
margin: auto;
font-family: "Helvetica Neue";
}
</style>
</head>
<body>
<h1>Hello world!</h1>
<p>Today is <strong>{{ the_date }}</strong> and it is a great day!</p>
<h2>Let's count!</h2>
{% for n in numbers %}
<div>{{ n }} bottles of soy.</div>
{% endfor %}
<p>Done counting!</p>
</body>
</html>
If you attempt to visit the homepage without providing the numbers
variable…you won't see any numbers:
So let's pass in a set of numbers (or any sequence) in the homepage()
view function:
from flask import Flask
from flask import render_template
from datetime import datetime
app = Flask(__name__)
@app.route("/")
def homepage():
somenumbers = range(1, 8)
htmltxt = render_template('homepage.html',
the_date=datetime.now(),
numbers=somenumbers)
return htmltxt
if __name__ == '__main__':
app.run(use_reloader=True, debug=True)
Jinja has conditional-branching too, with its kind-of-familiar-but-not-quite-the-same syntax:
You can read up on the if-loop syntax here:
http://jinja.pocoo.org/docs/dev/templates/#if
And here it is in homepage.html
, if we want to print "even"
and "odd"
after each number – remember that the percentage sign in Python performs the modulo operation (look it up yourself if you don't remember it from arithmeti class):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<style>
body{
width: 90%;
margin: auto;
font-family: "Helvetica Neue";
}
</style>
</head>
<body>
<h1>Hello world!</h1>
<p>Today is <strong>{{ the_date }}</strong> and it is a great day!</p>
<h2>Let's count!</h2>
{% for n in numbers %}
{% if n % 2 == 0 %}
<div>{{ n }} is an even number of bottles of soy.</div>
{% else %}
<div>{{ n }} is an odd number of bottles of soy.</div>
{% endif %}
{% endfor %}
<p>Done counting!</p>
</body>
</html>
We don't need to make any changes to app.py – the logic is all in the template:
This following step is not particularly related to jinja2 templating, but it is standard web dev practice.
The CSS style code that we've thrown into templates/homepage.html
should be in its own file. By convention, these files are saved with a .css
extension and are referred to as external stylesheets.
Why external stylesheets? Because it just doesn't work out, in the long run, to keep style code in the HTML templates.
So first, go into templates/homepage.html
and cut the following code that is between the <style>
tags (and you can delete the <style>
tags:
body{
width: 90%;
margin: auto;
font-family: "Helvetica Neue";
}
Make a new directory relative to app.py named static
. Then create a new file named static/styles.css
and paste in the CSS code that we cut out of homepage.html
.
url_for()
templating methodFlask has a nifty url_for()
method that lets us delegate the work of creating internal URLs for the Flask app. In this case, we need to generate the local path to static/styles.css
This is the url_for()
call to do it:
url_for('static', filename='style.css')
However, url_for()
only has power within a Flask template. Which means, we don't put this in app.py. We put it into homepage.html.
Let's just throw it in as plaintext. You can put this anywhere inside the body tags, e.g.:
<h1>Hello World</h1>
<p>{{ url_for('static', filename='style.css') }}</p>
The rendered URL should look like this:
/static/style.css
Except we don't want it there. We want to link this stylesheet to our template. Here's the HTML convention for that:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<link rel="stylesheet" href="some/url">
</head>
And here it is with our appropriate call to url_for()
– mind those braces and the quotation-marks:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
Now we're getting into multi-file web-apps. It's getting difficult to show all of the code at once. But this is what your file tree should look like:
├── app.py
├── static
│ └── styles.css
└── templates
└── homepage.html
The code for this lesson can be found here on the site Github repo.