Recordsets

New in version 8.0: This page documents the New API added in Odoo 8.0 which should be the
primary development API going forward. It also provides information about
porting from or bridging with the “old API” of versions 7 and earlier, but
does not explicitly document that API. See the old documentation for that.

Interaction with models and records is performed through recordsets, a sorted
set of records of the same model.

Warning

contrary to what the name implies, it is currently possible for
recordsets to contain duplicates. This may change in the future.

Methods defined on a model are executed on a recordset, and their self is
a recordset:

classAModel(models.Model):_name='a.model'defa_method(self):# self can be anywhere between 0 records and all records in the# databaseself.do_operation()

Iterating on a recordset will yield new sets of a single record
(“singletons”), much like iterating on a Python string yields strings of a
single characters:

Field access

Recordsets provide an “Active Record” interface: model fields can be read and
written directly from the record as attributes, but only on singletons
(single-record recordsets).
Field values can also be accessed like dict items, which is more elegant and
safer than getattr() for dynamic field names.
Setting a field’s value triggers an update to the database:

Record cache and prefetching

Odoo maintains a cache for the fields of the records, so that not every field
access issues a database request, which would be terrible for performance. The
following example queries the database only for the first statement:

record.name# first access reads value from databaserecord.name# second access gets value from cache

To avoid reading one field on one record at a time, Odoo prefetches records
and fields following some heuristics to get good performance. Once a field must
be read on a given record, the ORM actually reads that field on a larger
recordset, and stores the returned values in cache for later use. The prefetched
recordset is usually the recordset from which the record comes by iteration.
Moreover, all simple stored fields (boolean, integer, float, char, text, date,
datetime, selection, many2one) are fetched altogether; they correspond to the
columns of the model’s table, and are fetched efficiently in the same query.

Consider the following example, where partners is a recordset of 1000
records. Without prefetching, the loop would make 2000 queries to the database.
With prefetching, only one query is made:

forpartnerinpartners:printpartner.name# first pass prefetches 'name' and 'lang'# (and other fields) on all 'partners'printpartner.lang

The prefetching also works on secondary records: when relational fields are
read, their values (which are records) are subscribed for future prefetching.
Accessing one of those secondary records prefetches all secondary records from
the same model. This makes the following example generate only two queries, one
for partners and one for countries:

countries=set()forpartnerinpartners:country=partner.country_id# first pass prefetches all partnerscountries.add(country.name)# first pass prefetches all countries

Set operations

Recordsets are immutable, but sets of the same model can be combined using
various set operations, returning new recordsets. Set operations do not
preserve order.

record in set returns whether record (which must be a 1-element
recordset) is present in set. record not in set is the inverse
operation

set1 | set2 returns the union of the two recordsets, a new recordset
containing all records present in either source

set1 & set2 returns the intersection of two recordsets, a new recordset
containing only records present in both sources

set1 - set2 returns a new recordset containing only records of set1
which are not in set2

Other recordset operations

Recordsets are iterable so the usual Python tools are available for
transformation (map(), sorted(),
itertools.ifilter, …) however these return either a
list or an iterator, removing the ability to
call methods on their result, or to use set operations.

applies the provided function to each record in the recordset, returns
a recordset if the results are recordsets:

# returns a list of summing two fields for each record in the setrecords.mapped(lambdar:r.field1+r.field2)

The provided function can be a string to get field values:

# returns a list of namesrecords.mapped('name')# returns a recordset of partnersrecord.mapped('partner_id')# returns the union of all partner banks, with duplicates removedrecord.mapped('partner_id.bank_ids')

Environment

The Environment stores various contextual data used by
the ORM: the database cursor (for database queries), the current user
(for access rights checking) and the current context (storing arbitrary
metadata). The environment also stores caches.

All recordsets have an environment, which is immutable, can be accessed
using env and gives access to the current user
(user), the cursor
(cr) or the context
(context):

Altering the environment

creates a new environment with the provided user set, uses the
administrator if none is provided (to bypass access rights/rules in safe
contexts), returns a copy of the recordset it is called on using the
new environment:

Computed fields

Fields can be computed (instead of read straight from the database) using the
compute parameter. It must assign the computed value to the field. If
it uses the values of other fields, it should specify those fields using
depends():

Related fields

A special case of computed fields are related (proxy) fields, which provide
the value of a sub-field on the current record. They are defined by setting
the related parameter and like regular computed fields they can be
stored:

