cs304 logo

Templating with Jinja2

Today, we'll conclude Python/CGI scripts, and look at using the Jinja2 templating module.

Goal

By the end of today, you will be able to use the Jinja2 templating module with your database scripts.

Plan

Old material

New material

  1. Recap reading:
  2. Questions
  3. Exercise: rendering the template with a database query

Loading a Template

Your script can load one of a set of templates that live in an environment. This extra indirection allows the templates to be preloaded and compiled so that rendering is faster than if they had to be loaded and parsed from scratch each time.

The environment object can also list the available templates (in case you forget?) using the .list_templates method.

I've put a few templates in the templates subdirectory of this directory.

The main.html template looks like this:

<!doctype html>
<html lang='en'>
<head>
    <meta charset='utf-8'>
    <meta name=author content="Scott D. Anderson">
    <title>{{title}}</title>
</head>
<body>

<h1>{{title}}</h1>

<p>This page is about {{content}}

</body>
</html>

Let's examine this interaction:

[cs304@tempest jinja2] python
>>> from jinja2 import Environment, FileSystemLoader
>>> env = Environment(loader=FileSystemLoader('./templates'))
>>> env

>>> env.list_templates()
['main.html']
>>> tmpl = env.get_template('main.html')
>>> tmpl.render(title='hi there')
u'<!doctype html>\n<html lang=\'en\'>\n<head>\n    <meta charset=\'utf-8\'>\n    <meta name=author ...
>>> tmpl.render(title='hi there',content='nothing much')
u'<!doctype html>\n<html lang=\'en\'>\n<head>\n    <meta charset=\'utf-8\'>\n    <meta name=author ...
>>> print(tmpl.render(title='hi there',content='nothing much'))
<!doctype html>
<html lang='en'>
<head>
    <meta charset='utf-8'>
    <meta name=author content="Scott D. Anderson">
    <title>hi there</title>
</head>
<body>

<h1>hi there</h1>

<p>This page is about nothing much

</body>
</html>
>>> 

Notice:

Stereotypical Usage

from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader('./'))
tmpl = env.get_template('actorlist.tmpl')

actors = ['George Clooney', 'Salma Hayek', 'Jennifer Lopez', 'Colin Firth']
combo = tmpl.render(description='Actors',people=actors)
print(combo)

Of course, we could save this to a file if we wanted to.

Loaders

Jinja2 has a variety of loaders. With Flask, we'll create a Python package and Flask will use the PackageLoader.

The Template Language

Now let's turn to the template language

Variables

If the tag is just a python variable, the variable's value is inserted:

>>> from jinja2 import Template

>>> tmpl = Template('Hello, my name is {{ name }}')
>>> tmpl.render(name='Inigo Montoya')
u'Hello, my name is Inigo Montoya'

You can also access elements of dictionaries:

tmpl = Template('Hello, my name is {{ row.name }}, born on {{ row.birthdate }}')
data = {'name': 'Colin Firth', 'birthdate': '1960-09-10'}
tmpl.render(row=data)
u'Hello, my name is Colin Firth, born on 1960-09-10'

Arrays

If you have an array of data, you can loop over it. Here's a template that has a loop that uses Python syntax:

<!doctype html>
<html lang='en'>
<head>
    <meta charset='utf-8'>
    <meta name=author content="Scott D. Anderson">
    <title>{{title}}</title>
</head>
<body>

<h1>{{title}}</h1>

<p>This page is about {{content}}

</body>
</html>

Here's the Python script that uses that template. No loops here!

#!/usr/local/bin/python2.7

from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader('./templates'))
tmpl = env.get_template('actorlist.tmpl')

actors = [{'name': 'George Clooney', 'birthdate': '1961-05-06'},
          {'name': 'Salma Hayek', 'birthdate': '1967-09-02'},
          {'name': 'Jennifer Lopez', 'birthdate': '1969-07-24'},
          {'name': 'Colin Firth', 'birthdate': '1960-09-10'}]
combo = tmpl.render(description='Actors',people=actors)
print(combo)

Inheritance

Suppose we have the following three templates:

The first is a general template for the whole site. Here's what it looks like:

<!doctype html>
<html lang='en'>
<head>
    <meta charset='utf-8'>
    <meta name=author content="Scott D. Anderson">
    <title>{{title}}</title>
    {% block headstuff %}{% endblock %}
</head>
<body>

{% block content %}

<h1>{{title}}</h1>

<article>Lorem ipsum ...</article>

{% endblock %}

<footer>
    {% block footer %}
    Default Copyright notices and stuff
    {% endblock %}
</footer>

</body>
</html>

It can be rendered in its own right:

    from jinja2 import Environment, FileSystemLoader
