Templates and UI ================ .. testsetup:: import tornado.web Tornado includes a simple, fast, and flexible templating language. This section describes that language as well as related issues such as internationalization. Tornado can also be used with any other Python template language, although there is no provision for integrating these systems into `.RequestHandler.render`. Simply render the template to a string and pass it to `.RequestHandler.write` Configuring templates ~~~~~~~~~~~~~~~~~~~~~ By default, Tornado looks for template files in the same directory as the ``.py`` files that refer to them. To put your template files in a different directory, use the ``template_path`` `Application setting <.Application.settings>` (or override `.RequestHandler.get_template_path` if you have different template paths for different handlers). To load templates from a non-filesystem location, subclass `tornado.template.BaseLoader` and pass an instance as the ``template_loader`` application setting. Compiled templates are cached by default; to turn off this caching and reload templates so changes to the underlying files are always visible, 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) with Python control sequences and expressions embedded within the markup:: {{ title }} If you saved this template as "template.html" and put it in the same directory as your Python file, you could render this template with: .. testcode:: class MainHandler(tornado.web.RequestHandler): def get(self): items = ["Item 1", "Item 2", "Item 3"] self.render("template.html", title="My title", items=items) .. testoutput:: :hide: 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. We support ``if``, ``for``, ``while``, and ``try``, all of which are terminated with ``{% end %}``. We also support *template inheritance* using the ``extends`` and ``block`` statements, which are described in detail 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 following objects and functions (Note that this list applies to templates rendered using `.RequestHandler.render` and `~.RequestHandler.render_string`. If you're using the `tornado.template` module directly outside of a `.RequestHandler` many of these entries are not present). - ``escape``: alias for `tornado.escape.xhtml_escape` - ``xhtml_escape``: alias for `tornado.escape.xhtml_escape` - ``url_escape``: alias for `tornado.escape.url_escape` - ``json_encode``: alias for `tornado.escape.json_encode` - ``squeeze``: alias for `tornado.escape.squeeze` - ``linkify``: alias for `tornado.escape.linkify` - ``datetime``: the Python `datetime` module - ``handler``: the current `.RequestHandler` object - ``request``: alias for `handler.request <.HTTPServerRequest>` - ``current_user``: alias for `handler.current_user <.RequestHandler.current_user>` - ``locale``: alias for `handler.locale <.Locale>` - ``_``: alias for `handler.locale.translate <.Locale.translate>` - ``static_url``: alias for `handler.static_url <.RequestHandler.static_url>` - ``xsrf_form_html``: alias for `handler.xsrf_form_html <.RequestHandler.xsrf_form_html>` - ``reverse_url``: alias for `.Application.reverse_url` - All entries from the ``ui_methods`` and ``ui_modules`` ``Application`` settings - Any keyword arguments passed to `~.RequestHandler.render` or `~.RequestHandler.render_string` When you are building a real application, you are going to want to use all of the features of Tornado templates, especially template inheritance. Read all about those features in the `tornado.template` section (some features, including ``UIModules`` are implemented in the `tornado.web` module) Under the hood, Tornado templates are translated directly to Python. The expressions you include in your template are copied verbatim into a Python function representing your template. We don't try to prevent anything in the template language; we created it explicitly to provide the flexibility that other, stricter templating systems prevent. Consequently, if you write random stuff inside of your template expressions, you will get random Python errors when you execute the template. All template output is escaped by default, using the `tornado.escape.xhtml_escape` function. This behavior can be changed globally by passing ``autoescape=None`` to the `.Application` or `.tornado.template.Loader` constructors, for a template file with the ``{% autoescape None %}`` directive, or for a single expression by replacing ``{{ ... }}`` with ``{% raw ...%}``. Additionally, in each of these places the name of an alternative escaping function may be used instead of ``None``. Note that while Tornado's automatic escaping is helpful in avoiding XSS vulnerabilities, it is not sufficient in all cases. Expressions that appear in certain locations, such as in Javascript or CSS, may need additional escaping. Additionally, either care must be taken to always use double quotes and `.xhtml_escape` in HTML attributes that may contain untrusted content, or a separate escaping function must be used for attributes (see e.g. http://wonko.com/post/html-escaping) Internationalization ~~~~~~~~~~~~~~~~~~~~ The locale of the current user (whether they are logged in or not) is always available as ``self.locale`` in the request handler and as ``locale`` in templates. The name of the locale (e.g., ``en_US``) is available as ``locale.name``, and you can translate strings with the `.Locale.translate` method. Templates also have the global function call ``_()`` available for string translation. The translate function has 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 the value of the third argument. In the example above, a translation of the first string will be returned if ``len(people)`` is ``1``, or a translation of the second string will be returned otherwise. The most common pattern for translations is to use Python named placeholders for variables (the ``%(num)d`` in the example above) since placeholders can move around on translation. Here is a properly internationalized template:: FriendFeed - {{ _("Sign in") }}
{{ _("Username") }}
{{ _("Password") }}
{% module xsrf_form_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 find an appropriate ``Accept-Language`` value. If you let user's set their locale as a preference, you can override this default locale selection by overriding `.RequestHandler.get_user_locale`: .. testcode:: class BaseHandler(tornado.web.RequestHandler): def get_current_user(self): user_id = self.get_secure_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"] .. testoutput:: :hide: If ``get_user_locale`` returns ``None``, we fall back on the ``Accept-Language`` header. The `tornado.locale` module supports loading translations in two formats: the ``.mo`` format used by `gettext` and related tools, and a simple ``.csv`` format. An application will generally call either `tornado.locale.load_translations` or `tornado.locale.load_gettext_translations` once at startup; see those methods for more details on the supported formats.. You can get the list of supported locales in your application with `tornado.locale.get_supported_locales()`. The user's locale is chosen to be the closest match based on the supported locales. For example, if the user's locale is ``es_GT``, and the ``es`` locale is supported, ``self.locale`` will be ``es`` for that request. We fall back on ``en_US`` if no close match can be found. .. _ui-modules: UI modules ~~~~~~~~~~ Tornado supports *UI modules* to make it easy to support standard, reusable UI widgets across your application. UI modules are like special function calls to render components of your page, and they can come packaged with their own CSS and JavaScript. For example, if you are implementing a blog, and you want to have blog entries 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 in your application:: from . import uimodules class 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 both ``home.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 overriding the ``embedded_css``, ``embedded_javascript``, ``javascript_files``, or ``css_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 times a module is used on a page. CSS is always included in the ```` of the page, and JavaScript is always included just before the ```` tag at the end of the page. When additional Python code is not required, a template file itself may be used as a module. For example, the preceding example could be rewritten to put the following in ``module-entry.html``:: {{ set_resources(embedded_css=".entry { margin-bottom: 1em; }") }} 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 invoked via ``{% module Template(...) %}``. Unlike the ``{% include ... %}`` directive, template modules have a distinct namespace from their containing template - they can only see the global template namespace and their own keyword arguments.