Skip to content

ORM Methods ​

Examples target Odoo 18 and later unless marked. Tabs show the Odoo 16 and 17 versions where the API differs.

Use this page as a quick reference for the Odoo ORM methods you call most: creating, searching, updating and deleting records, building domains, working with recordsets and the environment, and customising display names and name search.

Quick CRUD ​

The four calls below cover the basic life cycle of a record: create() adds one, search() finds records, write() changes them and unlink() deletes them. The sections under CRUD in detail cover single and multiple records for each call.

python
# Create a record
self.env['model.name'].create({'field': value})
python
# Read records
records = self.env['model.name'].search([('field', '=', value)])
python
# Update records
record.write({'field': new_value})
python
# Delete records
record.unlink()

Advanced usage ​

These patterns build on the basic methods for cases where a single condition or call is not enough.

Combine domains with OR ​

A domain is a list of conditions joined by AND by default. Put the '|' operator before two conditions to join them with OR, as in the example below, where field1 must match and either field2 or field3 must match.

python
records = self.env['model.name'].search([
    ('field1', '=', value1),
    '|', ('field2', '!=', value2), ('field3', 'in', [1, 2, 3]),
])

CRUD in detail ​

These sections repeat the four CRUD calls from Quick CRUD and add the single-record and multi-record forms.

Create records ​

Call create() with a dictionary to add one record, or with a list of dictionaries to add several records in one call, which is faster than creating them in a loop.

python
# Create a single record
self.env['model.name'].create({
    'field1': value1,
    'field2': value2,
})

# Create multiple records
self.env['model.name'].create([
    {'field1': value1, 'field2': value2},
    {'field1': value3, 'field2': value4},
])

Read records ​

Use search() to find records by domain, browse() to load records from known IDs, read() to get field values as dictionaries, and attribute access to read a single field value.

python
# Search records
records = self.env['model.name'].search([('field', '=', value)])

# Browse by ID
record = self.env['model.name'].browse(id)

# Read specific fields
values = record.read(['field1', 'field2'])

# Get field value
value = record.field_name

Update records ​

Call write() on a record or a recordset with a dictionary of new values. On a recordset, one call updates every record in it.

python
# Update single record
record.write({
    'field1': new_value1,
    'field2': new_value2,
})

# Update multiple records
records.write({
    'field1': new_value1,
    'field2': new_value2,
})

Delete records ​

Call unlink() on a record or a recordset to delete it permanently. Deleting cannot be undone, so check the recordset first.

python
# Delete single record
record.unlink()

# Delete multiple records
records.unlink()

Search methods ​

Use search() with a domain, a list of conditions, to find records. The sections below cover the domain syntax and the operators you can use.

Search domain ​

A search domain is a list of (field, operator, value) tuples passed to search(). Conditions are joined with AND unless you add '|' (OR), '&' (AND) or '!' (NOT) in front of them, which Odoo reads in prefix notation.

python
# Basic search
records = self.env['model.name'].search([
    ('field1', '=', value1),
    ('field2', '!=', value2),
])

# Complex conditions
records = self.env['model.name'].search([
    '|',  # OR
    '&',  # AND
    ('field1', '=', value1),
    ('field2', '!=', value2),
    ('field3', 'in', [value3, value4]),
])

Search operators ​

Use these operators in the second position of a domain tuple. Use ilike for case-insensitive text matching and in to match any value in a list.

python
# Common operators
'='     # equals
'!='    # not equals
'>'     # greater than
'>='    # greater than or equal
'<'     # less than
'<='    # less than or equal
'like'  # pattern matching
'ilike' # case-insensitive pattern matching
'in'    # in list
'not in'  # not in list

Recordset operations ​

A recordset is a collection of records. You can filter, map and sort it in Python without extra database queries.

Filtering ​

filtered() keeps the records for which a Python function returns true, filtered_domain() keeps the records that match a domain without querying the database again, and mapped() returns the values of a field (or a recordset for a relational field) across all records. Use these on a recordset you already loaded instead of running a new search().

python
# Filter records
filtered_records = records.filtered(lambda r: r.field > value)

# Filter by domain
filtered_records = records.filtered_domain([('field', '=', value)])

# Map to field values
values = records.mapped('field_name')

Sorting ​

sorted() returns a new recordset ordered by a field name or a key function. Pass reverse=True for descending order.

python
# Sort by field
sorted_records = records.sorted('field_name')
sorted_records = records.sorted(key=lambda r: r.field_name)

# Reverse sort
reversed_records = records.sorted('field_name', reverse=True)

Environment methods ​

The environment (self.env) holds the current user, company and context, and lets you run code as a different user or company.

Context ​

The context is a dictionary of flags that travels with the environment. Read it with self.env.context, use with_context() to get a copy of the recordset with extra keys, and use with_company() to run code as a different company.

python
# Get context
context = self.env.context

# Update context
self.with_context(key=value)
self.with_context(dict(key=value))

# Switch company
self.with_company(company_id)

User ​

self.env.user is the user running the code. Use it to read the user's companies and to test group membership with has_group() before you allow an action.

python
# Get current user
user = self.env.user

# Get user companies
companies = self.env.user.company_ids

# Check access rights
self.env.user.has_group('group_name')

Common patterns ​

These are the ORM customisations you meet most often when extending a model.

Display name ​

To change how a record is shown in many2one fields and breadcrumbs, set the computed display_name field. Odoo 17 deprecates name_get() in favour of display_name, and Odoo 18 removes name_get(), so override _compute_display_name in Odoo 17 and later and name_get() in Odoo 16 and earlier.

python
@api.depends('code', 'name')
def _compute_display_name(self):
    for record in self:
        record.display_name = f"{record.code} - {record.name}"

Name search runs when a user types in a many2one field. To search more than the name field, such as a code, override the hook for your version: name_search (or _name_search) in Odoo 16, _name_search in Odoo 17 and _search_display_name in Odoo 18 and later. Starting in Odoo 18, _search_display_name returns a domain and name_search is no longer the hook to override.

python
@api.model
def _search_display_name(self, operator, value):
    if operator in ('ilike', 'like', '=', '=like', '=ilike') and value:
        return ['|', ('name', operator, value), ('code', operator, value)]
    return super()._search_display_name(operator, value)

Default get ​

default_get() returns the initial values of a new record's fields. Override it to set defaults that depend on the context or on other data, and call super() first to keep the standard defaults.

python
def default_get(self, fields_list):
    defaults = super().default_get(fields_list)
    defaults['field'] = self.env.context.get('default_field', False)
    return defaults

Built by Cybrosys Technologies.