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.