Fields can have values calculated by a function, instead of simply reading a database stored value. A computed field is declared just like a regular field, but has the additional compute
argument defining the function used to calculate it.
In most cases computed fields involve writing some business logic, so we will develop this topic more in Chapter 7, ORM Application Logic - Supporting Business Processes. We will still explain them here, but will keep the business logic side as simple as possible.
Let's work on an example: Stages have a fold
field. We will add to To-do Tasks a computed field with the Folded? flag for the corresponding Stage.
We should edit the TodoTask
model in the todo_model.py
file to add the following:
# class TodoTask(models.Model): stage_fold = fields.Boolean( 'Stage Folded?', compute='_compute_stage_fold') @api.depends('stage_id.fold') def _compute_stage_fold(self): for task in self: task.stage_fold = task.stage_id.fold
The preceding code adds a new stage_fold
field and the _compute_stage_fold
method used to compute it. The function name was passed as a string, but it's also allowed to pass it as a callable reference (the function identifier with no quotes). In this case we should make sure the function is defined in the Python file before the field is.
The @api.depends
decorator is needed when the computation depends on other fields, as it usually does. It lets the server know when to recompute stored or cached values. One or more field names are accepted as arguments and dot-notation can be used to follow field relationships.
The computation function is expected to assign a value to the field or fields to compute. If it doesn't, it will error. Since self
is a record object, our computation here is simply to get the Folded? field using stage_id.fold
. The result is achieved by assigning that value (writing it) to the computed field, stage_fold
.
We won't be working yet on the views for this module, but you can make right now a quick edit on the task form to confirm if the computed field is working as expected: using the Developer Mode pick the Edit View option and add the field directly in the form XML. Don't worry: it will be replaced by the clean module view on the next upgrade.
The computed field we just created can be read, but it can't be searched or written. To enable these operations, we first need to implement specialized functions for them. Along with the compute
function, we can also set a search
function, implementing the search logic, and the inverse
function, implementing the write logic.
Using these, our computed field declaration becomes like this:
# class TodoTask(models.Model): stage_fold = fields.Boolean( string='Stage Folded?', compute='_compute_stage_fold', # store=False, # the default search='_search_stage_fold', inverse='_write_stage_fold')
And the supporting functions are:
def _search_stage_fold(self, operator, value): return [('stage_id.fold', operator, value)] def _write_stage_fold(self): self.stage_id.fold = self.stage_fold
The search
function is called whenever a (field, operator, value)
condition on this field is found in a search domain expression. It receives the operator
and value
for the search and is expected to translate the original search element into an alternative domain search expression.
The inverse
function performs the reverse logic of the calculation, to find the value to write on the computation's source fields. In our example, this means writing back on the stage_id.fold
field.
Computed field's values can also be stored on the database, by setting store = True
on their definition. They will be recomputed when any of their dependencies change. Since the values are now stored, they can be searched just like regular fields, and a search function is not needed.
The computed field we implemented in the previous section just copies a value from a related record into a model's own field. However this is a common usage that can be automatically handled by Odoo.
The same effect can be achieved using related fields. They make available, directly on a model, fields that belong to a related model, accessible using a dot-notation chain. This makes them usable in situations where dot-notation can't be used, such as UI form views.
To create a related field, we declare a field of the needed type, just like with regular computed fields, but instead of compute we use the related attribute with the dot-notation field chain to reach the desired field.
To-do Tasks are organized in customizable Stages and these is turn map into basic States. We will make the State value available directly on the Task model, so that it can be used for some client-side logic in the next chapter.
Similarly to stage_fold
, we will add a computed field on the task model, but this time using the simpler related field:
# class TodoTask(models.Model): stage_state = fields.Selection( related='stage_id.state', string='Stage State')
Behind the scenes, related fields are just computed fields that conveniently implement search
and inverse
methods. This means that we can search and write on them out of the box, without having to write any additional code.