We have seen the basic extension of models, called class inheritance in the official documentation. This is the most frequent use of inheritance, and it's easiest to think about it as in-place extension. You take a model and extend it. As you add new features, they are added to the existing model. A new model isn't created. We can also inherit from multiple parent models, setting a list of values to the _inherit
attribute. With this, we can make use of mixin classes. Mixin classes are models that implement generic features we can add to other models. They are not expected to be used directly, and are like a container of features ready to be added to other models.
If we also use the _name
attribute with a value different from the parent model, we get a new model reusing the features from the inherited one but with its own database table and data. The official documentation calls this prototype inheritance. Here you take a model and create a brand new one that is a copy of the old one. As you add new features, they are added to the new model. The existing model isn't changed.
There is also the delegation inheritance method, using the _inherits
attribute. It allows a model to contain other models in a transparent way for the observer while, behind the scenes, each model handles its own data. You take a model and extend it. As you add new features, they are added to the new model. The existing module isn't changed. Records in the new model have a link to a record in the original model, and the fields of the original model are exposed and can be used directly in the new model.
Let's explore these possibilities in more detail.
The method we used before to extend a model used just the _inherit
attribute. We defined a class inheriting the todo.task
model and added some features to it. The class attribute _name
was not explicitly set; implicitly, it was todo.task
.
However, using the _name
attribute allows us to create a new model copying the features from the inherited ones. Here is an example:
from odoo import models class TodoTask(models.Model): _name = 'todo.task' _inherit = 'mail.thread'
This extends the todo.task
model by copying into it the features from the mail.thread
model. The mail.thread
model implements the Odoo messages and followers features and is reusable so that it's easy to add those features to any model.
Copying means that the inherited methods and fields will also be available in the inheriting model. For fields, this means that they will also be created and stored in the target model's database tables. The data records of the original (inherited) and the new (inheriting) models are kept unrelated. Only the definitions are shared.
In a moment, we will discuss in detail how to use this to add mail.thread
and its social networking features to our module. In practice, when using mixins, we rarely inherit from regular models because this causes duplication of the same data structures.
Odoo also provides the delegation inheritance mechanism that avoids data structure duplication, so it is usually preferred when inheriting from regular models. Let's look at it in more detail.
Delegation inheritance is less frequently used, but it can provide very convenient solutions. It is used through the _inherits
attribute (note the additional s
) with dictionary mapping inherited models with fields linking to them.
A good example of this is the standard user's model, res.users
; it has a Partner model embedded in it:
from odoo import models, fields class User(models.Model): _name = 'res.users' _inherits = {'res.partner': 'partner_id'} partner_id = fields.Many2one('res.partner')
With delegation inheritance, the res.users
model embeds the inherited model res.partner
so that when a new User
class is created, a partner is also created and a reference to it is kept in the partner_id
field of the User
class. It has some similarities with the polymorphism concept in object-oriented programming.
Through the delegation mechanism, all fields from the inherited model and Partner are available as if they were User
fields. For example, the Partner name
and address
fields are exposed as User
fields, but in fact, they are being stored in the linked Partner model, and no data duplication occurs.
The advantage of this, compared to prototype inheritance, is that there is no need to repeat data structures, such as addresses, across several tables. Any new model that needs to include an address can delegate that to an embedded Partner model. And if modifications are introduced in Partner address fields, these are immediately available to all the models embedding it!
The social network module (technical name mail
) provides the message board found at the bottom of many forms and the Followers feature, as well as the logic regarding messages and notifications. This is something we will often want to add to our models, so let's learn how to do it.
The social network messaging features are provided by the mail.thread
model of the mail
module. To add it to a custom model, we need to do the following:
mail
mail.thread
Let's follow this checklist.
Regarding the first point, our extension module will need the additional mail
dependency on the module __manifest__.py
manifest file:
'depends': ['todo_app', 'mail'],
Regarding the second point, the inheritance on mail.thread
is done using the _inherit
attribute we used before. But our to-do task extension class is already using the _inherit
attribute. Fortunately, it can accept a list of models to inherit from, so we can use this to make it also include the inheritance on mail.thread
:
_name = 'todo.task' _inherit = ['todo.task', 'mail.thread']
The mail.thread
is an abstract model. Abstract models are just like regular models, except that they don't have a database representation; no actual tables are created for them. Abstract models are not meant to be used directly. Instead, they are expected to be used as mixin classes, as we just did. We can think of them as templates with ready-to-use features. To create an abstract class, we just need it to use models.AbstractModel
instead of models.Model
for the class defining them.
For the third point, we want to add the social network widgets at the bottom of the form. This is done by extending the form view definition. We can reuse the inherited view we already created, view_form_todo_task_inherited
, and add this to its arch
data:
<sheet position="after"> <div class="oe_chatter"> <field name="message_follower_ids" widget="mail_followers" /> <field name="message_ids" widget="mail_thread" /> </div> </sheet>
The two fields added here haven't been explicitly declared by us, but they are provided by the mail.thread
model.
The final step, that is step four, is to set up record rules for followers: row-level access control. This is only needed if our model is required to limit other users from accessing the records. In this case, we want each to-do task record to also be visible to any of its followers.
We already have a Record Rules defined on the to-do task model, so we need to modify it to add this new requirement. That's one of the things we will be doing in the next section.