Templates
The openviper.template package integrates Jinja2 into OpenViper, providing
a cached environment factory and a plugin auto-loader for custom filters and
global functions.
Overview
Template rendering is available directly from response classes — use
HTMLResponse with template= and
context= in any view handler and the framework resolves and renders the
named template automatically.
The environment module maintains a
functools.lru_cache-backed Environment keyed by the tuple
of template search paths. Every subsequent render with the same paths returns
the same environment at zero cost.
Autoescape is enabled for .html and .jinja2 extensions by default.
Key Classes & Functions
- openviper.template.environment.get_jinja2_env(search_paths) jinja2.Environment
Return a cached Jinja2
Environmentfor search_paths (a tuple of directory strings). Callsload()on first construction so that all configured filters and globals are available immediately.Raises
ImportErrorifjinja2is not installed.
openviper.template.plugin_loader
The plugin loader discovers and registers custom Jinja2 filters and globals from two locations on startup (once per process):
<app_dir>/jinja_plugins/for each app inINSTALLED_APPS.The project-level directory configured by
settings.JINJA_PLUGINS["path"](default:"jinja_plugins").
Expected directory layout:
jinja_plugins/
filters/
slugify.py # def slugify(value): ...
truncate.py # def truncate(value, length=100): ...
globals/
now.py # def now(): ...
Each callable in a discovered module is registered under its own name.
Private names (starting with _) and unsafe built-ins (eval, exec,
etc.) are always skipped.
Example Usage
See also
Working projects that use templates:
examples/todoapp/ — HTML templates with form handling
examples/ai_smart_recipe_generator/ — Jinja2 HTML rendering with static assets
examples/ai_moderation_platform/ — Jinja2 templates with plugins
HTMLResponse with a Template
from openviper.routing.router import Router
from openviper.http.request import Request
from openviper.http.response import HTMLResponse
router = Router()
@router.get("/")
async def home(request: Request) -> HTMLResponse:
posts = await Post.objects.filter(is_published=True).limit(10).all()
return HTMLResponse(template="home.html", context={
"request": request,
"posts": posts,
})
Template Structure
Place templates in a templates/ directory at the project root or inside
any installed app:
myproject/
templates/
base.html
home.html
blog/
templates/
blog/
post_detail.html
templates/home.html:
{% extends "base.html" %}
{% block content %}
<ul>
{% for post in posts %}
<li><a href="/posts/{{ post.id }}">{{ post.title }}</a></li>
{% endfor %}
</ul>
{% endblock %}
Custom Filter Plugin
Create jinja_plugins/filters/truncate_words.py:
def truncate_words(value: str, count: int = 20) -> str:
"""Truncate *value* to at most *count* words."""
words = value.split()
if len(words) <= count:
return value
return " ".join(words[:count]) + "…"
The filter is available in all templates automatically:
{{ post.body | truncate_words(15) }}
Configuration
@dataclasses.dataclass(frozen=True)
class MySettings(Settings):
TEMPLATES_DIR: str = "templates" # base template search path
JINJA_PLUGINS: dict = dataclasses.field(default_factory=lambda: {
"enable": 1,
"path": "jinja_plugins",
})