Skip to content

Computed View Data (data_method)

By default a view gets its data by querying the model’s records through the standard record query (filter → records): pagination, sorting, grouping, the search bar, the Browse panel and inline edit all flow from that one query.

Some views render data that isn’t a flat list of records — a capacity board (every resource × time bucket, with a computed load %), a reconciliation grid, a roll-up. For those, a view declares a data_method in its arch. This is the standard, blessed way a view sources computed / non-record data: the front end calls that method on the view’s model — passing the view’s live params — and renders the result, instead of running the record query.

It replaces the older pattern of building a bespoke client action per computed board (each of which bypassed the view system and hand-rolled its own header). With data_method, computed data flows through the view system, so the view inherits the standard chrome: the title bar, view switching, full-bleed layout.

  • Read-only. A method returns a computed projection. The generic record affordances (inline edit, record selection, export, form drill-down, pagination, the Browse panel) only apply if the method itself honours the params it is handed. A purely computed board declares those off (see Gating the chrome below).
  • The return contract is per view type, not universal. There is no single magic shape. Each view type documents what its data_method must return — e.g. List wants record-shaped rows, Calendar wants dated records, TimelineHeatmap wants a { buckets, rows } matrix. Any view type can opt in, with its own documented shape.

Put data_method in the view arch instead of (or alongside, where a view type allows a fallback) field mappings:

- data_type: UiView
name: wc_load_heatmap_view
identifier: wc_load_heatmap_view
type: TimelineHeatmap
model: WorkCenterLoad
arch:
- data_method: ui_load

The action points at the same (often transient) model:

- data_type: WindowAction
name: Capacity Load
identifier: mrp_capacity_load_action
model: WorkCenterLoad
default_view: wc_load_heatmap_view
modes: TimelineHeatmap
action_ctx:
data_method: ui_load # mirror — see "Gating the chrome"
views:
- - R
- - wc_load_heatmap_view

The method lives on the view’s model. For computed boards this is usually a transient read model (_transient = True) — it stores nothing; it just exposes the method. Because the /api/execute/<model>/<method> path instantiates an in-memory instance and calls method(instance, **kwargs), the method MUST be an instance method (self), not a cls method. The view’s live params arrive as keyword arguments matched by name.

class WorkCenterLoad(Model):
_transient = True
_verbose_name = "Work Center Load"
async def ui_load(self, bucket="Day", anchor=None):
# `bucket` and `anchor` are the TimelineHeatmap's live params.
...
return {"buckets": [...], "rows": [...]} # the view type's shape

self, not cls. A cls-first method is auto-promoted to a classmethod and the execute path’s positional instance collides with its first real argument (got multiple values for argument ...). Every data_method is a self instance method.

The front end passes the view’s current state as the method kwargs, so navigating (changing the period, paging the window) re-queries:

View typeParams passed
TimelineHeatmapbucket (Day/Week/Month), anchor (YYYY-MM-DD)

(Other view types add their own as they adopt the mechanism — e.g. a calendar window, a pivot groupby.)

You declare data_method and implement the method — that’s all you wire up. When a view’s arch carries a data_method, the framework calls it on the model with the view’s live params (instead of running the record query), keeps the previous result on screen while refetching, and background-polls to stay fresh. The result is handed to the view to render in its declared shape; no per-view plumbing is required on your side.

A computed view has no records, so the search bar and Browse panel are meaningless. Mirror data_method into the action’s action_ctx (as shown above) and, for that action only, the shell drops both the search bar and the Browse panel.

This is gated per action, not per view type — so the generic record-sourced use of the same view type (e.g. a TimelineHeatmap over real records) keeps full search + Browse. Only the computed action loses them.

data_method sources a view’s data. Sometimes you instead want to run a side effect when an action opens — most often to materialize or refresh the records a normal record-sourced list is about to show, so the screen reflects current reality instead of waiting for a background job to have run.

Declare action_ctx.on_open as a "Model.method" string. When the action opens, the framework calls that no-argument classmethod once (per navigation, before the views load), then the list queries records as usual:

- data_type: WindowAction
name: Payment Follow-Ups
identifier: followup_status_action
model: FollowUpStatus
default_view: followup_status_list_view
modes: "List, Form"
action_ctx:
on_open: FollowUpStatus.refresh_worklist # runs once when the screen opens
views:
- - R
- - followup_status_list_view
class FollowUpStatus(Model):
async def refresh_worklist(cls, send=False):
# Upsert the worklist rows from current data. Idempotent — it runs on every
# open, so it must be safe to call repeatedly and do no work when nothing changed.
...
  • cls, not self. The handler is a no-argument classmethod — it acts on the model as a whole (materializing rows), not on one record.
  • It’s a side effect, not a data source. Use on_open to prepare the records a normal list then queries; use data_method to return computed data in place of the record query. They’re independent — a view can use either, both, or neither.
  • Make it idempotent and cheap. It fires on every open, so it must converge to the same state when nothing has changed. It runs once per navigation (opening the menu), not on pagination/sort/filter.
  • Failures never block the view. An error in the handler is logged and swallowed — the screen still opens (just without that refresh).
  • The method name comes from your trusted module YAML, never user input.