nickname=fields.Char(related='user_id.partner_id.name',store=True)

onchange: updating UI on the fly

When a user changes a field’s value in a form (but hasn’t saved the form yet),
it can be useful to automatically update other fields based on that value
e.g. updating a final total when the tax is changed or a new invoice line is
added.

computed fields are automatically checked and recomputed, they do not need
an onchange

for non-computed fields, the onchange() decorator is used
to provide new field values:

@api.onchange('field1','field2')# if these fields are changed, call methoddefcheck_change(self):ifself.field1<self.field2:self.field3=True

the changes performed during the method are then sent to the client program
and become visible to the user

Both computed fields and new-API onchanges are automatically called by the
client without having to add them in views

It is possible to suppress the trigger from a specific field by adding
on_change="0" in a view:

<fieldname="name"on_change="0"/>

will not trigger any interface update when the field is edited by the user,
even if there are function fields or explicit onchange depending on that
field.

Note

onchange methods work on virtual records assignment on these records
is not written to the database, just used to know which value to send back
to the client

Warning

It is not possible for a one2many or many2many field to modify
itself via onchange. This is a webclient limitation - see #2693.

Low-level SQL

The cr attribute on environments is the
cursor for the current database transaction and allows executing SQL directly,
either for queries which are difficult to express using the ORM (e.g. complex
joins) or for performance reasons:

self.env.cr.execute("some_sql",param1,param2,param3)

Because models use the same cursor and the Environment
holds various caches, these caches must be invalidated when altering the
database in raw SQL, or further uses of models may become incoherent. It is
necessary to clear caches when using CREATE, UPDATE or DELETE in
SQL, but not SELECT (which simply reads the database).

Clearing caches can be performed using the
invalidate_cache() method of the
BaseModel object.

Compatibility between new API and old API

Odoo is currently transitioning from an older (less regular) API, it can be
necessary to manually bridge from one to the other manually:

RPC layers (both XML-RPC and JSON-RPC) are expressed in terms of the old
API, methods expressed purely in the new API are not available over RPC

overridable methods may be called from older pieces of code still written
in the old API style

The big differences between the old and new APIs are:

values of the Environment (cursor, user id and
context) are passed explicitly to methods instead

record data (ids) are passed explicitly to
methods, and possibly not passed at all

methods tend to work on lists of ids instead of recordsets

By default, methods are assumed to use the new API style and are not callable
from the old API style.

Tip

calls from the new API to the old API are bridged

when using the new API style, calls to methods defined using the old API
are automatically converted on-the-fly, there should be no need to do
anything special:

>>> # method in the old API style>>> defold_method(self,cr,uid,ids,context=None):... printids>>> # method in the new API style>>> defnew_method(self):... # system automatically infers how to call the old-style... # method from the new-style method... self.old_method()>>> env[model].browse([1,2,3,4]).new_method()[1, 2, 3, 4]

For Selection, the value should match the
selection values (generally str, sometimes
int)

For Many2one, the value should be the
database identifier of the record to set

Other non-relational fields use a string for value

Danger

for historical and compatibility reasons,
Date and
Datetime fields use strings as values
(written and read) rather than date or
datetime. These date strings are
UTC-only and formatted according to
odoo.tools.misc.DEFAULT_SERVER_DATE_FORMAT and
odoo.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT

One2many and
Many2many use a special “commands” format to
manipulate the set of records stored in/associated with the field.

This format is a list of triplets executed sequentially, where each
triplet is a command to execute on the set of records. Not all
commands apply in all situations. Possible commands are:

(0, _, values)

adds a new record created from the provided value dict.

(1, id, values)

updates an existing record of id id with the values in
values. Can not be used in create().

(2, id, _)

removes the record of id id from the set, then deletes it
(from the database). Can not be used in create().

(3, id, _)

removes the record of id id from the set, but does not
delete it. Can not be used on
One2many. Can not be used in
create().

(4, id, _)

adds an existing record of id id to the set. Can not be
used on One2many.

(5, _, _)

removes all records from the set, equivalent to using the
command 3 on every record explicitly. Can not be used on
One2many. Can not be used in
create().

(6, _, ids)

replaces all existing records in the set by the ids list,
equivalent to using the command 5 followed by a command
4 for each id in ids.

Note

