API Decorators
Basic Decorators
@api.model
Used for methods that don't work on a specific record set.
python
@api.model
def create_default_config(self):
"""Create default configuration"""
return self.create({
'name': 'Default Config',
'active': True
})
@api.model
def get_system_info(self):
"""Get system information"""
return {
'version': '15.0',
'database': self.env.cr.dbname
}
@api.depends
Defines field dependencies for computed fields.
python
total_amount = fields.Float(compute='_compute_total_amount')
@api.depends('line_ids.price_total')
def _compute_total_amount(self):
for record in self:
record.total_amount = sum(record.line_ids.mapped('price_total'))
@api.depends('first_name', 'last_name')
def _compute_full_name(self):
for record in self:
record.full_name = f"{record.first_name} {record.last_name}"
@api.onchange
Triggers when field values change in the UI.
python
@api.onchange('partner_id')
def _onchange_partner_id(self):
if self.partner_id:
self.invoice_address = self.partner_id.street
self.payment_term_id = self.partner_id.property_payment_term_id
@api.onchange('product_id', 'quantity')
def _onchange_product_quantity(self):
if self.product_id and self.quantity:
self.price_unit = self.product_id.list_price
self.subtotal = self.quantity * self.price_unit
Constraint Decorators
@api.constrains
Validates field values and raises validation errors.
python
@api.constrains('email')
def _check_email_format(self):
for record in self:
if record.email and '@' not in record.email:
raise ValidationError("Invalid email format")
@api.constrains('start_date', 'end_date')
def _check_dates(self):
for record in self:
if record.start_date and record.end_date:
if record.start_date > record.end_date:
raise ValidationError("Start date must be before end date")
@api.model_create_multi
Optimizes batch record creation.
python
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if 'code' not in vals:
vals['code'] = self.env['ir.sequence'].next_by_code('model.code')
return super().create(vals_list)
Return Value Decorators
@api.returns
Specifies the return type and post-processing.
python
@api.returns('self', lambda value: value.id)
def get_parent_record(self):
return self.parent_id
@api.returns('res.partner')
def get_related_partners(self):
return self.partner_ids.filtered(lambda p: p.is_company)
@api.multi (Deprecated in v13+)
Legacy decorator for methods working on recordsets.
python
# Deprecated - use recordset methods directly
@api.multi
def action_confirm(self):
for record in self:
record.state = 'confirmed'
# Modern approach
def action_confirm(self):
for record in self:
record.state = 'confirmed'
Advanced Decorators
@api.autovacuum
Automatically called by the vacuum cleaner cron.
python
@api.autovacuum
def _gc_temp_files(self):
"""Clean up temporary files"""
temp_files = self.search([
('is_temp', '=', True),
('create_date', '<', fields.Datetime.now() - timedelta(days=7))
])
temp_files.unlink()
@api.model_cr
Low-level method with direct cursor access.
python
@api.model_cr
def init_database(self):
"""Initialize database with custom SQL"""
self.env.cr.execute("""
UPDATE res_company
SET currency_id = %s
WHERE currency_id IS NULL
""", (self.env.ref('base.USD').id,))
Combined Decorators
Multiple Dependencies
python
@api.depends('partner_id', 'partner_id.country_id', 'amount_total')
def _compute_tax_amount(self):
for record in self:
if record.partner_id and record.partner_id.country_id:
tax_rate = record.partner_id.country_id.tax_rate or 0.0
record.tax_amount = record.amount_total * tax_rate / 100
else:
record.tax_amount = 0.0
Onchange with Constraints
python
@api.onchange('quantity', 'price_unit')
def _onchange_quantity_price(self):
if self.quantity and self.price_unit:
self.total_price = self.quantity * self.price_unit
@api.constrains('quantity')
def _check_quantity_positive(self):
for record in self:
if record.quantity <= 0:
raise ValidationError("Quantity must be positive")
CRUD Method Decorators
Create Method
python
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
for record in records:
record._send_notification()
return records
Write Method
python
def write(self, vals):
result = super().write(vals)
if 'state' in vals:
self._trigger_state_change()
return result
Unlink Method
python
def unlink(self):
if any(record.state == 'confirmed' for record in self):
raise UserError("Cannot delete confirmed records")
return super().unlink()
Context and Environment
Working with Context
python
@api.model
def with_company_context(self, company_id):
return self.with_context(
allowed_company_ids=[company_id],
company_id=company_id
)
@api.depends('amount', 'currency_id')
def _compute_amount_company_currency(self):
for record in self:
if record.currency_id:
record.amount_company = record.currency_id._convert(
record.amount,
record.company_id.currency_id,
record.company_id,
record.date or fields.Date.context_today(record)
)
Sudo Operations
python
@api.model
def create_system_record(self, vals):
"""Create record with system privileges"""
return self.sudo().create(vals)
def get_private_data(self):
"""Access private data with sudo"""
return self.sudo().private_field
Error Handling Decorators
Custom Validation
python
@api.constrains('code')
def _check_code_unique(self):
for record in self:
if self.search_count([('code', '=', record.code), ('id', '!=', record.id)]) > 0:
raise ValidationError(f"Code '{record.code}' already exists")
@api.constrains('amount')
def _check_amount_limit(self):
for record in self:
if record.amount > 10000:
raise UserError("Amount cannot exceed 10,000")
Performance Decorators
Batch Processing
python
@api.model
def process_batch(self, record_ids, batch_size=100):
"""Process records in batches"""
records = self.browse(record_ids)
for batch in records.batch(batch_size):
batch._process_records()
def _process_records(self):
"""Process current recordset"""
for record in self:
record.processed = True
Common Patterns
State Management
python
@api.onchange('state')
def _onchange_state(self):
if self.state == 'draft':
self.can_edit = True
else:
self.can_edit = False
@api.depends('state')
def _compute_can_edit(self):
for record in self:
record.can_edit = record.state == 'draft'
Related Field Computation
python
@api.depends('partner_id.commercial_partner_id')
def _compute_commercial_partner(self):
for record in self:
record.commercial_partner_id = record.partner_id.commercial_partner_id
@api.depends('order_line.price_total')
def _compute_amount_total(self):
for order in self:
order.amount_total = sum(order.order_line.mapped('price_total'))
Migration Notes
Deprecated Decorators
@api.one
: Removed in v13, use recordset iteration@api.multi
: Deprecated in v13, methods work on recordsets by default@api.cr
: Use@api.model_cr
instead@api.v7
: Legacy compatibility, avoid in new code
Modern Patterns
python
# Old style (deprecated)
@api.one
def old_method(self):
return self.name
# New style
def new_method(self):
result = []
for record in self:
result.append(record.name)
return result