Templates
Complete guide to Tornado's template engine with copy-paste ready examples for rendering HTML, template inheritance, and custom filters.
Basic Template Rendering
Simple Template Rendering
import tornado.web
import os
class TemplateHandler(tornado.web.RequestHandler):
def get(self):
# Render template with data
self.render("index.html",
title="Welcome",
message="Hello, Tornado!")
def post(self):
# Render template with form data
name = self.get_body_argument("name", default="Guest")
self.render("greeting.html",
name=name,
timestamp=datetime.datetime.now())
# Application setup with template path
def make_app():
settings = {
"template_path": os.path.join(os.path.dirname(__file__), "templates"),
"static_path": os.path.join(os.path.dirname(__file__), "static"),
"debug": True,
"compiled_template_cache": False, # Disable caching in development
}
return tornado.web.Application([
(r"/", TemplateHandler),
], **settings)
Template with Context Data
class DataHandler(tornado.web.RequestHandler):
def get(self):
# Complex data structures
users = [
{"id": 1, "name": "John", "email": "john@example.com"},
{"id": 2, "name": "Jane", "email": "jane@example.com"},
]
config = {
"app_name": "My App",
"version": "1.0.0",
"debug": True
}
self.render("users.html",
users=users,
config=config,
current_user=self.current_user,
page_title="User List")
Render String (Template from String)
class StringTemplateHandler(tornado.web.RequestHandler):
def get(self):
template_string = """
<html>
<head><title>{{ title }}</title></head>
<body>
<h1>{{ heading }}</h1>
<p>{{ message }}</p>
</body>
</html>
"""
rendered = tornado.template.Template(template_string).generate(
title="Dynamic Template",
heading="Generated Content",
message="This template was created from a string!"
)
self.write(rendered)
Template Syntax
Basic HTML Template
<!-- templates/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<header>
<h1>{{ title }}</h1>
</header>
<main>
<p>{{ message }}</p>
<p>Current time: {{ datetime.datetime.now() }}</p>
</main>
<footer>
<p>© {{ datetime.datetime.now().year }} My App</p>
</footer>
</body>
</html>
Variables and Expressions
<!-- templates/variables.html -->
<div>
<!-- Simple variable -->
<p>Name: {{ name }}</p>
<!-- Expression -->
<p>Total: {{ price * quantity }}</p>
<!-- Method calls -->
<p>Upper: {{ name.upper() }}</p>
<p>Length: {{ len(items) }}</p>
<!-- Attribute access -->
<p>User: {{ user.name }}</p>
<p>Email: {{ user.email }}</p>
<!-- Dictionary access -->
<p>Setting: {{ config["debug"] }}</p>
<p>Value: {{ data.get("key", "default") }}</p>
<!-- List access -->
<p>First item: {{ items[0] }}</p>
<p>Last item: {{ items[-1] }}</p>
</div>
Conditional Statements
<!-- templates/conditionals.html -->
<div>
<!-- Simple if -->
{% if user %}
<p>Welcome, {{ user.name }}!</p>
{% end %}
<!-- If-else -->
{% if user %}
<p>Hello, {{ user.name }}</p>
{% else %}
<p>Please log in</p>
{% end %}
<!-- If-elif-else -->
{% if user.role == "admin" %}
<p>Admin Panel</p>
{% elif user.role == "moderator" %}
<p>Moderator Tools</p>
{% else %}
<p>User Dashboard</p>
{% end %}
<!-- Complex conditions -->
{% if user and user.is_active and user.permissions %}
<p>Full access granted</p>
{% end %}
<!-- Check for existence -->
{% if hasattr(user, "profile") %}
<p>Profile: {{ user.profile.bio }}</p>
{% end %}
</div>
Loops
<!-- templates/loops.html -->
<div>
<!-- Simple loop -->
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% end %}
</ul>
<!-- Loop with index -->
<ol>
{% for i, item in enumerate(items) %}
<li>{{ i + 1 }}. {{ item }}</li>
{% end %}
</ol>
<!-- Loop over dictionary -->
<dl>
{% for key, value in config.items() %}
<dt>{{ key }}</dt>
<dd>{{ value }}</dd>
{% end %}
</dl>
<!-- Loop with conditions -->
<ul>
{% for user in users %} {% if user.is_active %}
<li class="active">{{ user.name }}</li>
{% else %}
<li class="inactive">{{ user.name }} (inactive)</li>
{% end %} {% end %}
</ul>
<!-- Empty loop handling -->
{% if users %}
<ul>
{% for user in users %}
<li>{{ user.name }}</li>
{% end %}
</ul>
{% else %}
<p>No users found</p>
{% end %}
</div>
Template Inheritance
Base Template
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}My App{% end %}</title>
<!-- Default styles -->
<link rel="stylesheet" href="{{ static_url('css/base.css') }}" />
<!-- Additional styles -->
{% block styles %}{% end %}
</head>
<body>
<nav class="navbar">
<div class="container">
<a href="/" class="logo">My App</a>
<ul class="nav-links">
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
{% if current_user %}
<li><a href="/dashboard">Dashboard</a></li>
<li><a href="/logout">Logout</a></li>
{% else %}
<li><a href="/login">Login</a></li>
{% end %}
</ul>
</div>
</nav>
<main class="main-content">
<div class="container">
<!-- Flash messages -->
{% block messages %} {% if messages %} {% for message in messages %}
<div class="alert alert-{{ message.type }}">{{ message.text }}</div>
{% end %} {% end %} {% end %}
<!-- Page content -->
{% block content %}{% end %}
</div>
</main>
<footer class="footer">
<div class="container">
<p>
© {{ datetime.datetime.now().year }} My App. All rights reserved.
</p>
</div>
</footer>
<!-- Default scripts -->
<script src="{{ static_url('js/base.js') }}"></script>
<!-- Additional scripts -->
{% block scripts %}{% end %}
</body>
</html>
Child Template
<!-- templates/home.html -->
{% extends "base.html" %} {% block title %}Home - My App{% end %} {% block
styles %}
<link rel="stylesheet" href="{{ static_url('css/home.css') }}" />
{% end %} {% block content %}
<section class="hero">
<h1>Welcome to My App</h1>
<p>{{ welcome_message }}</p>
{% if current_user %}
<p>Hello, {{ current_user.name }}!</p>
<a href="/dashboard" class="btn btn-primary">Go to Dashboard</a>
{% else %}
<a href="/login" class="btn btn-primary">Get Started</a>
{% end %}
</section>
<section class="features">
<h2>Features</h2>
<div class="feature-grid">
{% for feature in features %}
<div class="feature-card">
<h3>{{ feature.title }}</h3>
<p>{{ feature.description }}</p>
</div>
{% end %}
</div>
</section>
{% end %} {% block scripts %}
<script src="{{ static_url('js/home.js') }}"></script>
{% end %}
Multi-level Inheritance
<!-- templates/admin/base.html -->
{% extends "../base.html" %} {% block title %}Admin - {% block admin_title
%}Dashboard{% end %}{% end %} {% block styles %} {{ super() }}
<link rel="stylesheet" href="{{ static_url('css/admin.css') }}" />
{% end %} {% block content %}
<div class="admin-layout">
<aside class="admin-sidebar">
<nav class="admin-nav">
<ul>
<li><a href="/admin">Dashboard</a></li>
<li><a href="/admin/users">Users</a></li>
<li><a href="/admin/settings">Settings</a></li>
</ul>
</nav>
</aside>
<main class="admin-content">
<h1>{% block admin_title %}Dashboard{% end %}</h1>
{% block admin_content %}{% end %}
</main>
</div>
{% end %}
<!-- templates/admin/users.html -->
{% extends "admin/base.html" %} {% block admin_title %}User Management{% end %}
{% block admin_content %}
<div class="users-table">
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
<td>{{ "Active" if user.is_active else "Inactive" }}</td>
<td>
<a href="/admin/users/{{ user.id }}">Edit</a>
<a href="/admin/users/{{ user.id }}/delete">Delete</a>
</td>
</tr>
{% end %}
</tbody>
</table>
</div>
{% end %}
Template Functions and Filters
Built-in Functions
<!-- templates/functions.html -->
<div>
<!-- URL generation -->
<a href="{{ reverse_url('user_profile', user.id) }}">Profile</a>
<!-- Static file URLs -->
<img src="{{ static_url('img/logo.png') }}" alt="Logo" />
<script src="{{ static_url('js/app.js') }}"></script>
<!-- XSRF token -->
<form method="post">
{% module xsrf_form_html() %}
<input type="text" name="username" />
<button type="submit">Submit</button>
</form>
<!-- Date formatting -->
<p>Created: {{ datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") }}</p>
<!-- JSON encoding -->
<script>
var data = {{ json_encode(data) }};
</script>
</div>
Custom Functions
# In your handler or application
import tornado.web
def format_currency(amount):
return f"${amount:.2f}"
def truncate_text(text, length=100):
if len(text) <= length:
return text
return text[:length] + "..."
class CustomTemplateHandler(tornado.web.RequestHandler):
def get(self):
self.render("custom_functions.html",
price=29.99,
description="This is a very long description that needs to be truncated",
format_currency=format_currency,
truncate_text=truncate_text)
<!-- templates/custom_functions.html -->
<div>
<p>Price: {{ format_currency(price) }}</p>
<p>Description: {{ truncate_text(description, 50) }}</p>
</div>
Template Modules
# ui_modules.py
import tornado.web
class MessageModule(tornado.web.UIModule):
def render(self, message_type, message_text):
return self.render_string(
"modules/message.html",
message_type=message_type,
message_text=message_text
)
class UserCardModule(tornado.web.UIModule):
def render(self, user):
return self.render_string(
"modules/user_card.html",
user=user
)
def css_files(self):
return ["css/user_card.css"]
def javascript_files(self):
return ["js/user_card.js"]
# In your application
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
], ui_modules={"Message": MessageModule, "UserCard": UserCardModule})
<!-- templates/modules/message.html -->
<div class="alert alert-{{ message_type }}">
<p>{{ message_text }}</p>
</div>
<!-- templates/modules/user_card.html -->
<div class="user-card">
<img src="{{ user.avatar_url }}" alt="{{ user.name }}" />
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<p>Member since: {{ user.created_at.strftime("%Y-%m-%d") }}</p>
</div>
<!-- templates/users.html -->
{% extends "base.html" %} {% block content %} {% module Message("success",
"Users loaded successfully") %}
<div class="user-grid">
{% for user in users %} {% module UserCard(user) %} {% end %}
</div>
{% end %}
Forms and CSRF Protection
Basic Form Template
<!-- templates/forms/contact.html -->
{% extends "base.html" %} {% block content %}
<form method="post" action="/contact">
{% module xsrf_form_html() %}
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required />
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required />
</div>
<div class="form-group">
<label for="message">Message:</label>
<textarea id="message" name="message" rows="5" required></textarea>
</div>
<button type="submit">Send Message</button>
</form>
{% end %}
Form with Validation Errors
<!-- templates/forms/user_form.html -->
{% extends "base.html" %} {% block content %} {% if errors %}
<div class="errors">
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% end %}
</ul>
</div>
{% end %}
<form method="post">
{% module xsrf_form_html() %}
<div class="form-group">
<label for="username">Username:</label>
<input
type="text"
id="username"
name="username"
value="{{ username or '' }}"
required
/>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input
type="email"
id="email"
name="email"
value="{{ email or '' }}"
required
/>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password" required />
</div>
<button type="submit">{{ "Update" if user else "Create" }} User</button>
</form>
{% end %}
Advanced Template Patterns
Template with Include
<!-- templates/includes/header.html -->
<header class="site-header">
<nav class="navbar">
<div class="container">
<a href="/" class="logo">My App</a>
<ul class="nav-links">
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
{% if current_user %}
<li><a href="/dashboard">Dashboard</a></li>
{% end %}
</ul>
</div>
</nav>
</header>
<!-- templates/page.html -->
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
{% include "includes/header.html" %}
<main>{{ content }}</main>
{% include "includes/footer.html" %}
</body>
</html>
Template with Macros
<!-- templates/macros/forms.html -->
{% macro form_field(field_type, name, label, value="", required=False) %}
<div class="form-group">
<label for="{{ name }}">{{ label }}:</label>
<input
type="{{ field_type }}"
id="{{ name }}"
name="{{ name }}"
value="{{ value }}"
{%
if
required
%}required{%
end
%}
/>
</div>
{% end %} {% macro submit_button(text="Submit", class="btn-primary") %}
<button type="submit" class="btn {{ class }}">{{ text }}</button>
{% end %}
<!-- templates/registration.html -->
{% extends "base.html" %} {% from "macros/forms.html" import form_field,
submit_button %} {% block content %}
<form method="post">
{% module xsrf_form_html() %} {{ form_field("text", "username", "Username",
required=True) }} {{ form_field("email", "email", "Email", required=True) }}
{{ form_field("password", "password", "Password", required=True) }} {{
submit_button("Register") }}
</form>
{% end %}
Template with Raw Content
<!-- templates/code_example.html -->
<div class="code-block">
<h3>JavaScript Example</h3>
<!-- Raw content (no template processing) -->
{% raw %}
<script>
function greet(name) {
console.log(`Hello, ${name}!`);
}
// This {{ variable }} won't be processed
var template = 'Hello, {{ name }}!';
</script>
{% end raw %}
</div>
Template Configuration
Template Loader Configuration
import tornado.template
import tornado.web
import os
class CustomTemplateLoader(tornado.template.Loader):
def __init__(self, root_directory, **kwargs):
super().__init__(root_directory, **kwargs)
def load(self, name, parent_path=None):
# Custom loading logic
if name.startswith("admin/"):
# Load admin templates from different path
name = name.replace("admin/", "")
return super().load(name, parent_path="admin_templates")
return super().load(name, parent_path)
def make_app():
# Custom template loader
template_loader = CustomTemplateLoader("templates")
settings = {
"template_loader": template_loader,
"debug": True,
"compiled_template_cache": False,
"static_path": "static",
}
return tornado.web.Application([
(r"/", MainHandler),
], **settings)
Template with Custom Filters
import tornado.template
# Custom filter function
def format_date(date_obj, format_str="%Y-%m-%d"):
return date_obj.strftime(format_str)
# Add to template namespace
tornado.template.Loader.defaults = {
"format_date": format_date,
}
class TemplateHandler(tornado.web.RequestHandler):
def get(self):
import datetime
self.render("date_template.html",
created_date=datetime.datetime.now())
<!-- templates/date_template.html -->
<div>
<p>Created: {{ format_date(created_date) }}</p>
<p>Formatted: {{ format_date(created_date, "%B %d, %Y") }}</p>
</div>
This comprehensive guide covers all aspects of Tornado's template system, from basic rendering to advanced inheritance patterns and custom functionality.