Values marked as _ in the list above are ignored and
can be anything, generally 0 or False.

fields (list) – list of fields present in the list view specified on the object

groupby (list) – list of groupby descriptions by which the records will be grouped.
A groupby description is either a field (then it will be grouped by that field)
or a string ‘field:groupby_function’. Right now, the only functions supported
are ‘day’, ‘week’, ‘month’, ‘quarter’ or ‘year’, and they only make sense for
date/datetime fields.

Environment swapping

Returns a new version of this recordset attached to the provided
user.

By default this returns a SUPERUSER recordset, where access
control and record rules are bypassed.

Note

Using sudo could cause data access to cross the
boundaries of record rules, possibly mixing records that
are meant to be isolated (e.g. records from different
companies in multi-company environments).

It may lead to un-intuitive results in methods which select one
record among many - for example getting the default company, or
selecting a Bill of Materials.

Note

Because the record rules and access control will have to be
re-evaluated, the new recordset will not benefit from the current
environment’s data cache, so later data access may incur extra
delays while re-fetching from the database.
The returned recordset has the same prefetch object as self.

Returns a new version of this recordset attached to the provided
environment

Warning

The new environment will not benefit from the current
environment’s data cache, so later data access may incur extra
delays while re-fetching from the database.
The returned recordset has the same prefetch object as self.

parent_id

parent_left

parent_right

Method decorators

This module provides the elements for managing two different API styles,
namely the “traditional” and “record” styles.

In the “traditional” style, parameters like the database cursor, user id,
context dictionary and record ids (usually denoted as cr, uid,
context, ids) are passed explicitly to all methods. In the “record”
style, those parameters are hidden into model instances, which gives it a
more object-oriented feel.

env=Environment(cr,uid,context)# cr, uid, context wrapped in envmodel=env[MODEL]# retrieve an instance of MODELrecs=model.search(DOMAIN)# search returns a recordsetforrecinrecs:# iterate over the recordsprintrec.namerecs.write(VALUES)# update all records in recs

Methods written in the “traditional” style are automatically decorated,
following some heuristics based on parameter names.

Decorates a constraint checker. Each argument must be a field name
used in the check:

@api.one@api.constrains('name','description')def_check_description(self):ifself.name==self.description:raiseValidationError("Fields name and description must be different")

Invoked on the records on which one of the named fields has been modified.

Should raise ValidationError if the
validation failed.

Warning

@constrains only supports simple field names, dotted names
(fields of relational fields e.g. partner_id.customer) are not
supported and will be ignored

@constrains will be triggered only if the declared fields in the
decorated method are included in the create or write call.
It implies that fields not present in a view will not trigger a call
during a record creation. A override of create is necessary to make
sure a constraint will always be triggered (e.g. to test the absence of
value).

In the form views where the field appears, the method will be called
when one of the given fields is modified. The method is invoked on a
pseudo-record that contains the values present in the form. Field
assignments on that record are automatically sent back to the client.

The method may return a dictionary for changing field domains and pop up
a warning message, like in the old API:

return{'domain':{'other_id':[('partner_id','=',partner_id)]},'warning':{'title':"Warning",'message':"What is this?"},}

Warning

@onchange only supports simple field names, dotted names
(fields of relational fields e.g. partner_id.tz) are not
supported and will be ignored

Decorate a record-style method where self is expected to be a
singleton instance. The decorated method automatically loops on records,
and makes a list with the results. In case the method is decorated with
returns(), it concatenates the resulting instances. Such a
method:

Fields

Basic fields

The field descriptor contains the field definition, and manages accesses
and assignments of the corresponding field on records. The following
attributes may be provided when instanciating a field:

Parameters

string – the label of the field seen by users (string); if not
set, the ORM takes the field name in the class (capitalized).

help – the tooltip of the field seen by users (string)

readonly – whether the field is readonly (boolean, by default False)

required – whether the value of the field is required (boolean, by
default False)

index – whether the field is indexed in database (boolean, by
default False)

default – the default value for the field; this is either a static
value, or a function taking a recordset and returning a value; use
default=None to discard default values for the field

states – a dictionary mapping state values to lists of UI attribute-value
pairs; possible attributes are: ‘readonly’, ‘required’, ‘invisible’.
Note: Any state-based condition requires the state field value to be
available on the client-side UI. This is typically done by including it in
the relevant views, possibly made invisible if not relevant for the
end-user.

