Skip to main content

More Model inheritance mechanisms



More Model inheritance mechanisms

Previously, we saw the basic extension of Models, called classic inheritance, in the official documentation. This is the most frequent use of inheritance, and the easiest way to think about it is as an 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. Most of the time, this is done with mixin classes. Mixin classes are Models that implement generic features to be reused. They are not expected to be used on their own, like regular Models, and are like a container of features, ready to be added to other Models.
If, along with _inherit, we also use the _name attribute with a value different from the parent Model, we get a new Model by reusing the features from the inherited one, 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 original one. As you add new features to it, they will be added only to the new Model, and the original Model will be left unchanged.
We also have the delegation inheritance method, which is available by using the _inherits attribute (notice the s on the end). It allows us to create a new Model that contains and extends an existing Model. When a new record is created for the new Model, a new record in the original Model is also created and linked, using a many-to-one field. Observers of the new Model see all of the fields, both from the original and new Models, although, behind the scenes, each Model handles its own data.
Let's explore these possibilities in more detail.

Copying features with prototype inheritance

The method we used to extend the Model used only the _inherit attribute. We defined a class by inheriting the library.book Model and added some features to it. The class attribute _name was not explicitly set; implicitly, it was library.book.
However, if we set a different value on the _name attribute, it will create a new Model by copying features from the inherited ones.
In practice, this type of inheritance is usually used with abstract mixin classes. It is rarely, if ever, used to inherit from regular Models, since that would create duplicate data structures.
Odoo also has a delegation inheritance mechanism that avoids this data structure duplication, so it is usually preferred when inheriting from regular Models. Let's look at it in more detail.


Embedding Models using delegation inheritance

Delegation inheritance allows us to reuse data structures, without actually duplicating them in the database. This is done by embedding a Model inside another. UML terms this is a composition relationship: the child cannot exist without the parent, but the parent can exist without the child.
For example, for the core User Model, each record contains a Partner record, and so has all of the fields you find on a Partner available, plus a few fields that are specific to Users.
For the Library project, we want to add a Library Members Model. Members will be able to borrow books, and have a library card that can be used when borrowing books. We want to record the card number, and we also want to be able to store personal information, such as email and address. The Partner Model already supports contact and address information, so it's best to reuse it, instead of creating duplicate data structures.
We will create a library_member/models/library_member.py file for this new Member Model with the following code:
from odoo import fields, models

class Member(models.Model): 
    _name = 'library.member'
    _description = 'Library Member'
    card_number = fields.Char()
    partner_id = fields.Many2one(
        'res.partner',
        delegate=True,
        ondelete='cascade',
        required=True) 
With delegation inheritance, the library.member Model embeds the inherited Model, res.partner, so that when a new Member record is created, a related Partner is automatically created and referenced in the partner_id field.

Note

Changes in Odoo 8: The delegate=True field attribute was introduced with the new API. Before that, delegation inheritance was defined using a Model attribute, like this: _inherits = {'res.partner': 'partner_id'}. This is still supported, and is described in the official documentation, but the delegate=True field attribute has the same effect and is simpler to use.


Through the delegation mechanism, all fields of the embedded Model are automatically made available as if they were fields of the parent Model fields. In this case, the Member Card Model has all of the Partner fields available for use, such as name , address, and email, plus the ones specific to members, such as card_number. Behind the scenes, the Partner fields are stored in the linked Partner record, and no data structure duplication occurs.

Note

Note that the same is not true for Model methods: the methods from the Partner Model are not made available in the Member Model.
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. If modifications are introduced in partner address fields, these are immediately available to all of the Models that are embedding them.

Note

It may be useful to note that the delegation could be replaced by a combination of the following:
  • A many-to-one filed to the parent record
  • An override to the create() method, to automatically create and set the parent record
  • The related fields to the parent fields, for the specific fields we want to expose
Sometimes, this is more appropriate than a full-fledged delegation inheritance. For instance, res.company does not inherit res.partner, but uses a res.partner record to store several of its fields.
We should not forget to add this code file to library_member/model/__init__.py. It should look like this:
from . import library_book
from . import library_member
To be able to try the Member Model we created, there are some additional steps we need to work on:
  • Adding the security ACLs
  • Adding the Menu item
  • Adding the Form and List Views
  • Updating the manifest file so that we can declare these new data files
It's a good exercise to try to add these by yourself now. Anyway, here is a detailed implementation of the steps to do so :
To add the security ACLs, create the library_member/security/ir.model.access.csv file with the following code:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_member_user,Member User Access,model_library_member,library_app.library_group_user,1,1,1,0
access_member_manager,Member Manager Access,model_library_member,library_app.library_group_manager,1,1,1,1
To add the menu item, create the library_member/views/library_menu.xml file with the following code:
<?xml version="1.0"?>
<odoo>
    <act_window id="action_library_member"
      name="Library Members"
      res_model="library.member"
      view_mode="tree,form" />
    <menuitem id="menu_library_member"
      name="Members"
      action="action_library_member"
      parent="library_app.library_menu" />
</odoo>
To add the Views, create the library_member/views/member_view.xml file with the following code:
<?xml version="1.0"?>
<odoo>
  <record id="view_form_member" model="ir.ui.view">
    <field name="name">Library Member Form View</field>
    <field name="model">library.member</field>
    <field name="arch" type="xml">
      <form>
        <group>
          <field name="name" />
          <field name="email" />
          <field name="card_number" />
        </group>
      </form>
    </field>
  </record>

  <record id="view_tree_member" model="ir.ui.view">
    <field name="name">Library Member List View</field>
    <field name="model">library.member</field>
    <field name="arch" type="xml">
      <tree>
          <field name="name" />
          <field name="card_number" />
      </tree>
    </field>
  </record>
