UI Helper Modules¶
The UI layer is organized into focused helper modules that handle specific concerns. This separation improves testability and reduces coupling in the route handlers. The submodules live under the metaseed.ui.helpers package, whose __init__ re-exports their public functions for convenient access.
graph TD
Routes[Route Handlers] --> H[metaseed.ui.helpers]
H --> EH[entity_helpers]
H --> TH[table_helpers]
H --> NH[navigation_helpers]
H --> FC[FormContext<br/>from metaseed.forms]
EH --> State[AppState]
TH --> State
NH --> State
FC --> State
Form rendering¶
Form rendering context and value collection are exposed through the metaseed.ui.helpers package.
FormContext¶
Dataclass that encapsulates form rendering state, reducing parameter counts in template rendering. It is defined in metaseed.forms and re-exported from metaseed.ui.helpers.
from metaseed.ui.helpers import FormContext
ctx = FormContext(
entity_type="Study",
helper=facade.Study,
values={"title": "My Study"},
node_id="abc123", # None for create forms
)
# Access field categories; each field is in exactly one
required = ctx.get_required_fields() # required, not holding entities
optional = ctx.get_optional_fields() # optional, not holding entities
nested = ctx.get_nested_fields() # holding entities, required or not
A field that holds entities, a list of entities or a single entity, is rendered once: as its table under Related Entities, whose header marks the field required and a single-entity field as holding exactly one. A single-entity table offers no second row, and the add-row route refuses one (ADR 006).
Dataset toolbar¶
The open dataset's toolbar (Validate, List, Graph, the exports, Import) is one partial, partials/dataset_toolbar.html, shown on the dataset page and above every record form, so the graph can be switched on beside the record being edited. The form's first crumb, Entities, returns to the open dataset's page, /dataset/<name>/edit.
Editing a row's record¶
A table under Related Entities lists the records stored as children of the open record in that field. A record can also carry a copy of its children embedded in its own data, from a nested document or a dataset saved before its children were stored as records of their own; get_nested_items_for_edit uses that copy only for a field with no stored child records, as the Excel export does. A copy shown next to the records it duplicates showed values the records no longer held: a renamed Source kept its old name in the Study's table.
Every row has an Edit button. A row with a stored record (_node_id) opens that record in its own form. A row added with + Add has no stored record yet; its Edit opens the row in the nested form (/nested/<parent>/<field>/<row>). Saving the nested form stores the row as a record and writes the dataset, as the open record's own Save does; the confirmation appears only after the write. A save that reported success while only the session held the row lost the record when the session ended (#356).
Field notes¶
Hovering over a field's label in the entity form, required or optional, opens its note: the description, whether it is required, its type, an example, and its pattern, length or range, allowed values and the validation rules that name it. field_notes(helper, spec) in metaseed.ui.services.heading_note builds the notes for one entity type from heading_note, the builder the exported Excel headings use, so the form and the workbook describe a field in the same words. The label carries the note as data-note; static/js/field-notes.js draws it in one box attached to the page body, which stays open while the pointer is on it so a long note can be scrolled. metaseed-hub loads the same script and builds its notes with the same function.
| Property | Description |
|---|---|
is_edit |
True if editing existing entity |
description |
Entity description from spec |
ontology_term |
Ontology term from spec |
Functions¶
| Function | Purpose |
|---|---|
collect_form_values(form_data, helper) |
Extract typed values from form submission; a list of any value type is split into one item per line |
form_field_groups(fields) |
Split fields into required_fields, optional_fields and nested_fields, each field in one; the form routes use only this |
filter_fields(fields, ...) |
Filter field list by required/nested criteria (in metaseed.forms) |
get_field_data(helper) |
Get field metadata for template rendering |
is_nested_field(field) |
Check if field dict represents nested entity: entity, or list whose items is not a value type in PRIMITIVE_TYPES |
format_validation_errors(e) |
Convert ValidationError to user-friendly messages |
entity_helpers¶
Handles entity traversal and nested item extraction.
Functions¶
| Function | Purpose |
|---|---|
to_dict(item) |
Convert Pydantic model or dict to plain dict |
walk_nested_entities(data, entity_type, facade) |
Recursively traverse nested entities, yielding (type, data) tuples |
extract_nested_items(instance, helper) |
Extract nested items from instance as dict mapping |
collect_entities_by_type(state, facade) |
Collect all entities (root and nested) organized by type |
Example: Walking Nested Entities¶
from metaseed.ui.helpers.entity_helpers import walk_nested_entities
investigation_data = {
"unique_id": "INV-1",
"studies": [
{"unique_id": "STU-1", "observation_units": [...]}
]
}
for entity_type, data in walk_nested_entities(investigation_data, "Investigation", facade):
print(f"{entity_type}: {data.get('unique_id')}")
# Output:
# Study: STU-1
# ObservationUnit: ...
table_helpers¶
Handles inline table building for nested entity display.
Functions¶
| Function | Purpose |
|---|---|
get_table_columns(facade, entity_type) |
Get column names for table (excludes nested fields) |
get_table_column_info(facade, entity_type) |
Get complete column metadata (types, constraints, required) |
build_inline_tables(state, facade, entity_type) |
Build table data for all nested fields of an entity |
get_items_store(state, parent_type, field_name) |
Get correct items store based on nested edit context |
format_table_rows(items) |
Format items list for table row rendering |
Table Column Info¶
get_table_column_info returns a dictionary with:
| Key | Type | Description |
|---|---|---|
columns |
list[str] |
Column names to display |
column_types |
dict[str, str] |
Field type per column |
column_constraints |
dict[str, dict] |
Constraints per column |
required_columns |
set[str] |
Required field names |
has_nested_children |
bool |
Whether entity has nested children |
navigation_helpers¶
Handles breadcrumb building and reference field resolution.
Functions¶
| Function | Purpose |
|---|---|
build_breadcrumb(state) |
Build breadcrumb navigation from nested edit stack |
build_ancestor_breadcrumb(state, node) |
Build breadcrumb from a node's containment chain (parent_id and parent_field, ADR 006), so an entity opened directly for editing still shows its ancestors and the field it sits in |
error_response(request, templates, message) |
Create error response with notification template |
get_reference_fields(profile, version, entity_type) |
Get all reference fields for an entity type |
get_parent_id_fields(reference_fields, parent_type) |
Get fields that reference the parent entity |
get_parent_identifier(state, parent_type, target_field) |
Get parent entity's identifier value |
Reference Field Resolution¶
Reference fields link entities to their parents. The helper checks two sources:
- Field definitions with
parent_refattribute (e.g.,parent_ref: Study.identifier) - Validation rules with
referenceattribute (legacy)
from metaseed.ui.helpers.navigation_helpers import get_reference_fields
refs = get_reference_fields("miappe", "1.2", "Sample")
# Returns:
# {
# "observation_unit_id": {
# "target_entity": "ObservationUnit",
# "target_field": "unique_id"
# }
# }
Module Dependencies¶
entity_helpers (standalone)
table_helpers --> navigation_helpers (for reference fields)
navigation_helpers --> specs.loader
FormContext is defined in metaseed.forms and re-exported from
metaseed.ui.helpers. The helper modules depend on AppState for session
state access.