Model Methods and Decorators

In Odoo models, the class contains both field definitions and methods that handle business logic. A common example is a method designed to respond to a button click action.

For instance, clicking the "Confirm" button in a form view initiates a change in the record's state from 'Draft' to 'Confirmed.' The related function is first referenced in the XML configuration as shown below:

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

The form view includes a 'Confirm' button, with its corresponding Python method specified in the button's name attribute as "action_confirm."

state = fields.Selection([
    ('draft', 'Draft'),
    ('confirmed', 'Confirmed')
], default='draft')

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

In this case, "action_confirm" is a Python method that takes "self" as its first parameter. In Odoo model methods, "self" refers to the current recordset and can also be used to accept additional arguments if needed.

Decorators

Decorators allow you to alter or extend the behavior of a method by adding extra functionality before the method is executed and returned.

@api.autovacuum

This decorator is used for methods that are triggered by the daily vacuum cron job. It is applied to the ir.autovacuum model and is suitable for performing background tasks that do not need a separate scheduled job.

@api.constrains(*args)

The decorator receives field names from the specific model as its arguments. When any of these fields are modified, the corresponding method is executed.

It is important to understand that @constrains only accepts direct field names and does not support dotted field paths such as related fields (for example, partner_id.city).

The execution of a method decorated with @constrains depends on the usage of the specified fields in the model's create or write operations. This means the method will not be triggered during record creation unless the listed fields are included in the view or form.

To ensure that the constraint is consistently enforced, it may be necessary to override the create method explicitly.

@api.depends

This decorator is used to define the field dependencies for a "compute" method. Each argument should be a string that represents a dot-separated path of field names.

pname = fields.Char(compute='_compute_pname')

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

@api.depends_context(*args)

This decorator is used to declare context dependencies for a non-stored "compute" method. Each argument represents a key from the context dictionary that the method depends on.

price = fields.Float(compute='_compute_product_price')

@api.depends_context('pricelist')
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'])
        else:
            pricelist = self.env['product.pricelist'].get_default_pricelist()
        product.price = pricelist.get_products_price(product).get(product.id, 0.0)

@api.model

This decorator is applied to a method where "self" refers to a recordset, but the actual records in the set are not important. Only the model itself is relevant for the method’s execution.

@api.model
def method(self, args):
    # Add your method here

@api.model_create_multi

This decorator is used for methods that create multiple records at once. The method should accept a list of dictionaries, where each dictionary contains the values for a single record to be created.

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

@api.onchange(*args)

The onchange decorator accepts field names from the view as arguments. When any of these fields are changed, the corresponding method is executed. The method runs on a pseudo-record that includes the current values from the form. Any changes made to fields within this record are automatically reflected back to the client interface.

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

Notes:

  • Only direct field names are supported, not relational fields like partner_id.city.
  • Runs on pseudo-records — avoid CRUD operations like create(), write(), or unlink().
  • Instead, assign values directly or use update().
  • Cannot directly modify one2many or many2many fields.

@api.ondelete(*, at_uninstall)

The ondelete decorator defines a method that will be triggered during the unlink() operation. It is often used to raise an error to prevent record deletion under certain conditions.

@api.ondelete(at_uninstall=False)
def _unlink_if_user_inactive(self):
    if any(user.active for user in self):
        raise UserError("Can't delete an active user!")

# same as above but with _unlink_except_* as method name
@api.ondelete(at_uninstall=False)
def _unlink_except_active_user(self):
    if any(user.active for user in self):
        raise UserError("Can't delete an active user!")

Parameter:

  • at_uninstall (boolean): If set to True, the method is also executed during module uninstallation. Default is False.

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

This decorator adapts a method’s return value to match the Odoo API style. It ensures correct return values whether the method is called in traditional API style (IDs, list of IDs, False) or recordset style.

  • model: The current model (string or 'self').
  • downgrade: A function that converts a recordset into a traditional-style output.
  • upgrade: A function that converts a traditional-style output into a recordset.
@api.model
@api.returns('res.partner')
def find_partner(self, arg):
    # return some record

# output depends on call style:
partner_id = model.find_partner(cr, uid, arg, context=context)   # traditional
partner_record = self.find_partner(arg)                         # recordset

Note: Decorators are inherited automatically — overriding a decorated method keeps its @returns(model) behavior.

whatsapp_icon
location

Calicut

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

location

Kochi

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

location

Bangalore

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

Send Us A Message