Chapter 4 - Odoo 15 Development Book

Model Methods and Decorators

In Odoo models, the class includes field declarations and business logic methods. One simple type of method is the method defined for a button click.

For example,confirm button in a form view changes the state from ‘Draft’ to ‘Confirmed’

The function will be declared from the XML first :

button name="action_confirm" string="Confirm" type="object"/ 

The form view will have a ‘Confirm’ button, its python action is declared in the button ‘name’ attribute(action_confirm).

def action_confirm(self):
   self.state = 'confirmed'

Here, action_confirm is a python method with self as an argument. ‘self’, the first argument method of odoo models can also have additional arguments.


Decorators allow you to change the behavior of a method. Simply it adds some capability to a function and then returns it.


The daily vacuum cron job calls the methods decorated by this decorator (model ir.autovacuum).This is used for tasks that do not require a particular cron job.


The arguments passed to the decorator are fields from the specific model. The function will get called when any of the fields are changed.

@constrains only support field names, not dotted fields(related fields,

Only if the defined fields in the decorated method are included in the model's create or write call will invoked by @constrains. It means that fields that do not exist in a view will not cause the function to be called during record generation.

Override of create is required to ensure the constraint is always activated.

To ensure that a constraint is always activated, an override of create is required.


The field dependencies of the “compute” method can be declared with this decorator. Each argument must be a string consisting of a dot-separated sequence of field names:

pname = fields.Char(compute='_compute_pname')
@api.depends('', 'partner_id.is_company')
def _compute_pname(self):
    for record in self:
        if record.partner_id.is_company:
            record.pname = ( or "").upper()
            record.pname =


The context dependencies of a non stored “compute” method can be declared with this decorator. Each argument passed is a key in the context dictionary.

price = fields.Float(compute='_compute_product_price')
def _compute_product_price(self):
    for product in self:
        if product.env.context.get('pricelist'):
            pricelist = self.env['product.pricelist'].browse(product.env.context['pricelist'])
            pricelist = self.env['product.pricelist'].get_default_pricelist()
        product.price = pricelist.get_products_price(product).get(, 0.0)


Decorate a record-style method where self is a recordset, but its contents is not relevant, only the model is.

def method(self, args):
    //Add your method here


Decorate a method if the method is creating multiple records with list of dictionaries. It can be called with sing or list of dictionaries.

 record = model.create(vals)
records = model.create([vals, ...])


Fields in the view will be passed as arguments to the ‘onchange’ decorator. When the field from the view is modified, the method will be called. The method is invoked on a pseudo-record that contains the values present in the form. Field assignments on that record are automatically sent back to the client.

def _onchange_partner(self):
    self.message = "Dear %s" % ( or "")
    return {
       'warning': {'title': "Warning", 'message': "What is this?",'type': 'notification'},}

@onchange only supports simple field names,relational fields are not supported(

Since @onchange returns a recordset of pseudo-records, calling any one of the CRUD methods (create(), read(), write(), unlink()) on this recordset is undefined behaviour, as they potentially do not exist in the database yet. Instead of it, we can use the update() method to set the record’s fields or simply set the field as in the example.

An one2many or many2many field can't modify itself via onchange.

@api.ondelete(*, at_uninstall)

Mark a method to be executed during unlink().

Methods decorated with @ondelete should raise an error following some conditions, and by convention, the method should be named either _unlink_if_<condition> or _unlink_except_ <not_condition>.

def _unlink_if_user_inactive(self):
    if any( for user in self):
        raise UserError("Can't delete an active user!")
# same as above but with _unlink_except_* as method name
def _unlink_except_active_user(self):
    if any( for user in self):
        raise UserError("Can't delete an active user!")


at_uninstall (bool) – This decorated method will be called when the module which implements this function is uninstalled. It should almost always be False, so that module uninstallation does not trigger those errors.

@api.returns(model, downgrade=None, upgrade=None)

For methods that return model instances, return a decorator.


  • model – A model name, or 'self' for the current model
  • downgrade – A function downgrade(self, value, *args, **kwargs) to convert the record-style value to a traditional-style output
  • upgrade – A function upgrade(self, value, *args, **kwargs) to convert the traditional-style value to a record-style output

In the record-style, the parameters self, *args, and **kwargs are passed to the method

The decorator adjusts the output of the method to the api style: id, ids, or False for the traditional style, and recordset for the record style.

def find_partner(self, arg):
    # return some record
# output depends on call style: traditional vs record style
partner_id = model.find_partner(cr, uid, arg, context=context)
# recs = model.browse(cr, uid, ids, context)
partner_record = recs.find_partner(arg)

Note that the decorated method must satisfy that convention.

Those decorators are automatically inherited: A method that overrides a decorated existing method will be decorated with the same @returns(model).



Cybrosys Technologies Pvt. Ltd.
Neospace, Kinfra Techno Park
Kakkancherry, Calicut
Kerala, India - 673635



Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.



Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message