Templating
Most WSGI applications are responding to HTTP requests to serve content in HTMLor other markup languages. Instead of directly generating textual content fromPython, the concept of separation of concerns advises us to use templates. Atemplate engine manages a suite of template files, with a system of hierarchyand inclusion to avoid unnecessary repetition, and is in charge of rendering(generating) the actual content, filling the static content of the templateswith the dynamic content generated by the application.
As template files aresometimes written by designers or front-end developers, it can be difficult tohandle increasing complexity.
Some general good practices apply to the part of the application passingdynamic content to the template engine, and to the templates themselves.
- Template files should be passed only the dynamiccontent that is needed for rendering the template. Avoidthe temptation to pass additional content “just in case”:it is easier to add some missing variable when needed than to removea likely unused variable later.
- Many template engines allow for complex statementsor assignments in the template itself, and manyallow some Python code to be evaluated in thetemplates. This convenience can lead to uncontrolledincrease in complexity, and often make it harder to find bugs.
- It is often necessary to mix JavaScript templates withHTML templates. A sane approach to this design is to isolatethe parts where the HTML template passes some variable contentto the JavaScript code.
Jinja2
Jinja2 is a very well-regarded template engine.
It uses a text-based template language and can thus be used to generate anytype of markup, not just HTML. It allows customization of filters, tags, tests,and globals. It features many improvements over Django’s templating system.
Here some important HTML tags in Jinja2:
- {# This is a comment #}
- {# The next tag is a variable output: #}
- {{title}}
- {# Tag for a block, can be replaced through inheritance with other html code #}
- {% block head %}
- <h1>This is the head!</h1>
- {% endblock %}
- {# Output of an array as an iteration #}
- {% for item in list %}
- <li>{{ item }}</li>
- {% endfor %}
The next listings are an example of a web site in combination with the Tornadoweb server. Tornado is not very complicated to use.
- # import Jinja2
- from jinja2 import Environment, FileSystemLoader
- # import Tornado
- import tornado.ioloop
- import tornado.web
- # Load template file templates/site.html
- TEMPLATE_FILE = "site.html"
- templateLoader = FileSystemLoader( searchpath="templates/" )
- templateEnv = Environment( loader=templateLoader )
- template = templateEnv.get_template(TEMPLATE_FILE)
- # List for famous movie rendering
- movie_list = [[1,"The Hitchhiker's Guide to the Galaxy"],[2,"Back to future"],[3,"Matrix"]]
- # template.render() returns a string which contains the rendered html
- html_output = template.render(list=movie_list,
- title="Here is my favorite movie list")
- # Handler for main page
- class MainHandler(tornado.web.RequestHandler):
- def get(self):
- # Returns rendered template string to the browser request
- self.write(html_output)
- # Assign handler to the server root (127.0.0.1:PORT/)
- application = tornado.web.Application([
- (r"/", MainHandler),
- ])
- PORT=8884
- if __name__ == "__main__":
- # Setup the server
- application.listen(PORT)
- tornado.ioloop.IOLoop.instance().start()
The base.html
file can be used as base for all site pages which arefor example implemented in the content block.
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
- <html lang="en">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <link rel="stylesheet" href="style.css" />
- <title>{{title}} - My Webpage</title>
- </head>
- <body>
- <div id="content">
- {# In the next line the content from the site.html template will be added #}
- {% block content %}{% endblock %}
- </div>
- <div id="footer">
- {% block footer %}
- © Copyright 2013 by <a href="http://domain.invalid/">you</a>.
- {% endblock %}
- </div>
- </body>
The next listing is our site page (site.html
) loaded in the Pythonapp which extends base.html
. The content block is automatically setinto the corresponding block in the base.html
page.
- {% extends "base.html" %}
- {% block content %}
- <p class="important">
- <div id="content">
- <h2>{{title}}</h2>
- <p>{{ list_title }}</p>
- <ul>
- {% for item in list %}
- <li>{{ item[0]}} : {{ item[1]}}</li>
- {% endfor %}
- </ul>
- </div>
- </p>
- {% endblock %}
Jinja2 is the recommended templating library for new Python web applications.
Chameleon
Chameleon Page Templates are an HTML/XML templateengine implementation of the Template Attribute Language (TAL),TAL Expression Syntax (TALES),and Macro Expansion TAL (Metal) syntaxes.
Chameleon is available for Python 2.5 and up (including 3.x and PyPy), andis commonly used by the Pyramid Framework.
Page Templates add within your document structure special element attributesand text markup. Using a set of simple language constructs, you control thedocument flow, element repetition, text replacement, and translation. Becauseof the attribute-based syntax, unrendered page templates are valid HTML and canbe viewed in a browser and even edited in WYSIWYG editors. This can makeround-trip collaboration with designers and prototyping with static files in abrowser easier.
The basic TAL language is simple enough to grasp from an example:
- <html>
- <body>
- <h1>Hello, <span tal:replace="context.name">World</span>!</h1>
- <table>
- <tr tal:repeat="row 'apple', 'banana', 'pineapple'">
- <td tal:repeat="col 'juice', 'muffin', 'pie'">
- <span tal:replace="row.capitalize()" /> <span tal:replace="col" />
- </td>
- </tr>
- </table>
- </body>
- </html>
The <span tal:replace=”expression” /> pattern for text insertion is commonenough that if you do not require strict validity in your unrendered templates,you can replace it with a more terse and readable syntax that uses the pattern${expression}, as follows:
- <html>
- <body>
- <h1>Hello, ${world}!</h1>
- <table>
- <tr tal:repeat="row 'apple', 'banana', 'pineapple'">
- <td tal:repeat="col 'juice', 'muffin', 'pie'">
- ${row.capitalize()} ${col}
- </td>
- </tr>
- </table>
- </body>
- </html>
But keep in mind that the full _<span tal:replace=”expression”>Default Text</span>_syntax also allows for default content in the unrendered template.
Being from the Pyramid world, Chameleon is not widely used.
Mako
Mako is a template language that compiles to Pythonfor maximum performance. Its syntax and API are borrowed from the best parts of othertemplating languages like Django and Jinja2 templates. It is the default templatelanguage included with the Pylons and Pyramid webframeworks.
An example template in Mako looks like:
- <%inherit file="base.html"/>
- <%
- rows = [[v for v in range(0,10)] for row in range(0,10)]
- %>
- <table>
- % for row in rows:
- ${makerow(row)}
- % endfor
- </table>
- <%def name="makerow(row)">
- <tr>
- % for name in row:
- <td>${name}</td>\
- % endfor
- </tr>
- </%def>
To render a very basic template, you can do the following:
- from mako.template import Template
- print(Template("hello ${data}!").render(data="world"))
Mako is well respected within the Python web community.
References
[1] | Benchmark of Python WSGI Servers |