0
# Template System
1
2
Async Jinja2 template rendering with streaming support, Flask-compatible template context, and comprehensive template environment customization.
3
4
## Capabilities
5
6
### Template Rendering Functions
7
8
Async template rendering with full Jinja2 support and streaming capabilities for large templates.
9
10
```python { .api }
11
async def render_template(template_name_or_list: str | list[str], **context) -> str:
12
"""
13
Render template with given context.
14
15
Args:
16
template_name_or_list: Template name or list of template names (first found is used)
17
**context: Template context variables
18
19
Returns:
20
Rendered template as string
21
"""
22
23
async def render_template_string(source: str, **context) -> str:
24
"""
25
Render template from string source.
26
27
Args:
28
source: Template source code
29
**context: Template context variables
30
31
Returns:
32
Rendered template as string
33
"""
34
35
async def stream_template(template_name_or_list: str | list[str], **context):
36
"""
37
Stream template rendering for large templates.
38
39
Args:
40
template_name_or_list: Template name or list of template names
41
**context: Template context variables
42
43
Returns:
44
AsyncIterator yielding template chunks
45
"""
46
47
async def stream_template_string(source: str, **context):
48
"""
49
Stream template rendering from string source.
50
51
Args:
52
source: Template source code
53
**context: Template context variables
54
55
Returns:
56
AsyncIterator yielding template chunks
57
"""
58
```
59
60
### Template Environment
61
62
Quart-specific Jinja2 environment with async support and Flask-compatible context.
63
64
```python { .api }
65
class Environment:
66
"""
67
Quart-specific Jinja2 environment with async template support.
68
69
Provides Flask-compatible template context with additional async capabilities
70
and integration with Quart's request/response cycle.
71
"""
72
73
def get_template(self, name: str | list[str]):
74
"""Get template by name."""
75
76
def from_string(self, source: str):
77
"""Create template from string source."""
78
79
def select_template(self, names: list[str]):
80
"""Select first available template from list."""
81
```
82
83
### Usage Examples
84
85
#### Basic Template Rendering
86
87
```python
88
from quart import Quart, render_template, request
89
90
app = Quart(__name__)
91
92
@app.route('/')
93
async def index():
94
# Simple template rendering
95
return await render_template('index.html',
96
title='Welcome',
97
message='Hello, Quart!')
98
99
@app.route('/user/<username>')
100
async def user_profile(username):
101
# Template with dynamic data
102
user_data = await get_user_data(username)
103
return await render_template('user.html',
104
user=user_data,
105
current_user=await get_current_user())
106
107
@app.route('/dashboard')
108
async def dashboard():
109
# Template with multiple data sources
110
stats = await get_dashboard_stats()
111
recent_activity = await get_recent_activity()
112
notifications = await get_notifications()
113
114
return await render_template('dashboard.html',
115
stats=stats,
116
activity=recent_activity,
117
notifications=notifications,
118
page_title='Dashboard')
119
```
120
121
#### Template with Request Context
122
123
```python
124
from quart import Quart, render_template, request, g
125
126
app = Quart(__name__)
127
128
@app.before_request
129
async def before_request():
130
# Set up global template context
131
g.current_time = datetime.now()
132
g.user_agent = request.headers.get('User-Agent', '')
133
134
@app.context_processor
135
async def inject_template_vars():
136
# Add variables to all templates
137
return {
138
'app_name': 'My Quart App',
139
'version': '1.0.0',
140
'current_time': g.current_time,
141
'is_mobile': 'Mobile' in g.user_agent
142
}
143
144
@app.route('/page')
145
async def page():
146
# Template automatically has access to injected variables
147
return await render_template('page.html',
148
content='Page content here')
149
```
150
151
#### Template String Rendering
152
153
```python
154
from quart import Quart, render_template_string
155
156
app = Quart(__name__)
157
158
@app.route('/dynamic')
159
async def dynamic_template():
160
# Render template from string (useful for dynamic templates)
161
template_source = """
162
<h1>{{ title }}</h1>
163
<p>Current time: {{ current_time }}</p>
164
{% for item in items %}
165
<li>{{ item.name }}: {{ item.value }}</li>
166
{% endfor %}
167
"""
168
169
return await render_template_string(
170
template_source,
171
title='Dynamic Template',
172
current_time=datetime.now(),
173
items=[
174
{'name': 'Item 1', 'value': 'Value 1'},
175
{'name': 'Item 2', 'value': 'Value 2'}
176
]
177
)
178
179
@app.route('/email/preview')
180
async def email_preview():
181
# Generate email template preview
182
email_template = await get_email_template('welcome')
183
user_data = await get_sample_user_data()
184
185
return await render_template_string(
186
email_template.content,
187
user=user_data,
188
company_name='My Company',
189
unsubscribe_url='https://example.com/unsubscribe'
190
)
191
```
192
193
#### Streaming Templates
194
195
```python
196
from quart import Quart, Response, stream_template
197
import asyncio
198
199
app = Quart(__name__)
200
201
@app.route('/large-report')
202
async def large_report():
203
# Stream large template for better performance
204
async def generate():
205
async for chunk in stream_template('large_report.html',
206
data=await get_large_dataset(),
207
title='Annual Report'):
208
yield chunk
209
210
return Response(generate(), mimetype='text/html')
211
212
@app.route('/live-data')
213
async def live_data():
214
# Stream template with real-time data updates
215
async def generate():
216
template_start = """
217
<html><head><title>Live Data</title></head><body>
218
<h1>Live Data Stream</h1>
219
<div id="data">
220
"""
221
yield template_start
222
223
# Stream data updates
224
for i in range(100):
225
data_chunk = f"<p>Data point {i}: {await get_live_data_point()}</p>\n"
226
yield data_chunk
227
await asyncio.sleep(0.1) # Simulate real-time updates
228
229
template_end = """
230
</div>
231
<script>
232
// Auto-scroll to bottom
233
window.scrollTo(0, document.body.scrollHeight);
234
</script>
235
</body></html>
236
"""
237
yield template_end
238
239
return Response(generate(), mimetype='text/html')
240
241
async def get_large_dataset():
242
# Simulate large dataset
243
return [{'id': i, 'value': f'Item {i}'} for i in range(10000)]
244
245
async def get_live_data_point():
246
# Simulate live data
247
return f"Value at {datetime.now()}"
248
```
249
250
#### Custom Template Functions and Filters
251
252
```python
253
from quart import Quart, render_template
254
from markupsafe import Markup
255
import json
256
257
app = Quart(__name__)
258
259
@app.template_filter('jsonify')
260
def jsonify_filter(value):
261
"""Custom filter to convert Python objects to JSON."""
262
return Markup(json.dumps(value, indent=2))
263
264
@app.template_filter('currency')
265
def currency_filter(value):
266
"""Format value as currency."""
267
try:
268
return f"${float(value):.2f}"
269
except (ValueError, TypeError):
270
return "$0.00"
271
272
@app.template_global()
273
def get_menu_items():
274
"""Global function available in all templates."""
275
return [
276
{'name': 'Home', 'url': '/'},
277
{'name': 'About', 'url': '/about'},
278
{'name': 'Contact', 'url': '/contact'}
279
]
280
281
@app.template_test('even')
282
def is_even(value):
283
"""Custom test for even numbers."""
284
try:
285
return int(value) % 2 == 0
286
except (ValueError, TypeError):
287
return False
288
289
@app.route('/demo')
290
async def template_demo():
291
return await render_template('demo.html',
292
data={'items': [1, 2, 3, 4, 5]},
293
price=29.99)
294
```
295
296
Example `demo.html` template using custom functions:
297
298
```html
299
<!DOCTYPE html>
300
<html>
301
<head>
302
<title>Template Demo</title>
303
</head>
304
<body>
305
<nav>
306
<ul>
307
{% for item in get_menu_items() %}
308
<li><a href="{{ item.url }}">{{ item.name }}</a></li>
309
{% endfor %}
310
</ul>
311
</nav>
312
313
<h1>Demo Page</h1>
314
315
<p>Price: {{ price | currency }}</p>
316
317
<h2>Data (JSON format):</h2>
318
<pre>{{ data | jsonify }}</pre>
319
320
<h2>Numbers:</h2>
321
{% for item in data.items %}
322
<p>{{ item }} is {% if item is even %}even{% else %}odd{% endif %}</p>
323
{% endfor %}
324
</body>
325
</html>
326
```
327
328
#### Error Handling in Templates
329
330
```python
331
from quart import Quart, render_template, abort
332
from jinja2 import TemplateNotFound, TemplateSyntaxError
333
334
app = Quart(__name__)
335
336
@app.route('/safe-render/<template_name>')
337
async def safe_render(template_name):
338
try:
339
# Safely render user-specified template
340
safe_templates = ['page1.html', 'page2.html', 'page3.html']
341
342
if template_name not in safe_templates:
343
abort(404)
344
345
return await render_template(f'safe/{template_name}',
346
content='Safe content')
347
348
except TemplateNotFound:
349
# Handle missing template
350
return await render_template('error.html',
351
error='Template not found',
352
code=404), 404
353
354
@app.errorhandler(TemplateSyntaxError)
355
async def handle_template_error(error):
356
# Handle template syntax errors
357
if app.debug:
358
return f"Template Error: {error}", 500
359
else:
360
return await render_template('error.html',
361
error='Template rendering failed',
362
code=500), 500
363
364
@app.route('/fallback-template')
365
async def fallback_template():
366
# Try multiple templates, use first found
367
template_options = [
368
'custom_page.html',
369
'default_page.html',
370
'fallback.html'
371
]
372
373
return await render_template(template_options,
374
title='Fallback Demo',
375
message='This uses template fallback')
376
```