MiniJinja Advanced Templates¶
This example demonstrates advanced templating concepts in Cello, including template inheritance (using a base layout), loop metadata (such as loop.index), rendering inline template strings, and running a standalone template engine instance.
Features Demonstrated¶
- Template Inheritance: Reusing layouts by defining
{% extends "base.html" %}and overridden blocks. - Loop Metadata: Custom loop attributes like
loop.indexto count items during iteration. - Standalone Template Engine: Creating and using a secondary
MiniJinjaEngineinstance for separate plain-text email generation without escaping. - Inline Rendering: Dynamically compiling and rendering inline template strings with
app.render_string().
Complete Source Code¶
"""
MiniJinja Advanced Example — Cello v1.1.0
Demonstrates:
- Template inheritance (base → child templates)
- Loops, conditionals, filters
- Globals (site-wide variables)
- Standalone MiniJinjaEngine (outside of App)
- Inline render_string()
- JSON API endpoint alongside HTML endpoints
Run:
python examples/minijinja_advanced.py
Then visit:
http://localhost:8081/ — dashboard
http://localhost:8081/users — user list (HTML)
http://localhost:8081/api/users — same data as JSON
http://localhost:8081/email/Alice — plain-text email preview
"""
import json
import os
import tempfile
from cello import App, MiniJinjaEngine, Response
# ---------------------------------------------------------------------------
# Templates
# ---------------------------------------------------------------------------
TEMPLATE_DIR = tempfile.mkdtemp(prefix="cello_adv_tpl_")
def tpl(name: str, content: str):
path = os.path.join(TEMPLATE_DIR, name)
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w") as f:
f.write(content)
# Base layout — shared header/footer with named blocks
tpl("base.html", """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{{ site_name }}{% endblock %}</title>
<style>
body { font-family: sans-serif; max-width: 800px; margin: 2rem auto; }
nav a { margin-right: 1rem; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ccc; padding: 0.5rem; text-align: left; }
.badge { background: #4caf50; color: white; border-radius: 4px; padding: 2px 6px; }
.badge.inactive { background: #9e9e9e; }
</style>
</head>
<body>
<header>
<strong>{{ site_name }}</strong> — v{{ version }}
<nav>
<a href="/">Dashboard</a>
<a href="/users">Users</a>
</nav>
<hr>
</header>
<main>{% block content %}{% endblock %}</main>
<footer><hr><small>© {{ year }} {{ site_name }}</small></footer>
</body>
</html>
""")
# Dashboard child template
tpl("dashboard.html", """{% extends "base.html" %}
{% block title %}Dashboard — {{ site_name }}{% endblock %}
{% block content %}
<h1>Dashboard</h1>
<p>Total users: <strong>{{ stats.total }}</strong></p>
<p>Active users: <strong>{{ stats.active }}</strong></p>
<p>Admins: <strong>{{ stats.admins }}</strong></p>
<h2>Recent activity</h2>
{% if activity %}
<ul>
{% for event in activity %}
<li>{{ event }}</li>
{% endfor %}
</ul>
{% else %}
<p><em>No recent activity.</em></p>
{% endif %}
{% endblock %}
""")
# Users list child template
tpl("users.html", """{% extends "base.html" %}
{% block title %}Users — {{ site_name }}{% endblock %}
{% block content %}
<h1>Users ({{ users | length }})</h1>
<table>
<tr><th>#</th><th>Name</th><th>Role</th><th>Status</th></tr>
{% for user in users %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ user.name }}</td>
<td>{{ user.role | title }}</td>
<td>
{% if user.active %}
<span class="badge">Active</span>
{% else %}
<span class="badge inactive">Inactive</span>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% endblock %}
""")
# Plain-text email template (not HTML — auto-escape won't apply)
tpl("emails/welcome.txt", """Hi {{ name }},
Welcome to {{ site_name }}! Your account has been created.
Login at: {{ login_url }}
Regards,
The {{ site_name }} Team
""")
# ---------------------------------------------------------------------------
# Standalone engine (used outside of App, e.g. for emails)
# ---------------------------------------------------------------------------
email_engine = MiniJinjaEngine(template_dir=TEMPLATE_DIR, auto_escape=False)
# ---------------------------------------------------------------------------
# App setup
# ---------------------------------------------------------------------------
app = App()
app.enable_templates(
template_dir=TEMPLATE_DIR,
auto_escape=True,
globals={
"site_name": "CelloAdmin",
"version": "1.1.0",
"year": 2026,
},
)
# Sample data
USERS = [
{"name": "Alice", "role": "admin", "active": True},
{"name": "Bob", "role": "editor", "active": True},
{"name": "Charlie", "role": "viewer", "active": False},
{"name": "Diana", "role": "editor", "active": True},
]
@app.get("/")
def dashboard(request):
stats = {
"total": len(USERS),
"active": sum(1 for u in USERS if u["active"]),
"admins": sum(1 for u in USERS if u["role"] == "admin"),
}
html = app.render("dashboard.html", {
"stats": stats,
"activity": [
"Alice logged in",
"Bob updated article #42",
"Diana created new post",
],
})
return Response.html(html)
@app.get("/users")
def users_page(request):
html = app.render("users.html", {"users": USERS})
return Response.html(html)
@app.get("/api/users")
def users_api(request):
# JSON endpoint — no template needed
return {"users": USERS, "total": len(USERS)}
@app.get("/email/{name}")
def email_preview(request):
"""Preview a plain-text welcome email (rendered via standalone engine)."""
name = request.params["name"]
text = email_engine.render("emails/welcome.txt", {
"name": name,
"site_name": "CelloAdmin",
"login_url": "https://example.com/login",
})
return Response.text(text)
@app.get("/snippet")
def snippet(request):
"""Render an inline template string — no file needed."""
items = request.params.get("items", "a,b,c").split(",")
html = app.render_string(
"<ul>{% for x in items %}<li>{{ x | upper }}</li>{% endfor %}</ul>",
{"items": items},
)
return Response.html(html)
if __name__ == "__main__":
print(f"Templates dir: {TEMPLATE_DIR}")
print("Listening on http://localhost:8081")
app.run(port=8081)
Running This Example¶
python examples/minijinja_advanced.py
# Test endpoints:
curl http://127.0.0.1:8081/
curl http://127.0.0.1:8081/users
curl http://127.0.0.1:8081/api/users
curl http://127.0.0.1:8081/email/Alice
curl http://127.0.0.1:8081/snippet?items=red,green,blue
Key Concepts¶
- Base Layouts & Extends: The
{% extends "base.html" %}tag lets child templates inherit structured wrappers and override only specific{% block content %}areas. - Dedicated Render Engines: You can instantiate a separate
MiniJinjaEnginewithauto_escape=Falsefor plain-text formatting (e.g. email generation). - String Rendering:
app.render_string()lets you compile templates on the fly from inline Python strings, avoiding disk reads for tiny templates or dynamic components.