Template Rendering with Jinja easy
Jinja is the server-side templating engine used throughout InvenioRDM for rendering HTML pages.
NRP-based repositories build on top of this system and use Jinja extensively for page layout, custom UI views, and template overrides.
If you are more interested in how to change built-in templates with your custom ones, the topic is covered in great detail undder the Branding -> Templating docs
In this page we will focus on creation and usage of custom Jinja templates including how to use them, how templates interact with UI Resources (the NRP Invenio mechanism for generating HTML responses for front-end pages).
Referencing templates in UI Resources
UI Resources in NRP repositories define view methods tied to specific templates.
Which view renders which template is determined by a UIResourceConfig configuration.
class DatasetUIResourceConfig(UIResourceConfig):
blueprint_name = "datasets"
url_prefix = "/datasets"
template_folder = "templates"
templates = {
"record_detail": "datasets/detail.html",
"record_preview": "datasets/preview.html",
}This means:
- Templates are looked up inside your registered module’s
templates/directory datasets/detail.htmlresolves toui/datasets/templates/datasets/detail.htmlrecord_detailview method ofDatasetUIResourceuses this exact template to render a detail of a record.
Using templates inside UI Resource methods
When implementing a custom view, the following method is typically used to render a template while including
any context variables. This method supports both plain Jinja and enhanced JinjaX templates, while pulling
the configured template from the templates map of UIResourceConfig.
from oarepo_ui.resources import UIResource
from oarepo_ui.proxies import current_oarepo_ui
class DatasetUIResource(UIResource):
def detail(self, identity, pid, record=None, **kwargs):
context_kwargs = { "title": "Demo" }
return current_oarepo_ui.catalog.render(
self.get_jinjax_macro(
"record_detail",
),
**context_kwargs,
)This keeps view logic clean: UI Resources provide the context driven by business logic, and templates handle the rendering.
Creating new templates
To create and use a new template, follow this elementary steps:
Create a file under your /templates folder, such as:
Hello from your Jinja template:
<h1>Hello, {{ name }}!</h1>
This will render at any route pointing to the hello view.Add it to your templates mapping in the ResourceConfig:
templates = {
"hello": "example/hello.html"
}Make sure to render it from your UI Resource’s hello view:
return current_oarepo_ui.catalog.render(
self.get_jinjax_macro(
"hello",
),
**{"name": "Mirek"},
)Basic Jinja syntax refresher
Here we cover just the most common syntax examples you might come across in a Jinja templates. Please refer to the official Jinja Template Designer docs for a full reference on Jinja syntax.
Variables
Prints a value of a context variable.
<p>{{ user.email }}</p>Filters
One or more chained filters processing/transforming the value of a context variable.
{{ title | upper }}Loops
Iterate over iterable context variable values (like lists or tuples).
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>Conditionals
The if statement in Jinja is comparable with the Python if statement. Used to test for a result of a boolean expression.
{% if record.access == "public" %}
<p>Public record</p>
{% endif %}Macros
Include other snippet of Jinja code into a template.
{% macro input(name, value='', type='text', size=20) -%}
<input type="{{ type }}" name="{{ name }}" value="{{
value|e }}" size="{{ size }}">
{%- endmacro %}
<p>{{ input('username') }}</p>Template inclusion & composition
Reusable shared templates (navbars, footers, panels) could be placed in shared dirs & then included where needed:
{% include "branding/header.html" %}Blocks
Defines extension points of a template or overrides block contents from inherited template (see Template Inheritance)
{% extends "invenio_theme/page.html" %}
{% block page_body %}
<div class="ui container">
<h1>{{ record.title }}</h1>
</div>
{% endblock %}Here a page_body block is defined, overriding content from the same block in invenio_theme/page.html.
Template Inheritance
Provide or extend content of blocks defined in inherited template.
For example, InvenioRDM uses a central base template for almost all repository pages:
invenio_theme/page.html
You typically extend it with extends keyword and provide your own content for the blocks
defined by extended template:
{% extends "invenio_theme/page.html" %}
{% block page_body %}
<div class="ui container">
<h1>{{ record.title }}</h1>
</div>
{% endblock %}For cases where you just want to append to the beginning/end of a block, there
is the super() keyword.
{% extends "invenio_theme/page.html" %}
{% block page_footer %}
<p>I'm before the standard footer content.</p>
{{ super() }}
<p>I folow after the standard footer content.</p>
{% endblock %}This will bring up original block content from the extended template.
Best practices
- Keep templates light & simple.
- Use templates only for presentational logic.
- Use
UIResourceConfigto map templates to views. - Never hard-code template filenames inside view methods.
- Prefer macros for repeated UI fragments.
- Templates should do minimal assumptions and rely mainly on the context passed by
UIResourceviews.