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 UIResourceConfig
from .components import ArticleContextComponent
class ArticleUIResourceConfig(UIResourceConfig):
blueprint_name = "articles"
url_prefix = "/articles"
template = "articles/detail.html"
components = [ArticleContextComponent]
#...
def my_custom_page(self):
#...
my_context = {}
self.run_components(
"before_my_custom_page",
extra_context=extra_context,
)
#...
return current_oarepo_ui.catalog.render(
self.get_jinjax_macro(
"my_custom_page",
),
**my_context,
)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"Template Rendering
UI resources render the template declared in their config.
Template context comes from resource view methods.
Context is extended via resource components.
Templates live in the resource’s registered templates/ directory.
Example structure:
- RecordDetail.jinja