UI Resource Views advanced
InvenioRDM uses the flask-resources framework to structure its REST APIs using resources, resource configs, and resource components.
NRP-based Invenio repositories extend this architecture with UI resources — resource classes that render HTML front-end views in addition to API responses (typically JSON).
UI resources provide a structured and declarative way to implement page-level views, keeping routing logic, permissions, and template context preparation consistent, configurable & extensible.
If you are interested in how to build custom API resources, you’ll find more in the InvenioRDM — Building Resources docs.
What are UI Resources?
UI resources mirror the structure of Invenio’s API resources but serve rendered templates rather than JSON. They consist of:
- UIResource - the view/controller class
- UIResourceConfig - configuration describing routes, templates, permissions, and components
- UIResourceComponent - specialised rendering logic that hooks into specific stage of view rendering. Prepares or modifies template context before rendered, e.g. by calling to service layer
Reference implementations:
-
UIResourceBase class:
https://github.com/oarepo/oarepo-ui/blob/main/oarepo_ui/resources/base.py -
Base record model UI resource (view controller class for record model specific UI): https://github.com/oarepo/oarepo-ui/blob/main/oarepo_ui/resources/records/resource.py#L107
-
Example repository usage (each record model has its own UI resource class, that can be further customized): https://github.com/NRP-CZ/datarepo/blob/main/ui/datasets/__init__.py
Why UI Resources?
UI resources define how front-end pages are routed & rendered, they are responsible for:
- routing and view configuration
- clean separation of logic through resource components
- invocation of service layer & preparation of context data for UI templates
- rendering of Jinja templates
Structure Overview
A typical UI resource module includes:
- __init__.py
- components.py
__init__.py- Contains definitions for both theMymodelUIResourceConfigandMymodelUIResource, this is how your views formymodelrecord model gets routed and rendered.components.py- If present, contains any custom UI resource components implementation, to customize template context before render.templates- Contains Jinja (or JinjaX) templates registered and rendered byMymodelUIResource
Resource Configuration
UIResourceConfig declares the behavior of a UI resource:
Following options may be typically present and configurable:
blueprint_name- name of Flask blueprinturl_prefix- prefix applied to all routes handled by a resourceroutes— route map configuration for Flasktemplates- maps route names to specific Jinja templatescomponents— list of registered resource componentssearch_component- declares the React component used to render search result items of that specific record model.
Example:
from oarepo_ui.resources.base import UIResourceConfig
from .components import ArticleContextComponent
class ArticleUIResourceConfig(UIResourceConfig):
blueprint_name = "articles"
url_prefix = "/articles"
template = "articles/detail.html"
components = [ArticleContextComponent]
#...Resource Class
UIResource implements controller-like methods:
from oarepo_ui.resources.base import UIResource
class ArticleUIResource(UIResource):
"""UI resource for Article records pages."""This controller is responsible for rendering pages related to certain record model, like:
routes: Mapping[str, str] = {
"search": "",
"deposit_create": "/uploads/new",
"deposit_edit": "/uploads/<pid_value>",
"record_detail": "/records/<pid_value>",
"record_latest": "/records/<pid_value>/latest",
"record_export": "/records/<pid_value>/export/<export_format>",
"published_file_preview": "/records/<pid_value>/files/<path:filepath>/preview",
"draft_file_preview": "/records/<pid_value>/preview/files/<path:filepath>/preview",
}
"""Routes for records resource, mapping route names to URL patterns."""For every view method, it can also define rendering lifecycle hooks (which can later be leveraged by Resource Components), e.g:
def deposit_edit(
self,
**kwargs: Any,
) -> ResponseReturnValue:
"""Return edit page for a record."""
#...
extra_context: dict[str, Any] = {}
# Here any registered components have access to
# & can customize any passed context arg
self.run_components(
"before_ui_edit",
extra_context=extra_context,
#...
)Workflow of such controller view methods typically looks like:
- validate & parse HTTP request args & headers
- call into the service layer to fetch data
- call registered resource components to process or generate template context
- render the assigned view Jinja template with processed context
Resource Components
Components extend the behavior of a UI resource via lifecycle hooks:
from oarepo_ui.resources.components import UIResourceComponent
class ArticleContextComponent(UIResourceComponent):
def before_detail(self, resource, request, extra_context, **kwargs):
extra_context["publisher_highlight_color"] = "red" Common rendering lifecycle hooks include:
before_detailafter_detailbefore_searchbefore_render_templatebefore_ui_editbefore_ui_create
More hooks can be found or created by defining them in the UIResource-based class view methods.
E.g. to define a before_my_custom_page hook:
from oarepo_ui.resources.base import UIResource, UIResourceConfig
from .components import ArticleContextComponent
from flask import current_oarepo_ui
class ArticleUIResourceConfig(UIResourceConfig):
blueprint_name = "articles"
url_prefix = "/articles"
template = "articles/detail.html"
components = [ArticleContextComponent]
# Bind view method to URL route
routes = {
"my_custom_page": "/custom/<pid_value>",
}
# Optionally set a custom template for this view
templates = {
"my_custom_page": "articles.custom_page", # JinjaX uses dot notation
}
class ArticleUIResource(UIResource):
def my_custom_page(self):
# Prepare template context
my_context = {
"record": self.service.read(),
"custom_data": "some value",
}
# Run components (lifecycle hooks)
self.run_components(
"before_my_custom_page",
extra_context=my_context,
)
# Render the template using JinjaX catalog
return current_oarepo_ui.catalog.render(
self.get_jinjax_macro(
"my_custom_page", # Uses templates["my_custom_page"] or falls back to macro name
),
**my_context,
)This creates a complete binding:
- URL route:
routes["my_custom_page"]maps the view to/custom/<pid_value> - Template:
templates["my_custom_page"]specifies the JinjaX template using dot notation (articles.custom_page) - View method:
my_custom_page()inArticleUIResourcecreates the context and renders
The template file would be located at ui/articles/templates/articles/custom_page.jinja and export a macro named custom_page.
We now implement the hook as a method of a resource component:
from oarepo_ui.resources.components import UIResourceComponent
class ArticleContextComponent(UIResourceComponent):
def before_detail(self, resource, request, extra_context, **kwargs):
extra_context["publisher_highlight_color"] = "red"
def before_my_custom_page(self, extra_context):
extra_context["foo"] = "bar"