Skip to content

Views Overview

Views define how data is displayed and edited in Fullfinity’s UI.

The type field is a Selection whose choices are the registered view types. The engine ships these (in UiView.type plus the core _selection_add):

TypeDescriptionUse Case
FormDetail/edit viewSingle record editing
ListTable viewBrowsing multiple records
KanbanCard-based viewVisual workflows
CalendarTimeline viewDate-based data
GanttTimeline with dependenciesScheduling, project tasks (Gantt Views)
PivotCross-tab aggregation tableReporting/analysis (Pivot & Chart)
ChartAggregated chart of recordsReporting/analysis (Pivot & Chart)
TimelineHeatmapRows × time-bucket matrixLoad boards, density grids
DashboardConfigurable home screenKPI cards, charts, lists, goals
SearchFilters and groupingsQuery configuration (Other Views)
WizardTransient-model formMulti-step input before an action (Other Views)
DesignerVisual report/layout designerDocument design

Tree is also an accepted value (a legacy alias for List); use List.

Modules register additional view types via a small models/ui_view_types.py that _selection_adds onto UiView.type. Each is rendered by its own React component, so a type is only usable when its owning module is installed:

TypeProvided by
OrgCharthr
PortalList, PortalDetailportal
FinancialReport, CustomerStatements, BankReconciliationaccounting (enterprise)

Other modules add their own (e.g. POS, manufacturing) the same way. To add a view type: create <module>/models/ui_view_types.py with an __inherit__ = "UiView" class whose _selection_add = {"type": ["MyType"]}, list the file in models/__init__.py, and add the matching renderer to the React ViewRenderer dispatch.

A view normally sources its data by querying the model’s records. To render computed / non-record data instead, declare a data_method in the arch.

All views share this structure:

{
"data_type": "UiView",
"name": "contact_form_view",
"identifier": "contact_form_view",
"type": "Form",
"model": "Contact",
"arch": [...]
}
FieldTypeDescription
data_typestringAlways "UiView"
namestringView name (usually same as identifier)
identifierstringUnique identifier
typestringView type (Form, List, Kanban, Calendar, Gantt, Pivot, Chart, Search, Wizard, …)
modelstringModel this view displays
archarray/objectView architecture
inherited_viewstringParent view identifier (for inheritance)

The arch contains the view structure:

ElementDescription
rowHorizontal container (12-column grid)
columnVertical container with span (1-12)
tabTab container with label
ElementDescription
fieldData field with widget
statusbarStatus indicator
actionButtonButton triggering model method
linkButtonStatistic button with link
ElementDescription
textStatic text
dividerVisual separator
activitiesActivity timeline

Each element has:

{
"type": "field",
"name": "email",
"properties": {
"widget": "TextInput",
"label": "Email Address",
"placeholder": "Enter email",
"visible": "Q(type__eq='company')",
"groups": ["admin"]
}
}
PropertyDescription
typeElement type (structural, at top level)
nameField name (structural, at top level)
spanColumn width 1-12 (structural, at top level)
propertiesAll field configuration (widget, visibility, behavior)

All behavioral and display properties go inside properties:

{
"properties": {
"widget": "TextInput",
"visible": "Q(status__eq='draft')",
"readonly": "Q(status__neq='draft')",
"required": true,
"groups": ["sales_manager"]
}
}
PropertyTypeDescription
widgetStringWidget type to render
visibleBoolean/StringShow/hide element. Can be false or Q-expression. Default: true
readonlyBoolean/StringMake field(s) read-only. Can be true or Q-expression.
requiredBoolean/StringMake field required. Can be true or Q-expression.
groupsArrayRequired group identifiers for access control.

Note: Structural attributes (type, name, span, content, title) stay at the top level. All behavior and display config goes in properties.

Actions connect views to navigation:

{
"data_type": "WindowAction",
"identifier": "contacts_action",
"name": "Contacts",
"model": "Contact",
"modes": "Kanban,List,Form",
"default_view": "contact_kanban_view",
"views": ["contact_kanban_view", "contact_list_view", "contact_form_view"],
"search_view": "contact_search_view"
}

Menus create navigation structure:

{
"data_type": "UiMenu",
"identifier": "contacts_menu",
"name": "Contacts",
"parent": "sales_menu",
"action": "contacts_action",
"sequence": 10
}

When loading a view:

  1. Fetch base view by identifier
  2. Find all child views (inherited_view)
  3. Apply inheritance operations
  4. Return combined view

If no view is defined, Fullfinity auto-generates one from the model registry. This allows navigation to any model without requiring a WindowAction or UiView definition.

/app/:rootMenu/m/:model # List view
/app/:rootMenu/m/:model/:recordId # Form view for specific record
/app/:rootMenu/m/:model/new # Form view for new record
/app/core_menu/m/Currency # Currency list view
/app/core_menu/m/Currency/5 # Currency form for record 5
/app/core_menu/m/Currency/new # New Currency form
ViewFields IncludedLayout
ListChar, Selection, ManyToOne, OneToOne, Integer, Float, MonetarySimple table
FormAll fields except JSON, File, Binary, OneToMany2-column grid with status bar
SearchActive filter + group-by for Selection/ManyToOneBasic filters
  1. If a UiView exists in the database for the model, it is used (with inheritance)
  2. If no UiView exists, a dynamic view is generated from the model registry
  • Quick prototyping without creating view definitions
  • Admin/debug access to any model
  • Action methods returning get_action(model="ModelName") for navigation
async def action_view_logs(self):
"""Open audit logs without a WindowAction."""
return await get_action(model="AuditLog")
async def action_view_record(self):
"""Open a specific record dynamically."""
return await get_action(model="SomeModel", record_id=self.related_id)

Views are stored in modules/<module>/views/:

views/
├── actions.json # Window actions
├── menus.json # Menu items
├── contact_views.json # Contact views
└── product_views.json # Product views