groups – comma-separated list of group xml ids (string); this
restricts the field access to the users of the given groups only

copy (bool) – whether the field value should be copied when the record
is duplicated (default: True for normal fields, False for
one2many and computed fields, including property fields and
related fields)

oldname (string) – the previous name of this field, so that ORM can rename
it automatically at migration

Computed fields

One can define a field whose value is computed instead of simply being
read from the database. The attributes that are specific to computed
fields are given below. To define such a field, simply provide a value
for the attribute compute.

Parameters

compute – name of a method that computes the field

inverse – name of a method that inverses the field (optional)

search – name of a method that implement search on the field (optional)

store – whether the field is stored in database (boolean, by
default False on computed fields)

compute_sudo – whether the field should be recomputed as superuser
to bypass access rights (boolean, by default False)
Note that this has no effects on non-stored computed fields

The methods given for compute, inverse and search are model
methods. Their signature is shown in the following example:

The compute method has to assign the field on all records of the invoked
recordset. The decorator odoo.api.depends() must be applied on
the compute method to specify the field dependencies; those dependencies
are used to determine when to recompute the field; recomputation is
automatic and guarantees cache/database consistency. Note that the same
method can be used for several fields, you simply have to assign all the
given fields in the method; the method will be invoked once for all
those fields.

By default, a computed field is not stored to the database, and is
computed on-the-fly. Adding the attribute store=True will store the
field’s values in the database. The advantage of a stored field is that
searching on that field is done by the database itself. The disadvantage
is that it requires database updates when the field must be recomputed.

The inverse method, as its name says, does the inverse of the compute
method: the invoked records have a value for the field, and you must
apply the necessary changes on the field dependencies such that the
computation gives the expected value. Note that a computed field without
an inverse method is readonly by default.

The search method is invoked when processing domains before doing an
actual search on the model. It must return a domain equivalent to the
condition: field operator value.

Related fields

The value of a related field is given by following a sequence of
relational fields and reading a field on the reached model. The complete
sequence of fields to traverse is specified by the attribute

Parameters

related – sequence of field names

Some field attributes are automatically copied from the source field if
they are not redefined: string, help, readonly, required (only
if all fields in the sequence are required), groups, digits, size,
translate, sanitize, selection, comodel_name, domain,
context. All semantic-free attributes are copied from the source
field.

By default, the values of related fields are not stored to the database.
Add the attribute store=True to make it stored, just like computed
fields. Related fields are automatically recomputed when their
dependencies are modified.

Company-dependent fields

Formerly known as ‘property’ fields, the value of those fields depends
on the company. In other words, users that belong to different companies
may see different values for the field on a given record.

Parameters

company_dependent – whether the field is company-dependent (boolean)

Incremental definition

A field is defined as class attribute on a model class. If the model
is extended (see Model), one can also extend
the field definition by redefining a field with the same name and same
type on the subclass. In that case, the attributes of the field are
taken from the parent class and overridden by the ones given in
subclasses.

For instance, the second class below only adds a tooltip on the field
state:

translate – enable the translation of the field’s values; use
translate=True to translate field values as a whole; translate
may also be a callable such that translate(callback, value)
translates value by using callback(term) to retrieve the
translation of terms.

Very similar to Char but used for longer contents, does not
have a size and usually displayed as a multiline text box.

Parameters

translate – enable the translation of the field’s values; use
translate=True to translate field values as a whole; translate
may also be a callable such that translate(callback, value)
translates value by using callback(term) to retrieve the
translation of terms.

Returns the given timestamp converted to the client’s timezone.
This method is not meant for use as a default initializer,
because datetime fields are automatically converted upon
display on client side. For default values fields.datetime.now()
should be used instead.

Parameters

timestamp (datetime) – naive datetime value (expressed in UTC)
to be converted to the client timezone

Inheritance and extension

Odoo provides three different mechanisms to extend models in a modular way:

creating a new model from an existing one, adding new information to the
copy but leaving the original module as-is

extending models defined in other modules in-place, replacing the previous
version

delegating some of the model’s fields to records it contains

Classical inheritance

When using the _inherit and
_name attributes together, Odoo creates a new
model using the existing one (provided via
_inherit) as a base. The new model gets all the
fields, methods and meta-information (defaults & al) from its base.

the second model has inherited from the first model’s check method and its
name field, but overridden the call method, as when using standard
Python inheritance.

