Development Book V18: Custom Widget

Odoo provides a wide range of widgets to enhance field behavior—for example, widget="image" for displaying image fields or widget="date" for formatting date inputs. But sometimes, you may want to go beyond what's available out of the box. In such cases, building a custom widget gives you the flexibility to tailor behavior to your exact needs.

Let's explore how to build a custom widget in Odoo 18, including JavaScript, XML, and Python integration, using a clean, modern approach supported by OWL (Odoo Web Library) and the registry system.

Example: Order Line Count Widget

Let’s look at a practical example — a widget that shows a quick summary of a Sales Order’s line items. It will display:

  • The total number of order lines.
  • The total quantity of all products.

This can help sales managers quickly evaluate an order without manually counting rows.

Step 1: Backend Model (Python)

First, we extend the sale.order model to include computed fields:

                         from odoo import fields, models

class SaleOrder(models.Model):
    _inherit = 'sale.order'

    order_line_count = fields.Integer(string='Order Line Count',
                                      compute='compute_line_count', store=True)
    num_qty = fields.Integer(string='Number Of Quantity',
                             compute='compute_line_count', store=True)

    def compute_line_count(self):
        for rec in self:
            rec.order_line_count = len(rec.order_line)
            rec.num_qty = sum(rec.order_line.mapped('product_uom_qty'))

Step 2: JavaScript Widget Logic

Now, we create a new widget using OWL that displays a popover with the counts.

            /** @odoo-module **/
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { usePopover } from "@web/core/popover/popover_hook";
import { Component } from "@odoo/owl";

export class OrderLineCountPopover extends Component {
    setup() {
        this.actionService = useService("action");
    }
}
OrderLineCountPopover.template = "your_module.OrderLineCountPopOver";

export class OrderLineCountWidget extends Component {
    setup() {
        this.popover = usePopover(this.constructor.components.Popover, { position: "top" });
        this.calcData = {};
    }
    showPopup(ev) {
        this.popover.open(ev.currentTarget, {
            record: this.props.record,
            calcData: this.calcData,
        });
    }
}
OrderLineCountWidget.components = { Popover: OrderLineCountPopover };
OrderLineCountWidget.template = "your_module.OrderLineCount";

export const OrderLineCountWidgetDefinition = {
    component: OrderLineCountWidget,
};
registry.category("view_widgets").add("order_line_count_widget", OrderLineCountWidgetDefinition);

Step 3: Widget Templates (XML)

Add the popover and icon layout for the widget:


<?xml version="1.0" encoding="UTF-8" ?>
<template xml:space="preserve">
    <t t-name="your_module.OrderLineCount">
        <a t-on-click="showPopup" class="fa fa-info-circle" role="button"/>
    </t>

    <t t-name="your_module.OrderLineCountPopOver">
        <div>
            <table class="table table-borderless table-sm">
                <tr>
                    <td><strong>Order line count:</strong></td>
                    <td><span t-out="props.record.data.order_line_count"/></td>
                </tr>
                <tr>
                    <td><strong>Total quantity:</strong></td>
                    <td><span t-out="props.record.data.num_qty"/></td>
                </tr>
            </table>
        </div>
    </t>
</template>

Step 4: Add Widget to Manifest

Reference the widget assets in your __manifest__.py:

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

Step 5: Embed Widget in Views

Now, integrate your widget into the sale order list or form view:

<odoo>
    <record id="view_sale_order_tree_custom" model="ir.ui.view">
        <field name="name">sale.order.tree.custom</field>
        <field name="model">sale.order</field>
        <field name="inherit_id" ref="sale.view_quotation_tree"/>
        <field name="arch" type="xml">
            <xpath expr="//tree" position="inside">
                <field name="order_line_count" invisible="1"/>
                <field name="num_qty" invisible="1"/>
                <widget name="order_line_count_widget"/>
            </xpath>
        </field>
    </record>
</odoo>

Once integrated, users will see an info icon on each Sales Order row. Clicking it opens a popover with real-time summary data — no need to open each order.

odoo-development odoo-development
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