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.
What it is — and isn’t
Section titled “What it is — and isn’t”- 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_methodmust return — e.g.Listwants record-shaped rows,Calendarwants dated records,TimelineHeatmapwants a{ buckets, rows }matrix. Any view type can opt in, with its own documented shape.
Declaring it
Section titled “Declaring it”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_loadThe 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_viewImplementing the method
Section titled “Implementing the method”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, notcls. Acls-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 ...). Everydata_methodis aselfinstance method.
Live params
Section titled “Live params”The front end passes the view’s current state as the method kwargs, so navigating (changing the period, paging the window) re-queries:
| View type | Params passed |
|---|---|
| TimelineHeatmap | bucket (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.)
How the framework consumes it
Section titled “How the framework consumes it”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.
Gating the chrome
Section titled “Gating the chrome”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.
Refreshing on open (on_open)
Section titled “Refreshing on open (on_open)”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_viewclass 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, notself. 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_opento prepare the records a normal list then queries; usedata_methodto 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.