Derived values
A copy takes its work’s shelf. An order line takes the product’s unit and price. A loan starts today. Three times the same thing: a field that follows from another.
from kewos.orm.derived_defaults import DerivedDefault
registry.register_model( "library_copy", module="library", fields=[...], derived_defaults=( DerivedDefault(field="shelf", source="title_id.genre"), ),)Five sources, no code at all
source | What it evaluates to |
|---|---|
"@current_user" | The current user (on a Link to the user). |
"@current_user_record:<field>" | The record whose <field> is the current user. The reverse lookup. |
"@today", "@current_month_start", "@current_month_end" | Today, or the first / last day of the month. |
"<field>" | A sibling field’s value on the same record. |
"<relation>.<attribute>" | One relation hop: load the Link’s target and read its attribute. |
It is a closed list, and that is the point. A field computed by user-supplied Python would be arbitrary code execution (rule 37). Here the declaration is a string, the kernel resolves it, and there is nothing to execute.
At creation: fill if empty
The derivation runs server-side, inside the session, so it applies whatever the door: the interface, the API, an import.
POST /api/v1/library_loan {"copy_id":1,"member_id":1,"due_on":"2026-10-01"}
-> {"id":19, "taken_on":"2026-09-06", ...}taken_on was not in the request: it is today. And a supplied value is never overwritten:
the rule is “fill if empty”, not “impose”.
In the form: recompute on change
The interactive counterpart is the recompute. When the user changes a field a derivation depends on, the form asks the server for the new values, before saving:
POST /api/v1/library_copy/recompute {"values": {"title_id": 1, "barcode": "X-1"}, "changed": "title_id"} -> {"values": {"shelf": "fiction", "name": "X-1 - fiction"}, ...}
POST /api/v1/library_copy/recompute {"values": {"title_id": 2, "barcode": "X-1"}, "changed": "title_id"} -> {"values": {"shelf": "science", "name": "X-1 - science"}, ...}Title 1 is a novel, title 2 a science book: the shelf follows. Note the computed name follows
too, in the same round trip. The frontend computed nothing: it sent the draft and merged the
answer.
A required field can be derived
A nullable=False field with a derivation may be omitted from the call: it is filled before
the write. That was false until this page: request-body validation demanded the field before the
session had its turn, so the “whatever the door” promise did not hold for the API. Two of the
product’s models were in that case (inventory_movement and its two locations, derived from the
parent operation).
And if the derivation finds nothing, the database refuses the row: the server then answers 400 naming the field, not a 500 and not a sentence about duplicates.