Skip to content

API Decorators ​

Examples target Odoo 18 and 19 unless marked. Platform covers Odoo 11 to 20.

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': '18.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")

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
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'
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

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

Built by Cybrosys Technologies.