>>> env = Environment(loader=FileSystemLoader('./templates'))
>>> base = env.get_template('base.html')
>>> print(base.render(title='Syllabus'))
<!doctype html>
<html lang='en'>
<head>
    <meta charset='utf-8'>
    <meta name=author content="Scott D. Anderson">
    <title>Syllabus</title>
    
</head>
<body>

<h1>Syllabus</h1>

<article>Lorem ipsum ...</article>

<footer>
    
    Default Copyright notices and stuff
    
</footer>

</body>
</html>
>>> 

More importantly, the named blocks can be overridden in the two child templates. Here's the first one:

{% extends "base.html" %}

{# additional stuff for the head #}
{% block headstuff %}
<style> .optional { color: gray; }</style>
{% endblock %}

{# replaces default content block #}
{% block content %}
<h1>Reading on {{title}}</h1>

<p>Please read the following before class on {{ classdate }}

<article>Lorem ipsum ...</article>
{% endblock %}

{# replaces default footer #}
{% block footer %}
&copy; 2016 Scott D. Anderson and the CS 304 staff
{% endblock %}


Now there's an additional tag, namely classdate:

>>> reading = env.get_template('reading.html')
>>> print(reading.render(title='Arrrr, MySQL',classdate='September 19, 2017'))
<!doctype html>
<html lang='en'>
<head>
    <meta charset='utf-8'>
    <meta name=author content="Scott D. Anderson">
    <title>Arrrr, MySQL</title>
    
<style> .optional { color: gray; }</style>

</head>
<body>


<h1>Reading on Arrrr, MySQL</h1>

<p>Please read the following before class on September 19, 2017

<article>Lorem ipsum ...</article>


<footer>
    
&copy; 2016 Scott D. Anderson and the CS 304 staff

</footer>

</body>
</html>

More Information

We'll look at some examples in the template designer documentation.

Quiz Questions

questions.

Deferring HTML

Note that we will have to change the way we code our core functionality: staying with Python data structures (which are more flexible anyhow), and allowing Jinja2 and our template language to convert those data structures to HTML. For example, here's actorNames3.py:

#!/usr/local/bin/python2.7

'''Lists all the actors in the database

This is written with less HTML, allowing that to be supplied by the Jinja2
templating engine

Written Fall 2016
Scott D. Anderson
'''

import sys
import MySQLdb
import dbconn2

# ================================================================
# The functions that do most of the work.  
    
def getList(conn):
    '''Returns a list of actor info (names and birthdates)'''
    curs = conn.cursor(MySQLdb.cursors.DictCursor) # results as Dictionaries
    curs.execute('select name,birthdate from person')
    return curs.fetchall()

def getNames(conn):
    '''Returns a string of LI elements, listing all actors and birthdates'''
    rows = getList(conn)
    lines = [ u'<li>{name} born on {birthdate}</li>'.format(**row)
             for row in rows ]
    return "\n".join(lines)

def getConn():
    dsn = dbconn2.read_cnf('/home/cs304/.my.cnf')
    dsn['db'] = 'wmdb'     # the database we want to connect to
    return dbconn2.connect(dsn)

def main():
    '''Returns an HTML listing all actors and birthdates'''
    conn = getConn()
    actorlist = getNames(conn)
    return actorlist

# ================================================================
# This starts the ball rolling, *if* the script is run as a script,
# rather than just being imported.    

if __name__ == '__main__':
    print main()

Here's how we might use it:

conn = actorNames3.getConn()
info = actorNames3.getList(conn)

Exercise: Using a Template

Put this in practice, by creating a template .html file and nicely formatting a list of actors pulled from the database.

  1. Copy ~cs304/pub/python/actorNames3.py to your own cgi-bin directory
  2. Copy ~cs304/pub/python/actorNames2.cgi to your own directory, naming it actorNames3.cgi. Note the 2 and the cgi!
  3. Create an HTML template file. You can base it on some of the ones above.
  4. Adapt the CGI script to use Jinja2 and your template.

Your solution might look like this:

#!/usr/local/bin/python2.7

import sys

import cgi
import cgitb; cgitb.enable()
import cgi_utils_sda
import actorNames3
from jinja2 import Environment, FileSystemLoader

if __name__ == '__main__':
    print 'Content-type: text/html\n'

    conn = actorNames3.getConn()
    info = actorNames3.getList(conn)
    env = Environment(loader=FileSystemLoader('../lectures/jinja2/templates/'))
    tmpl = env.get_template('actorlist.html')
    page = tmpl.render(title='Actors',
                       people=info)
    print page

actorlist.html

Summary

[an error occurred while processing this directive]