Development Book V17: Custom Widgets

Odoo offers a variety of field widgets that assist in giving our fields specific functionality. To show an image in binary fields, for instance, use widget="image"; similarly, for date fields, widget="date"

Let's investigate how to construct a customised Odoo 17 widget with the features we want.

Using the creation of a widget named "one2many_delete" as an example, we may remove many one2many lines from the view.

We may follow the steps listed below to construct our unique widget.

1. First, we need to add a static/src/js/widget.js file inside our module.

Then we need to extend ListRenderer to create a new class O2MListRenderer to show selection bow in One2many fields and X2ManyField to create a new class O2mMultiDelete for deleting one2many records.


/** @odoo-module **/

import { ListRenderer } from "@web/views/list/list_renderer";
import { registry } from "@web/core/registry";
import { Pager } from "@web/core/pager/pager";
import { KanbanRenderer } from "@web/views/kanban/kanban_renderer";
import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { useService } from '@web/core/utils/hooks';

export class O2MListRenderer extends ListRenderer {
   get hasSelectors() {
       if (this.props.activeActions.delete) {
           this.props.allowSelectors = true
       }
       let list = this.props.list
       list.selection = list.records.filter((rec) => rec.selected)
       list.selectDomain = (value) => {
           list.isDomainSelected = value;
           list.model.notify();
       }
       return this.props.allowSelectors && !this.env.isSmall;
   }
   toggleSelection() {
       const list = this.props.list;
       if (!this.canSelectRecord) {
           return;
       }
       if (list.selection.length === list.records.length) {
           list.records.forEach((record) => {
               record.toggleSelection(false);
               list.selectDomain(false);
           });
       } else {
           list.records.forEach((record) => {
               record.toggleSelection(true);
           });
       }
   }
   get selectAll() {
       const list = this.props.list;
       const nbDisplayedRecords = list.records.length;
       if (list.isDomainSelected) {
         return true;
       }
       else {
         return false
       }
   }
}

export class O2mMultiDelete extends X2ManyField {
   setup() {
       super.setup();
       X2ManyField.components = { Pager, KanbanRenderer, ListRenderer: O2MListRenderer };
       this.orm = useService('orm');
       this.dialog = useService("dialog");
   }
   get Selected(){
       return this.list.records.filter((rec) => rec.selected).length
   }
   DltSelected(){
       let selectedRecords = this.list.records.filter((rec) => rec.selected)
       this.dialog.add(ConfirmationDialog, {
           body: 'Are you sure you want to delete selected records?',
           confirm: () => selectedRecords.forEach((rec) => {
                          if (this.activeActions.onDelete) {
                              selectedRecords.forEach((rec) => {
                                       this.activeActions.onDelete(rec);
                                   })
                              }
                          }),
           cancel: () => {},
       });
   }
}

export const O2manyMultiDelete = {
   ...x2ManyField,
   component: O2mMultiDelete,
};
O2mMultiDelete.template = "O2mMultiDelete";
registry.category("fields").add("one2many_delete", O2manyMultiDelete);

2. Now we need to add the template inside static/src/xml/widget.xml.

This will add a delete button if one2many records are selected in the form view.




    <?xml version="1.0" encoding="UTF-8"?>
    <templates xml:space="preserve">
       <t t-name="O2mMultiDelete" t-inherit-mode="primary"
          t-inherit="web.X2ManyField" owl="1">
           <xpath expr="//div[hasclass('o_x2m_control_panel')]" position="before">
               <div class="dlt_btn">
                   <button t-if="Selected" t-on-click="DltSelected"
                           class="btn btn-primary">Delete
                   </button>
               </div>
           </xpath>
       </t>
    </templates>

3. Now we need to widget.js and widget.xml files inside the manifest file.


'assets': {
   'web.assets_backend': [
       module_name/static/src/xml/widget.xml',
       module_name/static/src/js/widget.js'
   ],
},

Now we can add it to the view. First, we need to define a model and view for the defined model with a one2many field.

First we are defining the model.


from odoo import fields, models


class EstateProperty(models.Model):
   _name = "estate.property"
   _description = "Real estate"

   name = fields.Char(string="Name")
   description = fields.Char(string="Description")
   postcode = fields.Char(string="PostCode")
   date_availability = fields.Date(string="Available From", default=fields.Datetime.today() + relativedelta(months=+3),
                                   copy=False)
   expected_price = fields.Float(string="Expected Price")
   selling_price = fields.Float(string="Selling Price", readonly=True)

   property_type = fields.Many2one("estate.type", string="Property Type")
   offer_id = fields.One2many("estate.offer", "property_id", string="Offer")

class EstateOffer(models.Model):
   _name = 'estate.offer'
   _rec_name = 'property_id'

   price = fields.Float(string="Price")
   status = fields.Selection(string="Status", selection=[('accepted', 'Accepted'), ('refused', 'Refused')],)
   partner_id = fields.Many2one("res.partner", string="Partner")
   property_id = fields.Many2one("estate.property", string="Property Id")
   dead_line = fields.Date(string="Validity", default=fields.Datetime.today())
   validity = fields.Integer(string="Dead line", default="7")

offer_id is the one2many field we have defined.

offer_id = fields.One2many("estate.offer", "property_id", string="Offer")

Now we need to define the view for the created model.


    <?xml version="1.0" encoding="utf-8"?>
    <odoo>
    <!--    Form View-->
       <record id="estate_property_view_form" model="ir.ui.view">
           <field name="name">estate.property.view.form</field>
           <field name="model">estate.property</field>
           <field name="arch" type="xml">
               <form string="Test">
                   <sheet>
                       <h1>
                           <field name="name"/>
                       </h1>
                       <group>
                           <group>
                               <field name="description"/>
                           </group>
                           <group>
                               <field name="postcode"/>
                           </group>
                           <group>
                               <field name="expected_price"/>
                           </group>
                           <group>
                               <field name="date_availability"/>
                           </group>
                           <group>
                               <field name="selling_price" readonly="1"/>
                           </group>
                       </group>
                           <notebook>
                               <page string="Offers">
                                       <field name="offer_id">
                                           <tree editable="bottom">
                                               <field name="partner_id"/>
                                               <field name="dead_line"/>
                                               <field name="validity"/>
                                               <field name="status"/>
                                           </tree>
                                       </field>
                               </page>
                           </notebook>
                   </sheet>
               </form>
           </field>
       </record>
    <!--    Record Action-->
       <record id="estate_property_record_action" model="ir.actions.act_window">
           <field name="name">Estate</field>
           <field name="res_model">estate.property</field>
           <field name="view_mode">tree,form</field>
       </record>
    <!--    Main Menu-->
       <menuitem id="real_estate_management_menu_root"
                 action="estate_property_record_action"
                 name="Real Estate"/>
    </odoo>

Finally, we need to add the widget we have created to the one2many field.


    <field name="offer_id" widget="one2many_delete">

Let’s check how the view looks like.

odoo-development

This is how we create custom widgets in odoo 17.

whatsapp_icon
location

Calicut

Cybrosys Technologies Pvt. Ltd.
Neospace, Kinfra Techno Park
Kakkancherry, Calicut
Kerala, India - 673635

location

Kochi

Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.

location

Bangalore

Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message