Odoo New API
Welcome to Odoo new API guideline’s
09:23
A recordset is:
- An ordered collection of records.
- One concept to replace:
- browse lists,
- browse nulls.
- An instance of the model's class.
1. The recordset as a collection:
It implements a sequence and set operations:
Operation Supported:
RecordSet also support set operations you can add, union and intersect, ... recordset:
1. Addition:
recordsetA + recordsetB: Returns the concatenation of two recordsets.
2. Substration:
recordsetA - recordsetB: Return the recordset of all the records in 'recordsetA' that are not in 'recordsetB'.
3. Intersection:
recordsetA & recordsetB: Return the intersection of two recordsets. Note that recordset order is not preserved.
4. Union:
recordsetA | recordsetB: Return the union of two recordsets. Note that recordset order is not preserved.
5. Equivalent:
recordsetA == recordsetB: Test whether two recordsets are equivalent (up to reordering).
...etc
2. The recordset as a record:
It behaves just like former browse records:
print partner.nameprint partner['name']
print partner.parent_id.company_id.name
Except that updates are written to database:
partner.name = 'Agrolait'
partner.email = 'info@agrolait.com'
partner.parent_id = ... # another record
partner.email = 'info@agrolait.com'
partner.parent_id = ... # another record
If len(partners) > 1, do it on the first record:
print partners.name # name of first partner
print partners[0].name
partners.name = 'Agrolait' # assign to first partner
partners[0].name = 'Agrolait'
If len(partners) == 0, return the null value of the field:
print partners.name # False
print partners.parent_id # Empty recordset
partners.name = 'Foo' # Error
3. The recordset as an instance:
Methods of the model's class can be invoked on recordsets:
# calling convention: leave out cr, uid, ids, context
@api.multi
def write(self, values):
result = super(C, self).write(values)
# search returns a recordset instead of a list of ids
domain = [('id', 'in', self.ids), ('parent_id', '=', False)]
roots = self.search(domain)
# modify all records in roots
roots.write({'modified': True})
return result
The missing parameters are hidden inside the recordset.
Record creation in cache only:
You can create a new record with following method:
@api.model
def new(self, values={}):
""" new([values]) -> record """
Return a new record instance attached to the current environment and initialized with the provided 'value'. The record is not created in database, it only exists in memory.
New record is used to compute default values and perform onchanges.
Example:
record = self.new(values)
This will creates a new record with values, and attach 'self' to it.
Odoo New API: Fields
Fields:
A field is defined as class attribute on a model class.Fields as descriptors:
In general, a descriptor is an object attribute with 'binding behavior', one whose attribute access has been overridden by methods in the descriptor protocol. Those methods are __get__(), __set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor.
The field descriptor contains the field definition, and manages accesses and assignments of the corresponding field on records.
Descriptor methods:
1. def __get__(self, record, owner):
- Return the value of field 'self' on 'record'.
- Define behavior for when the descriptor's value is retrieved.
2. def __set__(self, record, value):
- Set the value of field 'self' on 'record'.
- Define behavior for when the descriptor's value is changed.
Fields Attributes:
The following attributes may be provided when instantiating a field:
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.
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:
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).
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':
- 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).
The methods given for 'compute', 'inverse' and 'search' are model methods. Their signature is shown in the following example:
upper = fields.Char(compute='_compute_upper',
inverse='_inverse_upper',
search='_search_upper')
@api.depends('name')
def _compute_upper(self):
for rec in self:
self.upper = self.name.upper() if self.name else False
def _inverse_upper(self):
for rec in self:
self.name = self.upper.lower() if self.upper else False
def _search_upper(self, operator, value):
if operator == 'like':
operator = 'ilike'
return [('name', operator, value)]
The compute method has to assign the field on all records of the invoked
recordset. The decorator :meth:'openerp.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;
re-computation 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.
- related: sequence of field names.
The value of some attributes from related fields are automatically taken
from the source field, when it makes sense. Examples are the attributes
'string' or 'selection' on selection fields.
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(property) 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.
- company_dependent: whether the field is company-dependent (boolean).
Incremental definition (Field Inheritance):
A field is defined as class attribute on a model class. If the model is
extended (see :class:'~openerp.models.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 sub-classes.
For instance, the second class below only adds a tooltip on the field 'state':
class First(models.Model):
_name = 'foo'
state = fields.Selection([...], required=True)
class Second(models.Model):
_inherit = 'foo'
state = fields.Selection(help="Blah blah blah")
Odoo New API: Metaclasses and Decorators
Odoo New API Implementation Using Metaclasses and Decorators
API:
An API(Application Programming Interface) is a set of defined functions and methods for interfacing with the underlying system or program or service running on the computer.Metaclass:
A metaclass is defined as "the class of a class". Any class whose instances are themselves classes, is a metaclass.Things You Could Do With Metaclasses:
There are lots of things you could do with metaclasses. Here's a partial list:
- Enforce different inheritance semantics, e.g. automatically call base class methods when a derived class overrides.
- Implement class methods (e.g. if the first argument is not named 'self').
- Implement that each instance is initialized with copies of all class variables.
- Implement a different way to store instance variables (e.g. in a list kept outside the the instance but indexed by the instance's id()).
- Automatically wrap or trap all or certain methods:
- for precondition and post-condition checking
- for synchronized methods
- for automatic value caching
Metaclass's __new__ and __init__:
To control the creation and initialization of the class in the metaclass, you can implement the metaclass's __new__ method and/or __init__ constructor. Most real-life metaclasses will probably override just one of them. __new__ should be implemented when you want to control the creation of a new object (class in our case), and __init__ should be implemented when you want to control the initialization of the new object after it has been created.
New API Implementation Details:
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.
For instance, the statements:
model = self.pool.get(MODEL)
ids = model.search(cr, uid, DOMAIN, context=context)
for rec in model.browse(cr, uid, ids, context=context):
print rec.name
model.write(cr, uid, ids, VALUES, context=context)
may also be written as:
env = Env(cr, uid, context) # cr, uid, context wrapped in env
recs = env[MODEL] # retrieve an instance of MODEL
recs = recs.search(DOMAIN) # search returns a recordset
for rec in recs: # iterate over the records
print rec.name
recs.write(VALUES) # update all records in recs
Methods written in the "traditional" style are automatically decorated, following some heuristics based on parameter names.
1. class Meta(type):
It is used to automatically decorates traditional-style methods by guessing their API. It also implements the inheritance of the :func:'returns' decorators.
2. Metaclass's __new__:
It is used to automatically decorates traditional-style methods by guessing their API.
The guess() decorator is used by the metaclass's __new__ to decorates traditional-style methods by guessing their API.
Decorator's:
A decorator is just a callable that takes a function as an argument and returns a replacement function.
The Decorator's which is used to guess the API:
1. guess():
Decorate 'method' to make it callable in both traditional and record styles. This decorator is applied automatically by the model's metaclass, and has no effect on already-decorated methods.
The API style is determined by heuristics on the parameter names: 'cr' or 'cursor' for the cursor, 'uid' or 'user' for the user id, 'id' or 'ids' for a list of record ids, and 'context' for the context dictionary. If a traditional API is recognized, one of the decorators :func:'cr', :func:'cr_context', :func:'cr_uid', :func:'cr_uid_context', :func:'cr_uid_id', :func:'cr_uid_id_context', :func:'cr_uid_ids', :func:'cr_uid_ids_context' is applied on the method.
Method calls are considered traditional style when their first parameter is a database cursor.
2. noguess():
Decorate a method to prevent any effect from :func:'guess'.
The Decorator's which is used to decorate the traditional-style method:
1. @api.cr:
Decorate a traditional-style method that takes 'cr' as a parameter. Such a method may be called in both record and traditional styles, like:
# recs = model.browse(cr, uid, ids, context)
recs.method(args)
model.method(cr, args)
2. @api.cr_context:
Decorate a traditional-style method that takes 'cr', 'context' as parameters.
3. @api.cr_uid:
Decorate a traditional-style method that takes 'cr', 'uid' as parameters.
4. @api.cr_uid_context:
Decorate a traditional-style method that takes 'cr', 'uid', 'context' as parameters. Such a method may be called in both record and traditional
styles, like:
# recs = model.browse(cr, uid, ids, context)
recs.method(args)
model.method(cr, uid, args, context=context)
5. @api.cr_uid_id:
Decorate a traditional-style method that takes 'cr', 'uid', 'id' as parameters. Such a method may be called in both record and traditional styles. In the record style, the method automatically loops on records.
6. @api.cr_uid_id_context:
Decorate a traditional-style method that takes 'cr', 'uid', 'id', 'context' as parameters. Such a method:
@api.cr_uid_id
def method(self, cr, uid, id, args, context=None):
...
may be called in both record and traditional styles, like:
# rec = model.browse(cr, uid, id, context)
rec.method(args)
model.method(cr, uid, id, args, context=context)
7. @api.cr_uid_ids:
Decorate a traditional-style method that takes 'cr', 'uid', 'ids' as parameters. Such a method may be called in both record and traditional styles.
8. @api.cr_uid_ids_context:
Decorate a traditional-style method that takes 'cr', 'uid', 'ids', 'context' as parameters. Such a method:
@api.cr_uid_ids_context
def method(self, cr, uid, ids, args, context=None):
...
may be called in both record and traditional styles, like:
# recs = model.browse(cr, uid, ids, context)
recs.method(args)
model.method(cr, uid, ids, args, context=context)
It is generally not necessary, see :func:'guess'.
The Decorator's which is used to decorate the record-style method:
1. @api.model:
Decorate a record-style method where 'self' is a recordset, but its contents is not relevant, only the model is. Such a method:
@api.model
def method(self, args):
...
may be called in both record and traditional styles, like:
# recs = model.browse(cr, uid, ids, context)
recs.method(args)
model.method(cr, uid, args, context=context)
Notice that no 'ids' are passed to the method in the traditional style.
2. @api.one:
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:
@api.one
def method(self, args):
return self.name
may be called in both record and traditional styles, like::
# recs = model.browse(cr, uid, ids, context)
names = recs.method(args)
names = model.method(cr, uid, ids, args, context=context)
Each time 'self' is redefined as current record.
3. @api.multi:
Decorate a record-style method where 'self' is a recordset. The method typically defines an operation on records. Such a method:
@api.multi
def method(self, args):
...
may be called in both record and traditional styles, like::
# recs = model.browse(cr, uid, ids, context)
recs.method(args)
model.method(cr, uid, ids, args, context=context)
4. @api.constrains:
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):
if self.name == self.description:
raise ValidationError("Fields name and description must be different")
Invoked on the records on which one of the named fields has been modified.
Should raise :class:'~openerp.exceptions.ValidationError' if the validation failed.
5. @api.onchange:
Return a decorator to decorate an onchange method for given fields. Each argument must be a field name:
@api.onchange('partner_id')
def _onchange_partner(self):
self.message = "Dear %s" % (self.partner_id.name or "")
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.
6. @api.depends:
Return a decorator that specifies the field dependencies of a "compute" method (for new-style function fields). Each argument must be a string that consists in a dot-separated sequence of field names:
pname = fields.Char(compute='_compute_pname')
@api.one
@api.depends('partner_id.name', 'partner_id.is_company')
def _compute_pname(self):
if self.partner_id.is_company:
self.pname = (self.partner_id.name or "").upper()
else:
self.pname = self.partner_id.name
One may also pass a single function as argument. In that case, the dependencies are given by calling the function with the field's model.
7.@api.returns:
Return a decorator for methods that return instances of 'model'.
:param model: a model name, or 'self' for the current model
:param downgrade: a function 'downgrade(value)' to convert the
record-style 'value' to a traditional-style output
The decorator adapts the method output to the api style: 'id', 'ids' or 'False' for the traditional style, and recordset for the record style:
@model
@returns('res.partner')
def find_partner(self, arg):
... # return some record
# output depends on call style: traditional vs record style
partner_id = model.find_partner(cr, uid, arg, context=context)
# recs = model.browse(cr, uid, ids, context)
partner_record = recs.find_partner(arg)
Note that the decorated method must satisfy that convention.
Those decorators are automatically *inherited*: a method that overrides a decorated existing method will be decorated with the same
'@returns(model)'.
The Decorator's which is used to decorate a method that supports the old-style API only:
1. @api.v7:
Decorate a method that supports the old-style api only. A new-style api may be provided by redefining a method with the same name and decorated with :func:'~.v8':
@api.v7
def foo(self, cr, uid, ids, context=None):
...
@api.v8
def foo(self):
...
Note that the wrapper method uses the docstring of the first method.
The Decorator's which is used to decorate a method that supports the new-style API only:
1. @api.v8:
Decorate a method that supports the new-style api only. An old-style api may be provided by redefining a method with the same name and decorated with :func:'~.v7':
@api.v8
def foo(self):
...
@api.v7
def foo(self, cr, uid, ids, context=None):
...
Note that the wrapper method uses the docstring of the first method.
Odoo New API: Environment
Environment:
An environment wraps data for ORM records:
An environment wraps data for ORM records:
- 'cr', the current database cursor.
- 'uid', the current user id.
- 'context', the current context dictionary.
It also provides access to the registry, a cache for records, and a data structure to manage re-computations.
Encapsulates cr, uid, context:
Accessing the current cursor, user and context:
#recs.env encapsulates cr, uid, context
recs.env.cr # shortcut: recs._cr
recs.env.uid # shortcut: recs._uid
recs.env.context # shortcut: recs._context
# recs.env also provides helpers
recs.env.user # uid as a record
recs.env.ref('base.group_user') # resolve xml id
recs.env['res.partner'] # access to new-API model
Switching Environments:
1. Switch to new user:
Returns a new version of this recordset attached to the provided user:
self.sudo(user.id)
self.sudo() # This will use the SUPERUSER_ID by default
# or
self.env['res.partner'].sudo().create(vals)
2. Modifying the context:
Returns a new version of this recordset attached to an extended context:
Syntax: with_context([context][, **overrides]) -> records
The extended context is either the provided 'context' in which 'overrides' are merged or the *current* context in which 'overrides' are merged e.g.:
# current context is {'key1': True}
r2 = records.with_context({}, key2=True)
# -> r2._context is {'key2': True}
r2 = records.with_context(key2=True)
# -> r2._context is {'key1': True, 'key2': True}
3. Switch to new Environment:
Returns a new version of this recordset attached to the provided environment:
# rebrowse recs with different parameters
env2 = recs.env(cr2, uid2, context2)
recs2 = recs.with_env(env2)
Dry Run:
Environment context manager provides the helper method to perform the action only in caches:
1. do_in_draft: Context-switch to draft mode, where all field updates are done in cache only.
2. do_in_onchange: Context-switch to 'onchange' draft mode, which is a specialized draft mode used during execution of onchange methods.
Invalidate the Environment Caches:
An Environment maintains multiple caches that are used by the Models/Fields classes.
In some cases, if you want to invalidate the caches, then you can do:
self.env.invalidate_all()
It will clear the cache of all environments.
Odoo New API: Active Record Pattern
Active Record:
Active Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database. It is an implementation of the Active Record pattern which itself is a description of an Object Relational Mapping system.
Active Record Pattern:
The active record pattern is an approach to accessing data in a database. A database table is wrapped into a class.
In Active Record, objects carry both persistent data and behavior which operates on that data. Active Record takes the opinion that ensuring data access logic is part of the object will educate users of that object on how to write to and read from the database.
One of the new feature introduced in new API is a support for the active record pattern.
Example:
@api.multi
def method(self):
for record in self:
record.name = 'blah'
This will simply writes the value to database.
Implementation Detail:
With the help of special methods:
1. __iter__(self):
Return an iterator over 'self'.
2. __getitem__(self, key):
If 'key' is an integer or a slice, return the corresponding record
selection as an instance (attached to 'self.env').
Otherwise read the field 'key' of the first record in 'self'.
Examples::
inst = model.search(dom) # inst is a recordset
r4 = inst[3] # fourth record in inst
rs = inst[10:20] # subset of inst
nm = rs['name'] # name of first record in inst
3. __setitem__(self, key, value):
- Assign the field 'key' to 'value' in record 'self'.
- It also calls the field's setter, to simply write to the database, and update cache.
Be Careful:
It is very expensive to use Active Record Pattern while writing the value. As Each assignment will trigger a write action on the database.
Example:
@api.one
def method(self):
self.a = 1
self.b = 7
self.c = 8
On above example each assignment will trigger a write on the database. As the function is decorated with @api.one for each record in recordset write will be called 3 times. So if you have 'n' numbers of records in recordset, the number of writes will be n*3.
This may leads to performance bottleneck on heavy task. In that case it is better to use:
@api.multi
def method(self):
for record in self:
record.write({'a': 1, 'b': 7, 'c': 8})
or, if you want to write same value on all the records then it is more better to use:
@api.multi
def method(self):
self.write({'a':1, 'b': 7, 'c': 8})
Active Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database. It is an implementation of the Active Record pattern which itself is a description of an Object Relational Mapping system.
Active Record Pattern:
The active record pattern is an approach to accessing data in a database. A database table is wrapped into a class.
In Active Record, objects carry both persistent data and behavior which operates on that data. Active Record takes the opinion that ensuring data access logic is part of the object will educate users of that object on how to write to and read from the database.
One of the new feature introduced in new API is a support for the active record pattern.
Example:
@api.multi
def method(self):
for record in self:
record.name = 'blah'
This will simply writes the value to database.
Implementation Detail:
With the help of special methods:
1. __iter__(self):
Return an iterator over 'self'.
2. __getitem__(self, key):
If 'key' is an integer or a slice, return the corresponding record
selection as an instance (attached to 'self.env').
Otherwise read the field 'key' of the first record in 'self'.
Examples::
inst = model.search(dom) # inst is a recordset
r4 = inst[3] # fourth record in inst
rs = inst[10:20] # subset of inst
nm = rs['name'] # name of first record in inst
3. __setitem__(self, key, value):
- Assign the field 'key' to 'value' in record 'self'.
- It also calls the field's setter, to simply write to the database, and update cache.
Be Careful:
It is very expensive to use Active Record Pattern while writing the value. As Each assignment will trigger a write action on the database.
Example:
@api.one
def method(self):
self.a = 1
self.b = 7
self.c = 8
On above example each assignment will trigger a write on the database. As the function is decorated with @api.one for each record in recordset write will be called 3 times. So if you have 'n' numbers of records in recordset, the number of writes will be n*3.
This may leads to performance bottleneck on heavy task. In that case it is better to use:
@api.multi
def method(self):
for record in self:
record.write({'a': 1, 'b': 7, 'c': 8})
or, if you want to write same value on all the records then it is more better to use:
@api.multi
def method(self):
self.write({'a':1, 'b': 7, 'c': 8})
Odoo: New API
Definition:
An API(Application Programming Interface) is a set of defined functions and methods for interfacing with the underlying system or program or service running on the computer.
The primary motivations behind new API:
An API(Application Programming Interface) is a set of defined functions and methods for interfacing with the underlying system or program or service running on the computer.
The primary motivations behind new API:
- Odoo development must be simple.
- Odoo development must be simpler.
- By exposing a better API (and some new features too).
- Without sacrificing existing flexibility including: learning, understanding, prototyping, implementing, debugging, testing, performance tuning, maintenance.
- Backward compatibility remains a priority.
Overview:
Odoo becomes simpler, more modular, more pythonic. It's lower the learning curve, allow you
to write less code for the same result.
Key features introduced in new API:
1. Active Record Pattern
2. Environment
3. Fields
4. Decorators
5. Recordsets
0 comments