From the previous section, we already got a taste of what it is like to use the ORM API. Next we will look at what more we can do with it.

During our journey, the several methods we encountered used API decorators like @api.multi. These are important for the server to know how to handle the method. Let's recap the ones available and when they should be used.

The @api.multi decorator is used to handle recordsets with the new API and is the most frequently used. Here self is a recordset, and the method will usually include a for loop to iterate it.

In some cases, the method is written to expect a singleton: a recordset containing no more than one record. The @api.one decorator was deprecated as of 9.0 and should be avoided. Instead we should still use @api.multi and add to the method code a line with self.ensure_one(), to ensure it is a singleton.

As mentioned, the @api.one decorator is deprecated but is still supported. For completeness, it might be worth knowing that it wraps the decorated method, feeding it one record at a time, doing the recordset iteration for. In our method self is guaranteed to be a singleton. The return values of each individual method call are aggregated as a list and returned.

The @api.model decorates a class-level static method, and it does not use any recordset data. For consistency, self is still a recordset, but its content is irrelevant. Note that this type of method cannot be used from buttons in the user interface.

A few other decorators have more specific purposes and are to be used together with the decorators described earlier:

In particular, the onchange methods can send a warning message to the user interface. For example, this could warn the user that the product quantity just entered is not available in stock, without preventing the user from continuing. This is done by having the method return a dictionary describing the warning message:

return { 
         'warning': { 
         'title': 'Warning!', 
         'message': 'You have been warned'} 
        } 

We have learned about the standard methods provided by the API but there uses don't end there! We can also extend them to add custom behavior to our models.

The most common case is to extend the create() and write() methods. This can be used to add logic to be triggered whenever these actions are executed. By placing our logic in the appropriate section of the custom method, we can have the code run before or after the main operations are executed.

Using the TodoTask model as an example, we can make a custom create(), which would look like this:

@api.model 
def create(self, vals): 
    # Code before create: can use the `vals` dict 
    new_record = super(TodoTask, self).create(vals) 
    # Code after create: can use the `new_record` created 
    return new_record 

A custom write() would follow this structure:

@api.multi 
def write(self, vals): 
    # Code before write: can use `self`, with the old values 
    super(TodoTask, self).write(vals) 
    # Code after write: can use `self`, with the updated values 
    return True 

These are common extension examples, but of course any standard method available for a model can be inherited in a similar way to add our custom logic to it.

These techniques open up a lot of possibilities, but remember that other tools are also available that can be better suited for common specific tasks:

We have seen the most important model methods used to generate recordsets and how to write on them. But there are a few more model methods available for more specific actions, as shown here:

The following methods are mostly used by the web client to render the user interface and perform basic interaction:

  • name_get(): This returns a list of (ID, name) tuples with the text representing each record. It is used by default for computing the display_name value, providing the text representation of relation fields. It can be extended to implement custom display representations, such as displaying the record code and name instead of only the name.
  • name_search(name='', args=None, operator='ilike', limit=100) returns a list of (ID, name) tuples, where the display name matches the text in the name argument. It is used in the UI while typing in a relation field to produce the list with the suggested records matching the typed text. For example, it is used to implement product lookup both by name and by reference, while typing in a field to pick a product.
  • name_create(name) creates a new record with only the title name to use for it. It is used in the UI for the "quick-create" feature, where you can quickly create a related record by just providing its name. It can be extended to provide specific defaults for the new records created through this feature.
  • default_get([fields]) returns a dictionary with the default values for a new record to be created. The default values may depend on variables such as the current user or the session context.
  • fields_get() is used to describe the model's field definitions, as seen in the View Fields option of the developer menu.
  • fields_view_get() is used by the web client to retrieve the structure of the UI view to render. It can be given the ID of the view as an argument or the type of view we want using view_type='form'. For example, you might try this: rset.fields_view_get(view_type='tree').

Database writing operations are executed in the context of a database transaction. Usually, we don't have to worry about this as the server takes care of that while running model methods.

But in some cases, we may need a finer control over the transaction. This can be done through the database cursor self.env.cr, as shown here:

With the cursor execute() method, we can run SQL directly in the database. It takes a string with the SQL statement to run and a second optional argument with a tuple or list of values to use as parameters for the SQL. These values will be used where %s placeholders are found.

If you're using a SELECT query, records should then be fetched. The fetchall() function retrieves all the rows as a list of tuples, and dictfetchall() retrieves them as a list of dictionaries, as shown in the following example:

>>> self.env.cr.execute("SELECT id, login FROM res_users WHERE 
login=%s OR id=%s", ('demo', 1))
>>> self.env.cr.fetchall() 
[(4, u'demo'), (1, u'admin')]

It's also possible to run Data Manipulation Language (DML) instructions such as UPDATE and INSERT. Since the server keeps data caches, they may become inconsistent with the actual data in the database. Because of that, while using raw DML, the caches should be cleared afterward by using self.env.invalidate_all().