Odoo has many widgets that ready to use, such as many2many_tags, image, html, handle etc. How to use those widgets are relatively easy. But of all the widgets that Odoo has, I think the many2many_tags widget is kinda weird. Why ? Because it can’t be clicked.
I mean if I click on that widget, I am not be redirected to another page to see the detail, unlike his sibling, the many2one. In this article I will share how to change the odoo’s widget, precisely changing the many2many_tags widget so that it can be clicked like many2one.
Where is the widget source code ?
Widgets are usually written in javascript and xml files in the web module. The many2many_tags widget javascript file is written at /web/static/src/js/fields/relational_fields.js . While the xml file or template is written at /web/static/src/xml/base.xml.
Template
Template are an xml file that will be rendered when the widget is running. To modify the many2many_tags widget, first we must create a template. Create an xml file with the name of template.xml or another name in your module or addon, precisely in /static/src/xml directory, then copy and paste the code below.
<?xml version="1.0" encoding="UTF-8"?> <templates id="template" xml:space="preserve"> <t t-name="FieldMany2ManyTagsLink"> <t t-foreach="elements" t-as="el"> <t t-set="color" t-value="el[colorField] || 0"/> <t t-set="colornames" t-value="['No color', 'Red', 'Orange', 'Yellow', 'Light blue', 'Dark purple', 'Salmon pink', 'Medium blue', 'Dark blue', 'Fushia', 'Green', 'Purple']"/> <div t-attf-class="badge badge-pill dropdown o_tag_color_#{color}" t-att-data-color="color" t-att-data-index="el_index" t-att-data-id="el.id" t-attf-title="Tag color: #{colornames[color]}"> <t t-set="_badge_text"> <a href="#" class="o_external_link" t-att-modelid="el.id"> <span class="o_badge_text" t-att-title="el.display_name" ><span role="img" t-attf-aria-label="Tag color: #{colornames[color]}"/><t t-esc="el.display_name"/></span> </a> </t> <t t-if="colorField"> <a href="#" class="dropdown-toggle o-no-caret" data-toggle="dropdown"> <t t-raw="_badge_text"/> </a> </t> <t t-else=""> <t t-raw="_badge_text"/> </t> <a t-if="!readonly" href="#" class="fa fa-times o_delete" title="Delete" aria-label="Delete"></a> </div> </t> </t> </templates>
The code above is not much different from the original many2many_tags template, I only change the name to FieldMany2ManyTagsLink, add the o_external_link class as a trigger when we click the widget, and add the modelid attribute to store the model’s primary key.
Next, create a javascript file in your module or addon precisely in /static/src/js/ directory with any name like widget.js.
Define a Javascript Module
First, you should define a javascript module by write the code like below.
odoo.define('many2many_tags_link.widget', function (require) { "use strict"; // place your code here });
Pay attention to the first parameter in the define function above. Usually, the formula is your_module_name.any_text. The purpose of the code above is so the module that you write is easy to be overridden by other modules, and so that your code is executed in the proper order by odoo. See the next section.
Dependency
Dependency are like the import function in python. To override the many2many_tags widget, at least we must import some javascript module like the code below.
odoo.define('many2many_tags_link.widget', function (require) { "use strict"; var core = require('web.core'); var AbstractField = require('web.AbstractField'); var registry = require('web.field_registry'); var relational_fields = require('web.relational_fields'); });
From the code above your code will not be loaded by odoo before the web.core, web.AbstractField etc code has finished loading. This is how the dependency work in Odoo.
Pay attention to the relational_fields variable. If you notice, the web.relational_fields is the code that was written in the /web/static/src/js/fields/relational_fields.js file. So in odoo, dependency are not written based on the file name where the code is written. But based on what text that you insert as a parameter in the odoo.define function. I mean to import some odoo’s javasrcript module we don’t have to write the file name completely include its directory.
Include or Extend
Include is used if we want to change the functionality of a widget everywhere, in any view. While the extend is the same as inheritance, we can determine where this change is applied manually. Because I don’t want all views that use the many2many_tags widget can be clicked on all views, then I will use the extend, so I can determine which many2many_tags widget views that can be clicked and which are not. Look at the code below.
// define module baru dengan rumus nama_module.teks_bebas_atau_nama_file odoo.define('many2many_tags_link.widget', function (require) { "use strict"; // load all dependencies that wee need, think of it as the same as import function in python var core = require('web.core'); var AbstractField = require('web.AbstractField'); var registry = require('web.field_registry'); var relational_fields = require('web.relational_fields'); var _t = core._t; var qweb = core.qweb; // start override the relational_fields module by calling the include or extend function // the relational_fields variable has many objects, if necessary try the console.log to show them // in this code we try to overide the FieldMany2ManyTags object // if you want to use an include function where the changes will take effect in all over the place use this code // var FieldMany2ManyTagsLink = relational_fields.FieldMany2ManyTags.include // here we are using the extend function because we want the changes only take effect in certain places var FieldMany2ManyTagsLink = relational_fields.FieldMany2ManyTags.extend({ tag_template: "FieldMany2ManyTagsLink", // call template or xml file events: _.extend({}, AbstractField.prototype.events, { // this is list of action 'click .o_delete': '_onDeleteTag', // this action is exist in original odoo's source code 'click .o_external_link': '_openRelated', // I add this action, if we click the widget odoo will call the _openRelated method }), _openRelated: function (event) { // actually I copy the concept of the _onClick function on FieldMany2One object // in the same file where the original many2many_tag widget code was written // see the /web/static/src/js/fields/relational_fields.js file event.preventDefault(); event.stopPropagation(); var self = this; var modelid = parseInt(event.currentTarget.getAttribute('modelid')); if (this.mode === 'readonly' && !this.noOpen && modelid) { this._rpc({ model: this.field.relation, method: 'get_formview_action', args: [[modelid]], context: this.record.getContext(this.recordParams), }) .then(function (action) { self.trigger_up('do_action', {action: action}); }); } }, }); // this code is written so that our widget can be called by writing // <field name="sales_person_ids" widget="many2many_tags_link" /> // in the erp xml file // if we use the extend function and this code is not written, the widget cannot be used registry .add('many2many_tags_link', FieldMany2ManyTagsLink); // This code is written so that the widget that we write can be used as dependency by other modules and can be overridden // the widget keeps running if we don't write this return { FieldMany2ManyTagsLink: FieldMany2ManyTagsLink, } });
Lastly, load the javascript and xml files that just we created above. For more detail please download the completed module here and take a look at the manifest.py and views/view.xml files.
This article was written based on odoo 12.