New models are defined through Python classes. Extending them is also done through Python classes, but with the help of an Odoo-specific inheritance mechanism.
To extend an existing model, we use a Python class with a _inherit
attribute. This identifies the model to be extended. The new class inherits all the features of the parent Odoo model, and we only need to declare the modifications we want to introduce.
In fact, Odoo models exist outside our particular Python module, in a central registry. This registry, can be accessed from model methods using self.env[<model name>]
. For example, to get a reference to the object representing the res.partner
model, we would write self.env['res.partner']
.
To modify an Odoo model, we get a reference to its registry class and then perform in-place changes on it. This means that these modifications will also be available everywhere else where this new model is used.
During the Odoo server startup, the module loading the sequence is relevant: modifications made by one add-on module will only be visible to the add-on modules loaded afterward. So it's important for the module dependencies to be correctly set, ensuring that the modules providing the models we use are included in our dependency tree.
We will extend the todo.task
model to add a couple of fields to it: the user responsible for the task and a deadline date.
The coding style guidelines recommended having a models/
subdirectory with one file per Odoo model. So we should start by creating the model subdirectory, making it Python-importable.
Edit the todo_user/__init__.py
file to have this content:
from .import models
Create todo_user/models/__init__.py
with the following code:
from . import todo_task
The preceding line directs Python to look for a file called odoo_task.py
in the same directory and imports it. You would usually have a from
line for each Python file in the directory:
Now create the todo_user/models/todo_task.py
file to extend the original model:
# -*- coding: utf-8 -*- from odoo import models, fields, api class TodoTask(models.Model): _inherit = 'todo.task' user_id = fields.Many2one('res.users', 'Responsible') date_deadline = fields.Date('Deadline')
The class name TodoTask
is local to this Python file and, in general, is irrelevant for other modules. The _inherit
class attribute is the key here: it tells Odoo that this class is inheriting and thus modifying the todo.task
model.
The next two lines are regular field declarations. The user_id
field represents a user from the users model res.users
. It's a Many2one
field, which is equivalent to a foreign key in database jargon. The date_deadline
is a simple date field. In
Chapter 5
, Models - Structuring the Application Data, we will explain the types of fields available in Odoo in more detail.
To have the new fields added to the model's supporting database table, we need to perform a module upgrade. If everything goes as expected, you should see the new fields when inspecting the todo.task
model in the Technical | Database Structure | Models menu option.
As you can see, adding new fields to an existing model is quite straightforward. Since Odoo 8, modifying the attributes on existing inherited fields is also possible. It's done by adding a field with the same name and setting values only for the attributes to be changed.
For example, to add a help tooltip to the name
field, we would add this line to todo_ task.py
, described previously:
name = fields.Char(help="What needs to be done?")
This modifies the field with the specified attributes, leaving unmodified all the other attributes not explicitly used here. If we upgrade the module, go to a to-do task form and pause the mouse pointer over the Description field; the tooltip text will be displayed.
Inheritance also works at the business logic level. Adding new methods is simple: just declare their functions inside the inheriting class.
To extend or change the existing logic, the corresponding method can be overridden by declaring a method with the exact same name. The new method will replace the previous one, and it can also just extend the code of the inherited class, using Python's super()
method to call the parent method. It can then add new logic around the original logic both before and after super()
method is called.
The original Clear All Done action is not appropriate for our task-sharing module anymore since it clears all the tasks, regardless of their user. We need to modify it so that it clears only the current user tasks.
For this, we will override (or replace) the original method with a new version that first finds the list of completed tasks for the current user and then inactivates them:
@api.multi def do_clear_done(self): domain = [('is_done', '=', True), '|', ('user_id', '=', self.env.uid), ('user_id', '=', False)] dones = self.search(domain) dones.write({'active': False}) return True
For clarity, we first build the filter expression to be used to find the records to be cleared.
This filter expression follows an Odoo-specific syntax referred to as domain
: it is a list of conditions, where each condition is a tuple.
These conditions are implicitly joined with the AND (&
) operator. For the OR operation, a pipe, |
, is used in the place of a tuple, and it joins the next two conditions. We will go into more details about domains in
Chapter 6
, Views - Designing the User Interface.
The domain used here filters all the done tasks ('is_done', '=', True
) that either have the current user as responsible ('user_id', '=', self.env.uid
) or don't have a current user set ('user_id', '=', False
).
We then use the search
method to get a recordset with the done records to act upon and, finally, do a bulk write on them setting the active
field to False
. The Python False
value here represents the database NULL
value.
In this case, we completely overwrote the parent method, replacing it with a new implementation, but that is not what we usually want to do. Instead, we should extend the existing logic with some additional operations. Otherwise, we might break the already existing features.
To have the overriding method keep the already existing logic, we use Python's super()
construct to call the parent's version of the method. Let's see an example of this.
We can improve the do_toggle_done()
method so that it only performs its action on the tasks assigned to the current user. This is the code to achieve that:
from odoo.exceptions import ValidationError # ... # class TodoTask(models.Model): # ... @api.multi def do_toggle_done(self): for task in self: if task.user_id != self.env.user: raise ValidationError( 'Only the responsible can do this!') return super(TodoTask, self).do_toggle_done()
The method in the inheriting class starts with a for
loop to check that none of the tasks to toggle belongs to another user. If these checks pass, it then goes on calling the parent class method, using super()
. If not an error is raised, and we should use for this the Odoo built-in exceptions. The most relevant are ValidationError
, used here, and UserError
.
These are the basic techniques for overriding and extending business logic defined in model classes. Next, we will see how to extend the user interface views.