In Odoo, decorators are small but powerful tools that change how a method behaves and interacts with the framework. They allow developers to control when a method runs, how it responds to changes, or even how it is exposed to users and external systems. By understanding these decorators, you can write cleaner, safer, and more efficient code that works seamlessly with Odoo’s ORM. Let’s go through the most important decorators with simple explanations and examples.
@api.constrains(*fields)
This decorator is used to validate data whenever certain fields are changed. Whenever a record is created or updated, Odoo will automatically run the constraint method. If the condition is not met, you can raise a validation error, and the transaction will be blocked.
Example:
from odoo import api, fields, models
from odoo.exceptions import ValidationError
class SaleOrder(models.Model):
_inherit = "sale.order"
amount_total = fields.Float()
@api.constrains('amount_total')
def _check_amount(self):
for order in self:
if order.amount_total <= 0:
raise ValidationError("The total amount must be greater than zero.")
Here, if a user tries to save a Sale Order with zero or negative total, Odoo will display a polite error message and stop the process.
@api.ondelete(at_uninstall=False)
Sometimes you want to control what happens when a record is deleted. This decorator lets you prevent deletions or add custom checks before they happen. If the condition is not satisfied, you can raise an exception. The at_uninstall option makes sure Odoo can still clean up data during module uninstallation.
Example:
@api.ondelete(at_uninstall=False)
def _check_order_delete(self):
for order in self:
if order.state == 'sale':
raise ValidationError("You cannot delete confirmed Sale Orders.")
In this example, confirmed Sale Orders cannot be deleted, which helps protect important business data.
@api.depends(*fields)
This decorator is the engine behind computed fields. Whenever one of the specified fields changes, Odoo will re-calculate the field value. It makes sure your data always stays up-to-date without writing extra code manually.
Example:
discounted_amount = fields.Float(compute="_compute_discounted_amount")
@api.depends('amount_total', 'discount_rate')
def _compute_discounted_amount(self):
for order in self:
order.discounted_amount = order.amount_total * (1 - order.discount_rate/100)
Here, discounted_amount is always recalculated whenever either amount_total or discount_rate changes.
@api.onchange(*fields)
The Onchange field is used to make forms more interactive. It triggers immediately when a user changes a field in the UI, even before the record is saved. It’s often used to auto-fill values, show warnings, or update domains in real time.
Example:
@api.onchange('partner_id')
def _onchange_partner_id(self):
if self.partner_id:
self.note = f"Order created for {self.partner_id.name}"Here, whenever the customer is changed on a Sale Order, a note is automatically updated in the form.
@api.model
This decorator is used for methods that do not depend on individual records but rather apply at the model level. It is often used for utility methods or when customizing the create method.
Example:
@api.model
def create(self, vals):
vals['name'] = vals.get('name', self.env['ir.sequence'].next_by_code('sale.order'))
return super().create(vals)
This ensures that every new Sale Order gets a unique sequence number, even if the user did not provide one.
@api.model_create_multi
Normally, the create method handles one record at a time. With this decorator, Odoo can pass multiple values at once, making record creation more efficient.
Example:
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
vals['state'] = 'draft'
return super().create(vals_list)
Now, even if multiple records are created in bulk, each will automatically start in draft state.
@api.private
Some methods are meant for internal use only and should not be exposed through RPC (like XML-RPC or JSON-RPC). Adding this decorator ensures they remain private.
Example:
@api.private
def _update_internal_status(self):
self.write({'state': 'processing'})
This method can be used within the system but is hidden from outside calls.
@api.readonly
This decorator makes sure that a method runs in a read-only mode. It can be very useful for report generation or performance-sensitive queries where no database modifications are needed.
Example:
@api.readonly
def get_report_data(self):
return self.env['res.partner'].search([]).read(['name', 'email'])
Here, the method simply reads partner data without ever risking a write.
@api.autovacuum
Some operations, like cleaning old records, should run automatically from time to time. This decorator allows you to mark methods that Odoo will call during its daily housekeeping (vacuum) task.
Example:
@api.autovacuum
def _cleanup_old_logs(self):
old_logs = self.search([('create_date', '<', fields.Datetime.now() - timedelta(days=30))])
old_logs.unlink()
This method automatically deletes logs older than 30 days.
@api.deprecated
Over time, methods may be replaced by newer ones. This decorator helps mark them as deprecated while still allowing existing code to work. It warns developers that the method should no longer be used.
Example:
from odoo import api, models
class ResPartner(models.Model):
_inherit = "res.partner"
@api.deprecated(reason="Replaced by _get_partner_address.", instead="_get_partner_address")
def get_address(self):
"""Deprecated: Use _get_partner_address instead."""
return self._get_partner_address()
def _get_partner_address(self):
return f"{self.street}, {self.city}" if self.street and self.city else self.name
*@api.depends_context(keys)
Sometimes, a computed field depends not only on other fields but also on the execution context — such as the current company, user, language, or date.
In those cases, using @api.depends_context tells Odoo to re-compute the field when any of the specified context keys change.
It’s especially useful in multi-company or multi-language environments where the result of a computed field can vary based on the context.
from odoo import api, fields, models
class ProductTemplate(models.Model):
_inherit = "product.template"
price_in_currency = fields.Float(compute="_compute_price_in_currency")
@api.depends('list_price')
@api.depends_context('company_id', 'lang')
def _compute_price_in_currency(self):
for product in self:
company = self.env.company
# Simulate conversion based on company currency
rate = company.currency_id.rate or 1
product.price_in_currency = product.list_price * rate
Odoo’s method decorators are powerful tools that simplify how your code interacts with the system. Each decorator has its own role in controlling data, ensuring consistency, and improving user experience. For instance, @api.constrains is used to validate data before saving, helping maintain accuracy and preventing invalid entries. @api.ondelete lets you manage what happens when a record is deleted, ensuring that important data isn’t accidentally removed. When you need to automatically recalculate fields based on changes, @api.depends keeps your computed fields up to date without extra effort.
Similarly, @api.onchange makes your forms more interactive by updating values or showing warnings as users modify fields, improving usability. @api.model is useful for model-level logic that doesn’t depend on a specific record, such as generating default values or overriding the create method. When dealing with bulk record creation, @api.model_create_multi enhances efficiency by handling multiple records in one go. For methods that should only be used inside the system, @api.private ensures they remain hidden from external access, adding a layer of security.
In cases where a method should only read data, @api.readonly ensures that no accidental changes are made, which is especially useful for reports or data extraction. The @api.autovacuum decorator automates background maintenance tasks like cleaning up old data, keeping your database healthy without manual intervention. Lastly, @api.deprecated is used to mark old methods as outdated, guiding developers to use newer alternatives while maintaining backward compatibility.
By understanding when and how to use these decorators, developers can write cleaner, safer, and more maintainable code. They make your modules behave more naturally within Odoo’s framework, reduce repetitive logic, and ensure your customizations remain efficient and professional
To read more about An Overview of Method Decorators in Odoo 18, refer to our blog An Overview of Method Decorators in Odoo 18.