Tornado includes a simple, fast, and flexible templating language.This section describes that language as well as related issuessuch as internationalization.
Tornado can also be used with any other Python template language,although there is no provision for integrating these systems intoRequestHandler.render. Simply render the template to a stringand pass it to RequestHandler.write
Configuring templates¶
By default, Tornado looks for template files in the same directory asthe .py
files that refer to them. To put your template files in adifferent directory, use the template_path
Application setting (or override RequestHandler.get_template_pathif you have different template paths for different handlers).
To load templates from a non-filesystem location, subclasstornado.template.BaseLoader and pass an instance as thetemplate_loader
application setting.
Compiled templates are cached by default; to turn off this cachingand reload templates so changes to the underlying files are alwaysvisible, use the application settings compiled_template_cache=False
or debug=True
.
Template syntax¶
A Tornado template is just HTML (or any other text-based format) withPython control sequences and expressions embedded within the markup:
<html> <head> <title>{{ title }}</title> </head> <body> <ul> {% for item in items %} <li>{{ escape(item) }}</li> {% end %} </ul> </body> </html>
If you saved this template as “template.html” and put it in the samedirectory as your Python file, you could render this template with:
class MainHandler(tornado.web.RequestHandler): def get(self): items = ["Item 1", "Item 2", "Item 3"] self.render("template.html", title="My title", items=items)
Tornado templates support control statements and expressions.Control statements are surrounded by {%
and %}
, e.g.{% if len(items) > 2 %}
. Expressions are surrounded by {{
and}}
, e.g. {{ items[0] }}
.
Control statements more or less map exactly to Python statements. Wesupport if
, for
, while
, and try
, all of which areterminated with {% end %}
. We also support template inheritanceusing the extends
and block
statements, which are described indetail in the documentation for the tornado.template.
Expressions can be any Python expression, including function calls.Template code is executed in a namespace that includes the followingobjects and functions. (Note that this list applies to templatesrendered using RequestHandler.render andrender_string. If you’re using thetornado.template module directly outside of a RequestHandler manyof these entries are not present).
escape
: alias for tornado.escape.xhtml_escapexhtml_escape
: alias for tornado.escape.xhtml_escapeurl_escape
: alias for tornado.escape.url_escapejson_encode
: alias for tornado.escape.json_encodesqueeze
: alias for tornado.escape.squeezelinkify
: alias for tornado.escape.linkifydatetime
: the Pythondatetime
modulehandler
: the current RequestHandler objectrequest
: alias for handler.requestcurrent_user
: alias for handler.current_userlocale
: alias for handler.locale_
: alias for handler.locale.translatestatic_url
: alias for handler.static_urlxsrf_form_html
: alias for handler.xsrf_form_htmlreverse_url
: alias for Application.reverse_urlAll entries from the
ui_methods
andui_modules
Application
settingsAny keyword arguments passed to render orrender_string
When you are building a real application, you are going to want to useall of the features of Tornado templates, especially templateinheritance. Read all about those features in the tornado.templatesection (some features, including UIModules
are implemented in thetornado.web module)
Under the hood, Tornado templates are translated directly to Python. Theexpressions you include in your template are copied verbatim into aPython function representing your template. We don’t try to preventanything in the template language; we created it explicitly to providethe flexibility that other, stricter templating systems prevent.Consequently, if you write random stuff inside of your templateexpressions, you will get random Python errors when you execute thetemplate.
All template output is escaped by default, using thetornado.escape.xhtml_escape function. This behavior can be changedglobally by passing autoescape=None
to the Application ortornado.template.Loader constructors, for a template file with the{% autoescape None %}
directive, or for a single expression byreplacing {{ ... }}
with {% raw ...%}
. Additionally, in each ofthese places the name of an alternative escaping function may be usedinstead of None
.
Note that while Tornado’s automatic escaping is helpful in avoidingXSS vulnerabilities, it is not sufficient in all cases. Expressionsthat appear in certain locations, such as in JavaScript or CSS, may needadditional escaping. Additionally, either care must be taken to alwaysuse double quotes and xhtml_escape in HTML attributes that may containuntrusted content, or a separate escaping function must be used forattributes (see e.g.this blog post).
Internationalization¶
The locale of the current user (whether they are logged in or not) isalways available as self.locale
in the request handler and aslocale
in templates. The name of the locale (e.g., en_US
) isavailable as locale.name
, and you can translate strings with theLocale.translate method. Templates also have the global functioncall _()
available for string translation. The translate functionhas two forms:
_("Translate this string")
which translates the string directly based on the current locale, and:
_("A person liked this", "%(num)d people liked this", len(people)) % {"num": len(people)}
which translates a string that can be singular or plural based on thevalue of the third argument. In the example above, a translation of thefirst string will be returned if len(people)
is 1
, or atranslation of the second string will be returned otherwise.
The most common pattern for translations is to use Python namedplaceholders for variables (the %(num)d
in the example above) sinceplaceholders can move around on translation.
Here is a properly internationalized template:
<html> <head> <title>FriendFeed - {{ _("Sign in") }}</title> </head> <body> <form action="{{ request.path }}" method="post"> <div>{{ _("Username") }} <input type="text" name="username"/></div> <div>{{ _("Password") }} <input type="password" name="password"/></div> <div><input type="submit" value="{{ _("Sign in") }}"/></div> {% module xsrf_form_html() %} </form> </body> </html>
By default, we detect the user’s locale using the Accept-Language
header sent by the user’s browser. We choose en_US
if we can’t findan appropriate Accept-Language
value. If you let user’s set theirlocale as a preference, you can override this default locale selectionby overriding RequestHandler.get_user_locale:
class BaseHandler(tornado.web.RequestHandler): def get_current_user(self): user_id = self.get_signed_cookie("user") if not user_id: return None return self.backend.get_user_by_id(user_id) def get_user_locale(self): if "locale" not in self.current_user.prefs: # Use the Accept-Language header return None return self.current_user.prefs["locale"]
If get_user_locale
returns None
, we fall back on theAccept-Language
header.
The tornado.locale module supports loading translations in twoformats: the .mo
format used by gettext
and related tools, and asimple .csv
format. An application will generally call eithertornado.locale.load_translations ortornado.locale.load_gettext_translations once at startup; see thosemethods for more details on the supported formats.
You can get the list of supported locales in your application withtornado.locale.get_supported_locales(). The user’s locale is chosento be the closest match based on the supported locales. For example, ifthe user’s locale is es_GT
, and the es
locale is supported,self.locale
will be es
for that request. We fall back onen_US
if no close match can be found.
UI modules¶
Tornado supports UI modules to make it easy to support standard,reusable UI widgets across your application. UI modules are like specialfunction calls to render components of your page, and they can comepackaged with their own CSS and JavaScript.
For example, if you are implementing a blog, and you want to have blogentries appear on both the blog home page and on each blog entry page,you can make an Entry
module to render them on both pages. First,create a Python module for your UI modules, e.g. uimodules.py
:
class Entry(tornado.web.UIModule): def render(self, entry, show_comments=False): return self.render_string( "module-entry.html", entry=entry, show_comments=show_comments)
Tell Tornado to use uimodules.py
using the ui_modules
setting inyour application:
from . import uimodulesclass HomeHandler(tornado.web.RequestHandler): def get(self): entries = self.db.query("SELECT * FROM entries ORDER BY date DESC") self.render("home.html", entries=entries)class EntryHandler(tornado.web.RequestHandler): def get(self, entry_id): entry = self.db.get("SELECT * FROM entries WHERE id = %s", entry_id) if not entry: raise tornado.web.HTTPError(404) self.render("entry.html", entry=entry)settings = { "ui_modules": uimodules,}application = tornado.web.Application([ (r"/", HomeHandler), (r"/entry/([0-9]+)", EntryHandler),], **settings)
Within a template, you can call a module with the {% module %}
statement. For example, you could call the Entry
module from bothhome.html
:
{% for entry in entries %} {% module Entry(entry) %}{% end %}
and entry.html
:
{% module Entry(entry, show_comments=True) %}
Modules can include custom CSS and JavaScript functions by overridingthe embedded_css
, embedded_javascript
, javascript_files
, orcss_files
methods:
class Entry(tornado.web.UIModule): def embedded_css(self): return ".entry { margin-bottom: 1em; }" def render(self, entry, show_comments=False): return self.render_string( "module-entry.html", show_comments=show_comments)
Module CSS and JavaScript will be included once no matter how many timesa module is used on a page. CSS is always included in the <head>
ofthe page, and JavaScript is always included just before the </body>
tag at the end of the page.
When additional Python code is not required, a template file itself maybe used as a module. For example, the preceding example could berewritten to put the following in module-entry.html
:
{{ set_resources(embedded_css=".entry { margin-bottom: 1em; }") }}<!-- more template html... -->
This revised template module would be invoked with:
{% module Template("module-entry.html", show_comments=True) %}
The set_resources
function is only available in templates invokedvia {% module Template(...) %}
. Unlike the {% include ... %}
directive, template modules have a distinct namespace from theircontaining template - they can only see the global template namespaceand their own keyword arguments.