---
url: https://docs.cybrosys.com/cheatsheets/api.md
description: >-
  Odoo API cheatsheet: model decorators, manifest structure and common ORM API
  patterns with Odoo 18 examples.
---

# 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
```

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

### 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
```
