Domain formatting such as money, dates or slugs belongs in reusable filters, not repeated template code.
When the built-ins aren't enough, extend the Environment. All three are plain dicts of callables:
env.filters["money"] = format_moneymakes{{ price|money }}callformat_money(price). Extra filter arguments become extra function arguments:{{ price|money("EUR") }}callsformat_money(price, "EUR").env.tests["prime"] = is_primeenables{% if n is prime %}.env.globals["now"] = datetime.nowmakes{{ now() }}available in every template; also good for config values and helper functions.
Register them before loading templates, since filters are looked up when a template is compiled; an unknown filter is a TemplateAssertionError at compile time, not a runtime blank.
Sometimes a filter needs more than its value. Decorators from jinja2 pass extra first arguments:
@pass_context: the activeContext, giving access to template variables, for example the current locale@pass_eval_context: the evaluation context, mainly to checkeval_ctx.autoescape@pass_environment: the Environment
A filter that returns HTML should return Markup, escaping its inputs, so it works correctly with autoescaping on. In Flask, register filters with @app.template_filter() and globals with @app.template_global() or a context processor.
Keep filters pure and fast: they run for every value, on every render.
Example
from decimal import Decimal
from jinja2 import Environment, pass_context
from markupsafe import Markup, escape
def money(value, currency="USD"):
return f"{Decimal(value):,.2f} {currency}"
@pass_context
def greet(ctx, name): # can read template variables
return f"{ctx.get('greeting', 'Hello')}, {name}"
def badge(text):
return Markup('<span class="badge">%s</span>') % escape(text)
env = Environment(autoescape=True)
env.filters.update(money=money, greet=greet, badge=badge)
env.tests["even_len"] = lambda s: len(s) % 2 == 0
env.globals["site"] = "Shop"
t = env.from_string("{{ site }}: {{ 1234.5|money('EUR') }} | {{ 'Ann'|greet }} | "
"{{ '<b>new</b>'|badge }} | {{ 'abcd' is even_len }}")
print(t.render(greeting="Hi"))
# Shop: 1,234.50 EUR | Hi, Ann | <span class="badge"><b>new</b></span> | TrueRemember
- env.filters, env.tests and env.globals are dicts you can extend
- Extra filter arguments map to extra function parameters
- @pass_context gives a filter access to template variables
- Return Markup from filters that produce HTML, escaping inputs
Watch out for
- Registering a filter after templates using it were compiled, causing "No filter named" errors.