Add computed fields

Computed fields can also be defined to avoid storing duplicated data in the database. For example, as we have the start date and the end date of our opportunity we can always compute the duration the opportunity lasts. This is done with a Function field, which can be used to represent any kind of field.

Lets see how this can be done in opportunity.py file:

class Opportunity(ModelSQL, ModelView):
    ...
    duration = fields.Function(
        fields.TimeDelta("Duration"), 'compute_duration')
    ...
    def compute_duration(self, name):
        if self.start_date and self.end_date:
            return self.end_date - self.start_date
        return None

The first parameter of the Function field is another Field instance which defined the type of the field to mimic and on the second parameter, the getter, we must specify the name of the method used to compute the value.

Function fields are read-only be default, but we can make them writable by defining a setter attribute, which is a method to call to store the value. Similarly we can also provide a method to search or order on them. All the Function fields possibilities are explained on Function fields reference.

Warning

If you change the start date or the end date of the opportunity, you will notice that the days value is not updated until the record is saved. That’s because function fields are computed only on server side.

Note

We let you add the new field to the views.

Combine Function fields and on_change_with

On previous steps we learned how on_change and Function fields work. One interesting feature is to combine them to compute and update the value. So we can have a computed field that changes every time the user modifies one of the values of the form.

It’s a common pattern to use an on_change_with method as getter of a Function field, so the value is correctly computed on client side and then it reacts to the user input.

In order to achieve it the following changes must be done in opportunity.py file:

class Opportunity(ModelSQL, ModelView):
    ...
    duration = fields.Function(
        fields.TimeDelta("Duration"), 'on_change_with_duration')
    ...
    @fields.depends('start_date', 'end_date')
    def on_change_with_duration(self, name=None):
        if self.start_date and self.end_date:
            return self.end_date - self.start_date
        return None

The important facts are the following:

  • Add depends() decorator to react on user input

  • Change the name of the method to on_change_with_<field_name>

  • Add a default None value for the name argument as it won’t be supplied when the client updates the values reacting to user input.

Great, you designed a Function fields which reacts to the user input. Let’s go to the next step to add domain restrictions.