Extension

When using _inherit but leaving out
_name, the new model replaces the existing one,
essentially extending it in-place. This is useful to add new fields or methods
to existing models (created in other modules), or to customize or reconfigure
them (e.g. to change their default sort order):

Note

Delegation

The third inheritance mechanism provides more flexibility (it can be altered
at runtime) but less power: using the _inherits
a model delegates the lookup of any field not found on the current model
to “children” models. The delegation is performed via
Reference fields automatically set up on the parent
model:

(name is 'ABC')
AND (language is NOT english)
AND (country is Belgium OR Germany)

Porting from the old API to the new API

bare lists of ids are to be avoided in the new API, use recordsets instead

methods still written in the old API should be automatically bridged by the
ORM, no need to switch to the old API, just call them as if they were a new
API method. See Automatic bridging of old API methods for more details.

fields.related and fields.function are replaced by using a normal
field type with either a related= or a compute= parameter

depends() on compute= methods must be complete,
it must list all the fields and sub-fields which the compute method
uses. It is better to have too many dependencies (will recompute the field
in cases where that is not needed) than not enough (will forget to recompute
the field and then values will be incorrect)

remove all onchange methods on computed fields. Computed fields are
automatically re-computed when one of their dependencies is changed, and
that is used to auto-generate onchange by the client

the decorators model() and multi() are
for bridging when calling from the old API context, for internal or pure
new-api (e.g. compute) they are useless

remove explicit definition of create_uid,
create_date,
write_uid and
write_date fields: they are now created as
regular “legitimate” fields, and can be read and written like any other
field out-of-the-box

when straight conversion is impossible (semantics can not be bridged) or the
“old API” version is not desirable and could be improved for the new API, it
is possible to use completely different “old API” and “new API”
implementations for the same method name using v7() and
v8(). The method should first be defined using the
old-API style and decorated with v7(), it should then be
re-defined using the exact same name but the new-API style and decorated
with v8(). Calls from an old-API context will be
dispatched to the first implementation and calls from a new-API context will
be dispatched to the second implementation. One implementation can call (and
frequently does) call the other by switching context.

Danger

using these decorators makes methods extremely difficult to
override and harder to understand and document

uses of _columns or
_all_columns should be replaced by
_fields, which provides access to instances of
new-style odoo.fields.Field instances (rather than old-style
odoo.osv.fields._column).

Non-stored computed fields created using the new API style are not
available in _columns and can only be
inspected through _fields

reassigning self in a method is probably unnecessary and may break
translation introspection

Environment objects rely on some threadlocal state,
which has to be set up before using them. It is necessary to do so using the
odoo.api.Environment.manage() context manager when trying to use
the new API in contexts where it hasn’t been set up yet, such as new threads
or a Python interactive environment:

Automatic bridging of old API methods

When models are initialized, all methods are automatically scanned and bridged
if they look like models declared in the old API style. This bridging makes
them transparently callable from new-API-style methods.

Methods are matched as “old-API style” if their second positional parameter
(after self) is called either cr or cursor. The system also
recognizes the third positional parameter being called uid or user and
the fourth being called id or ids. It also recognizes the presence of
any parameter called context.

When calling such methods from a new API context, the system will
automatically fill matched parameters from the current
Environment (for cr,
user and
context) or the current recordset (for id
and ids).

In the rare cases where it is necessary, the bridging can be customized by
decorating the old-style method:

disabling it entirely, by decorating a method with
noguess() there will be no bridging and methods will be
called the exact same way from the new and old API styles

defining the bridge explicitly, this is mostly for methods which are matched
incorrectly (because parameters are named in unexpected ways):

cr()

will automatically prepend the current cursor to explicitly provided
parameters, positionally

cr_uid()

will automatically prepend the current cursor and user’s id to explictly
provided parameters

cr_uid_ids()

will automatically prepend the current cursor, user’s id and recordset’s
ids to explicitly provided parameters

cr_uid_id()

will loop over the current recordset and call the method once for each
record, prepending the current cursor, user’s id and record’s id to
explicitly provided parameters.

Danger

the result of this wrapper is always a list when calling
from a new-API context

All of these methods have a _context-suffixed version
(e.g. cr_uid_context()) which also passes the current
context by keyword.

dual implementations using v7() and
v8() will be ignored as they provide their own “bridging”