</odoo>
Finally, we should edit the manifest to declare these three new files:
 'data': [
   'views/book_view.xml',
   'security/library_security.xml',
   'security/ir.model.access.csv',
   'views/member_view.xml',
   'views/library_menu.xml',
 ],
If everything was entered correctly, after a module upgrade, we should be able to work with the new Library Member Model.

Extending Models using mixin classes

The main use for prototype inheritance is to support mixin classes. Mixins are abstract Models, based on models.Abstract (instead of models.Model) that have no actual representation in the database. Instead, they provide a set of features that can be reused by (mixed in) other Models.
The Odoo add-ons offer several mixins, but the two most widely used ones are provided by the Discuss app (mailadd-on module):
  • The mail.thread Model provides features for a message board, found at the bottom or right-hand side of many document forms, 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 that.
  • The mail.activity.mixin Model provides the planning of to-do tasks.

Note

Changes in Odoo 11The mail module now provides the Activities task management features through the mail.activity.mixin abstract Model. This feature was introduced in version 11, and is not available in earlier versions.
We will add both mixins to the Members Model.
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:
  1. Add a dependency on the add-on module by providing the mixin Models: mail
  2. Have the class inherit the mail.thread and mail.activity.mixin mixin classes
  3. Add the data fields provided by the message_follower_ids, message_ids ,and activity_ids mixins to the form View
Regarding the first step, our extension module will need the additional mail dependency on the module's __manifest__.py file:
'depends': ['library_app', 'mail'], 
Regarding the second step, the inheritance from the mixin classes is done using the _inherit attribute. We should edit the library_member/models/library_member.py file and add the following:
class Member(models.Model): 
    _name = 'library.member'
    _description = 'Library Member'
    _inherit = ['mail.thread', 'mail.activity.mixin']
With this single extra line of code, our Model will include all additional fields and methods provided by these mixins.
The third step is to add the relevant fields to our form View. Edit the library_member/views/member_view.xml file to add these fields at the bottom of the form:
  <record id="view_form_member" model="ir.ui.view">
    <field name="name">Library Member Form View</field>
    <field name="model">library.member</field>
    <field name="arch" type="xml">
      <form>
        <group>
          <field name="name" />
          <field name="email" />
          <field name="card_number" />
        </group>
        <!-- mail mixin fields -->
        <div class="oe_chatter">
            <field name="message_follower_ids" widget="mail_followers"/>
            <field name="activity_ids" widget="mail_activity"/>
            <field name="message_ids" widget="mail_thread"/>
        </div>
      </form>
    </field>
  </record>
The mail module also provides specific web widgets to present these fields. We use them in the preceding code.
After we upgrade the module, to install these changes, our Members form will look like this:


In some cases, regular users will only have access to the records they are following. In these cases, we should create an addition access Record Rule to ensure that Users can see the records they follow. For our example project, this is not a relevant feature, but it could be added using a Domain such as [('message_partner_ids', 'in', [user.partner_id.id])].

Comments

Popular posts from this blog

The message and activity features

The message and activity features Odoo has available global  messaging  and activity planning features, provided by the  Discuss  application, with technical name mail. The mail module provides the  mail.thread  abstract class that makes it simple to add the messaging features to any model, and the  mail.activity.mixin  that adds planned activity features. This was done in  Chapter 4 ,  Extending Modules , to explain how to inherit features from mixin abstract classes. To add these features, we need to add the mail dependency to the add-on module,  library_checkout , and then have the library checkout model class inherit from the abstract classes providing the following features. Edit the  'depends'  key in the  library_checkout/__manifest__.py   file, to add the mail module, shown as follows: Copy 'depends': ['library_member' , 'mail' ], And edit the  library_checkout/m...

Setting up an nginx reverse proxy

Setting up an nginx reverse proxy While Odoo itself can serve web pages, it's strongly recommended that there is a  reverse  proxy positioned in front of it. A reverse proxy acts as an intermediary that manages the traffic between clients sending requests and the Odoo servers responding to them. Using a reverse proxy has several benefits. On the security side, it can do the following: Handle (and enforce) HTTPS protocols to encrypt traffic Hide the internal network characteristics Act as an application firewall, limiting the URLs accepted for processing Also, on the performance side, it can provide the following significant improvements: Cached static content, hence reducing the load on the Odoo servers Compressed content to speed up loading time Act as a load balancer, distributing load between several servers Apache is a popular choice when considering a reverse proxy, although  nginx  is a recent alternative with good technical argumen...

The QWeb template language

The QWeb template language The  QWeb  parser looks for special directives in the templates and replaces them with dynamically generated HTML. These directives are XML element attributes and can be used in any valid tag or element, such as  <div> ,  <span> , or  <field> . Sometimes, we may want to use a QWeb directive but we don't want to place it in any of the XML elements in our template. For those cases, we have a  <t>  special element that can have QWeb directives, such as  t-if  or  t-foreach , but is silent and won't have any output on the final XML/HTML produced. The QWeb directives will frequently make use of evaluated expressions to produce different results, depending on the current record values. There are two different QWeb implementations: client-side JavaScript and server-side Python. The reports and website pages use the server-side Python implementation of QWeb. Kanban views us...