The model classes defining custom data models declare fields for the data processed by the model. They can also define custom behavior by defining methods on the model class.
In this recipe, we will see how to write a method that can be called by a button in the user interface, or by some other piece of code in our application. This method will act on LibraryBooks
and perform the required actions to change the state of a selection of books.
This recipe assumes you have an instance ready, with the my_module
addon module available, as described in Chapter 3, Creating Odoo Modules. You will need to add a state field to the LibraryBook
model defined as follows:
from openerp import models, fields, api class LibraryBook(models.Model): # […] state = fields.Selection([('draft', 'Unavailable'), ('available', 'Available'), ('borrowed', 'Borrowed'), ('lost', 'Lost')], 'State')
Please refer to Chapter 4, Adding data fields to a Model for more clarity.
For defining a method on LibraryBook
to allow changing the state of a selection of books, you need to add the following code to the model definition:
@api.model def is_allowed_transition(self, old_state, new_state): allowed= [('draft', 'available'), ('available', 'borrowed'), ('borrowed', 'available'), ('available', 'lost'), ('borrowed', 'lost'), ('lost', 'available')] return (old_state, new_state) in allowed
@api.multi def change_state(self, new_state): for book in self: if book.is_allowed_transition(book.state, new_state): book.state = new_state else: continue
The code in the recipe defines two methods. They are normal Python methods, having self
as their first argument and can have additional arguments as well.
The methods are decorated with decorators from the openerp.api
module. These decorators are there to ensure the conversion of calls made using the old or traditional API to the new API. The old API is used by the remote procedure call (RPC) protocol of the web client and by modules that have not yet been ported to the new API. A key part of this conversion is the creation of an execution environment stored in self.env
; it contains the following:
self.env.cr
: This is a database cursorself.env.user
: This is the user executing the actionself.env.context
: This is context, which is a Python dictionary containing various information such as the language of the user, his configured time zone, and other specific keys that can be set at run time by the actions of the user interfaceWhen writing a new method, you will generally be using @api.multi
. In addition to the creation of the environment, this decorator tells the RPC layer to initialize self
using the record IDs supplied in the RPC argument ids
. In such methods, self
is a recordset that can refer to an arbitrary number of database records.
The @api.model
decorator is similar but is used on methods for which only the model is important, not the contents of the recordset, which is not acted upon by the method. The concept is similar to Python's @classmethod
decorator (but we generally cannot use this in Odoo models because we need access to self.env
and other important instance attributes).
The @api.model
and @api.multi
decorators also have specific meaning when ensuring the compatibility of your addon module with other modules still using the "old API". You will find more information on this in the Porting old API code to the new API recipe in Chapter 6, Advanced Server Side Development Techniques.
Here is an example code snippet called change_state()
:
# returned_book_ids is a list of book ids to return books = self.env['library.book'] books.browse(returned_book_ids).change_state('available')
When change_state()
is called, self
is a (possibly empty) recordset containing records of the library.book
model. The body of the change_state()
method loops over self
to process each book in the recordset. Looping on self
looks strange at first, but you will get used to this pattern very quickly.
Inside the loop, change_state()
calls is_allowed_transition()
. The call is made using the local variable book
, but it could have been made on any recordset for the library.book
model, including, for example, self
, since is_allowed_transition()
is decorated with @api.model
. If the transition is allowed, change_state()
assigns the new state to the book by assigning a value to the attribute of the recordset. This is only valid on recordsets of length 1
, which is guaranteed to be the case when iterating over self
.
There are some important factors that are worth understanding.
In the old API, methods with a name prefixed by an underscore are not exposed through the RPC interface and are therefore not callable by the web client. This is still the case with the new API, which also offers another way to make a method unavailable to the RPC interface; if you don't put an @api.model
or @api.multi
decorator on it (or on one of the decorators mentioned in the Porting old API code to the new API recipe in Chapter 6, Advanced Server Side Development Techniques), then the method will neither be callable by extensions of the model using the traditional API nor via RPC.
You may encounter the @api.one
decorator while reading source code. In Odoo 9.0, this decorator is deprecated because its behavior can be confusing—at first glance, and knowing of @api.multi
, it looks like this decorator allows the method to be called only on recordsets of size 1
, but it does not. When it comes to recordset length, @api.one
is similar to @api.multi
, but it does a for
loop on the recordset outside the method and aggregates the returned value of each iteration of the loop in a list, which is returned to the caller. Avoid using it in your code.
In Chapter 8, Backend Views, refer to the Adding buttons to form recipe to learn how to call such a method from the user interface.
You will find additional information on the decorators defined in openerp.api
in the recipe from Chapter 6, Advanced Server Side Development Techniques: Porting old API code to the new API.