Business models and the relation between them are the key components of any Odoo module. Each business model consists of fields, which can be of two types: Basic (or Scalar) fields and Relational fields. Basic fields are used to represent simple values such as numbers or texts and Relational fields are used to represent relations between models.
In certain aspects we may also want to link between fields or sometimes the value of a particular field may be related to another field or more than one field. In such cases, we can use the concept of Compute field and Onchanges in Odoo.
Here in the blog, we will discuss the difference between the Compute and Onchange fields in Odoo.
Computed Fields
A Computed field can be simply defined as a field for which the value is computed using a method rather than directly reading from the user. This may be a Basic field or Relational field with an additional attribute ‘compute’ within the field definition. Consider a business model to manage Rental orders, where the total rent amount is calculated from the duration and price for the unit duration. For this purpose let us define a monetary field total_rent as in the following code which is a computed field where the value of this field is computed using the value of another field or more than one field.
from odoo import models, fields
class VehicleRental(models.Model):
_name = "vehicle.rental"
_description = "Vehicle Rental"
hour_rate = fields.Monetary(string="Hour Rate",)
hours = fields.Integer(string="Hours")
total_rent = fields.Monetary(string='Total Rent',
compute='_compute_total_rent')
def _compute_total_rent(self):
for record in self:
record.total_rent = record.hour_rate*record.hours
In the Vehicle Rental module that we have defined, there is a computed field total_rent for which the value is calculated by multiplying the hourly rate and total hours. We know that in the case of other fields, the values are stored in a database and are retrieved directly from there. But the value of a computed field is not stored in the database. They are computed using the compute method specified in the model on demand. Hence the compute method in a model will be executed each time on the updation. Moreover, every compute method must assign a value to the computing field, otherwise, the system will encounter an error.
Depends
The Depends function depends(*args) returns a decorator that specifies the field dependencies of a “compute” function. For the depends() decorator each argument must be a string and it is also possible to pass a single method as an argument. In such cases, the dependencies are set by calling the defined method.
Usually, the value of computed fields is dependent on other fields therefore, it is better to include the dependencies in the compute method. The ORM expects the developer to specify those dependencies with the decorator depends() on the method. The given dependencies are used by ORM to trigger the compute method whenever any change occurs in the dependent fields.
from odoo import models, fields, api
class VehicleRental(models.Model):
_name = "vehicle.rental"
_description = "Vehicle Rental"
hour_rate = fields.Monetary(string="Hour Rate",)
hours = fields.Integer(string="Hours")
total_rent = fields.Monetary(string='Total Rent',
compute='_compute_total_rent')
@api.depends('hour_rate', 'hours')
def _compute_total_rent(self):
for record in self:
record.total_rent = record.hour_rate*record.hours
We can also use paths through a field as a dependency like @api.depends(move_id.state)
The relational field used can be Many2many, One2many, or Many2one and for all kinds of relational fields.
Here is an example for setting the argument as a function.
from odoo import models, fields, api
class VehicleRental(models.Model):
_name = "vehicle.rental"
_description = "Vehicle Rental"
hour_rate = fields.Monetary(string="Hour Rate",)
hours = fields.Integer(string="Hours")
total_rent = fields.Monetary(string='Total Rent',
compute='_compute_total_rent')
def arguments(self):
return ['hour_rate', 'hours']
@api.depends(lambda self: self.arguments())
def _compute_total_rent(self):
for record in self:
record.total_rent = record.hour_rate*record.hours
As we are clear on the Computed Fields let's now move on to understand what the Onchange fields are in Odoo in the next section of the blog.
Onchange
onchange(*args) returns a decorator to decorate an “onchange” method for given fields. Each argument for onchange must be a single field where both scalar field and relational field can be used as arguments. If an argument with a dotted name(eg: partner_id.name) is given, then it will be ignored by the decorator. Moreover, the onchange() returns a recordset of pseudo-records hence, it is always better to avoid calling any one of the CRUD methods ( create() , read() , write() , unlink() ).
The onchange mechanism in Odoo enables the feature to modify or update the value of a field if any update is done on the other fields. Furthermore, the onchange will be triggered when one of the given fields is modified in the form view, that is, they are only triggered in the form view. In the given example the Reference for the rental order is achieved from a method. The onchange() decorator enables the feature to trigger the onchange method whenever the vehicle_id field in the form changes its value.
from odoo import models, fields, api
class VehicleRental(models.Model):
_name = "vehicle.rental"
_description = "Vehicle Rental"
name = fields.Char(string="Ref.")
vehicle_id = fields.Many2one('fleet.vehicle', string="Vehicle")
@api.onchange('vehicle_id')
def _onchange_vehicle(self):
self.name = "Rental Order for %s" % self.vehicle_id.name
The onchange methods are not automatically triggered when creating a record by code or programmatically. It is also important to remember that each argument for the decorator must be a single field value. As we have understood what Compute and Onchange fields are, let's now move on to understanding the difference between them in the next section of the blog.
Difference between Compute and Onchange
Now let us see what are the differences between Compute and Onchange.
1. Compute methods are private by convention.
2. On the one hand the value of a computed field is not stored in the database but it is computed every time when the field is accessed. Therefore, we can say that the compute method is executed each time when we try to access the field. Moreover, opening a tree view or form view which includes the computed field can be said as an example. On the other hand, the ‘onchange’ mechanism provides a way for the client interface to update a form without saving anything to the database whenever the user has filled in a field value. Onchange is triggered only when one of the given attributes changes its value.
3. Every compute method should assign a value to the computed field therefore, it is necessary to ensure that the compute method will assign a value to the field on every condition. However, it is possible to define an onchange method other than assigning a value for a field based on another field value-changing process. Onchange functions can return non-blocking error messages also.
4. Compute methods are triggered whenever the computed field is accessed, whether in views or by code. That is they are triggered outside of the context of a form view also thus it's always better to prefer computed fields. The onchange methods are not automatically triggered when creating a record through code or programmatically. Hence it is not a good idea to use an onchange concept for adding business logic to your model.
5. Computed fields are easier to debug since it is very easy to get the corresponding function from where the value of the field is actually set. The compute method can be found from the field definition. However, in the case of onchange it will be more difficult for one to figure out where the value is exactly coming from since several onchange methods may set the same fields.
6. Since the compute method executes on accessing the computed field there is a possibility of getting multiple records in self(). So it is important to remember that one must iterate through the self() to get rid of singleton error. As the onchange method is triggered only on changing the values it is not required to iterate through self since we will be getting a single record in self() For compute dependencies it is possible to use a dotted name for argument list and it is also possible to pass a single method as an argument.
7. The arguments used for onchange() decorator must be simple field names. If dotted names are used then it will be ignored by the decorator. It is not recommended to use any of the CRUD methods in an onchange function since it returns a set of pseudo-records.