Today, we'll conclude Python/CGI scripts, and look at using the Jinja2
templating module.
By the end of today, you will be able to use the Jinja2 templating module with your database scripts.
Old material
New material
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:
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.
Jinja2 has a variety of loaders. With Flask, we'll create a Python
package and Flask will use the PackageLoader
.
Now let's turn to the template language
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'
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)
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 %} © 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> © 2016 Scott D. Anderson and the CS 304 staff </footer> </body> </html>
We'll look at some examples in the template designer documentation.
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)
Put this in practice, by creating a template .html file and nicely formatting a list of actors pulled from the database.
~cs304/pub/python/actorNames3.py
to your
own cgi-bin
directory~cs304/pub/python/actorNames2.cgi
to your
own directory, naming it actorNames3.cgi
. Note
the 2 and the cgi!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