391 6

Understanding and implementing method decorators in Odoo

Understanding and implementing method decorators in Odoo

6 Min Read

Last updated at


In Python, method decorators are a powerful tool that allow for the modification or enhancement of a method's behavior without altering its actual code. They act as wrappers, providing a convenient way to add functionality to existing methods through a clean and readable syntax.

The Odoo method decorators are python functions defined in the Odoo API module. Method decorators are used by calling the decorator name with a preceding @​ above the method definition, which applies this decorator to this method.

from odoo import api, fields, models

class ModelName(models.Model):
    _name = 'model.name'
    _description = 'Model Name'

    @api.model
	def method(self, args):
	    ...

Although there are many method decorators in Odoo (see the official documentation), in this tutorial I will explain the more commonly used decorators.

api.depends

This decorator is used for computed field methods to specify other fields that should trigger the (re)calculation. Each argument must be a string that consists in a dot-separated sequence of field names.

from odoo import api, fields, models

class ResPartner(models.Model):
    _inherit = "res.partner"

    firstname = fields.Char("First name")
    lastname = fields.Char("Last name")
    fullname = fields.Char(compute="_compute_fullname", store=True)

    @api.depends("firstname", "lastname")
    def _compute_fullname(self):
        for record in self:
        	if record.firstname and record.lastname:
            	record.fullname = " ".join([record.firstname, record.lastname])

In this example, the name is automatically calculated by merging the value from the first name field and the second name field. In case the first name or the second name is updated, the compute method is triggered and the name will be recalculated.

api.onchange

Contrary to the api.depends​ decorator, this decorator is used for changes in the user interface. This decorator is triggered if a user edits a value in a particular field in the user interface. This method can change other field values, validate the field input, display a message to the user, or set a domain filter which limits the available options for relational fields. Note that it does not write store data directly, but it provides feedback to the user, which allows a user to change the data in the user interface.

from dateutil.relativedelta import relativedelta
from odoo import api, fields, models

class ResPartner(models.Model):
    _inherit = "res.partner"

    birthdate = fields.Date("Birthdate")
    age = fields.Integer("Age")

    @api.onchange("birthdate")
    def onchange_birthdate(self):
        if self.birthdate:
            self.age = relativedelta(fields.Date.today(), self.birthdate).years

When the birth date is changed, the decorator method is triggered. It calculates the age based on the current date and updates the age field.

Note

This decorator only supports simple field names, dotted names (fields of relational fields e.g. category_id.name​​​) are not supported and will be ignored.

Another possibility is that you can validate the birth date when it is is changed and return a notification or dialog when the validation fails.

from dateutil.relativedelta import relativedelta
from odoo import api, fields, models

class ResPartner(models.Model):
    _inherit = "res.partner"

    birthdate = fields.Date("Birthdate")

    @api.onchange("birthdate")
    def onchange_birthdate(self):
    	if self.birthdate:
	        age = relativedelta(fields.Date.today(), self.birthdate).years
	        if age < 18:
	        	return {
	        		"warning": {
	        			"title": "Age Restriction",
	        			"message": "The partner should be 18 years or older!",
	        		}
	        	}

When the birth date is updated in the user interface this method is called. It validates the birth date and if the calculated age is less than 18, a dialog is displayed with a warning.

Warning dialog

Inside the decorator method, the self argument is a virtual record that contains all the values from the user interface. This allows you for example to compare the current value from the user interface with the stored value. Sometimes this can be helpful for logging changes, allowing to revert these changes.

@api.onchange("birthdate")
def onchange_birthdate(self):
    log = "Birth date updated from %s to %s" % (self._origin.birthdate, self.birthdate)
    self._message_log(body=log)

If the birth date is changed this change is logged inside the log note.


api.constrains

This decorator is used for model validation methods and these methods run whenever any of the mentioned fields are changed. These methods should only validate the data and raise an exception if a check fails. The decorator will only be triggered if the specified fields are included in a create or write call, or if they are present in the view.

from odoo import api, fields, models
from odoo.exceptions import ValidationError

class ResPartner(models.Model):
    _inherit = "res.partner"

    firstname = fields.Char("First name")
    lastname = fields.Char("Last name")

    @api.constrains("firstname", "lastname")
    def _check_name(self):
        for record in self:
            if not record.firstname or not record.lastname:
            	raise ValidationError("Both first name and last name must be specified")

When the first name or the second name is updated, this decorator makes sure that both the first name and the second name are not empty. In case one of them is empty, or both are, an exception is raised and the user gets an error message (similar as the warning raised above).

Note

This decorator only supports simple field names, dotted names (fields of relational fields e.g. category_id.name​) are not supported and will be ignored.

api.model

In some instances the decorator method should work on a model level rather than the record level, such as methods for creating records or performing model-wide tasks. Methods decorated with @api.model​ are not bound to any specific record. Instead, they operate at the model level. This means that they don't require a record to be called and don't have access to any record-specific data (like self.id​ or self.name​ in a recordset). They are typically used for operations that apply to the model as a whole or when you need to perform a task that doesn't involve manipulating or querying individual records.

from odoo import api, fields, models

class ResPartner(models.Model):
    _inherit = "res.partner"

    @api.model
    def count_all_partners(self):
        return self.search_count([])

The method performs a search on the res.partner​ model without any specific domain ([]​ meaning all records) and returns the count of all found records. Being a model-wide method, it can be invoked without the need to reference a specific partner record. For instance, you could call this method from any part of your Odoo code like this: self.env['res.partner'].count_all_partners()​.

Conclusion

You should now be able to use these essential tools, allowing you to create more effective Odoo modules. Although I did not explain all decorators, but these are by far the most used decorators in Odoo. Moreover, with the information provided here, it will be relatively straightforward to understand how to implement other Odoo method decorators.

Share this post