New models are defined through Python classes. Extending them is also done through Python classes, but using an Odoo specific 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 that we wish to introduce.
In fact, Odoo models exist outside our particular module, in a central registry. This registry can also be referred to as the pool, and can be accessed from model methods using self.env[<model name>]
. For example, to reference 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 the model is used.
In the module loading sequence, during a server start, modifications will only be visible to the modules loaded afterward. So, the loading sequence is important and we should make sure that the module dependencies are correctly set.
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.
Create a new todo_task.py
file declaring a class extending the original model:
# -*- coding: utf-8 -*- from openerp 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 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 from the todo.task
model. Notice the _name
attribute absent. It is not needed because it is already inherited from the parent model.
The next two lines are regular field declarations. The user_id
represents a user from the Users model, res.users
. It's a Many2one
field, the 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 be explaining in more detail the types of fields available in Odoo.
We still need to add to the __init__.py
file the import statement to include it in the module:
from . import todo_task
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 menu, Database Structure | Models option.
As you can see, adding new fields to an existing model is quite straightforward. Since Odoo 8, modifying attributes on already existing 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 could add this line to the todo_ task.py
described above:
name = fields.Char(help="What needs to be done?")
If we upgrade the module, go to a to-do task form, and pause the mouse pointer over the Description field, the above 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 existing logic, the corresponding method can be overridden by declaring a method with the exact same name, and the new method will replace the previous one. But it can extend the code of the inherited class, by using Python's super()
keyword to call the parent method.
It's best to avoid changing the method's function signature (that is, keep the same arguments) to be sure that the existing calls on it will keep working properly. In case you need to add additional parameters, make them optional (with a default value) keyword arguments.
The original Clear All Done
action is not appropriate for our task-sharing module anymore, since it clears all tasks regardless of their user. We need to modify it so that it clears only the current user tasks.
For this, we will override 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)] done_recs = self.search(domain) done_recs.write({'active': False}) return True
We first list the done
records to act upon using the search
method with a filter expression. The filter expression follows an Odoo specific syntax referred to as a domain
.
The filter domain used is defined the first instruction: it is a list of conditions, where each condition is a tuple.
These conditions are implicitly joined with an AND
operator ('&'
in domain syntax). To add an OR
operation a pipe ('|'
) is used in place of a tuple, and it will affect 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 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
).
What we just did was to completely overwrite the parent method, replacing it with a new implementation.
But this is not what we usually want to do. Instead we should extend the existing logic to add some additional operations to it. Otherwise we could break already existing features. Existing logic is inserted in an overriding method using Python's super()
command, to call the parent's version of the method.
Let's see an example of this: we could write a better version of do_toggle_done()
that only performs its action on the Tasks assigned to our user:
@api.one def do_toggle_done(self): if self.user_id != self.env.user: raise Exception('Only the responsible can do this!') else: return super(TodoTask, self).do_toggle_done()
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.