[IMP] web_editor: add filter-in to m2o/m2m snippet widget
Adds basic support to filter possible m2x values based on another record. This is necessary for the website_appointment snippet introduced within this bundle (see related ENT PR). An important need implemented here is to not store a list of valid IDs in the DOM but fetch them based on data stored by a different widget. Use case covered: Model A has a Many2Many relationship with Model B via a `model_b_ids` field. A first widget (Wa) on a snippet's options allows to select a record of Model A. A second widget (Wb) allows to select records of Model B. ```xml <!--Widget A--> <we-many2many data-model="model.a" data-m2o-field="name" data-fakem2m="true".../> <!--Widget B--> <we-many2many data-model="model.a" data-m2o-field="model_b_ids" data-filter-in="true" .../> ``` Before this commit, the second widget would only be able to show all records of Model B linked to any Model A record and matching a static domain provided as attribute, which is still supported. This commit allows, after having selected `record_a` in the first widget, to only populate the second widget with the records of Model B that are in `record_a.model_b_ids`. Implementing this is done by * Adding `data-filter-in` to Wb's xml attributes (as above) * Calling `Wb.setFilterInDomainIds()` when another record is selected in Wa. Task-2574175 odoo/odoo#90748 See odoo/enterprise#23750 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -1982,21 +1982,9 @@ const ListUserValueWidget = UserValueWidget.extend({
|
||||
const selectedIds = currentValues.map(({ id }) => id)
|
||||
.filter(id => typeof id === 'number');
|
||||
// Note: it's important to simplify the domain at its maximum as the
|
||||
// rpc using it are cached. Similar domains should be written the
|
||||
// same way for the cache to work.
|
||||
const selectedIdsDomain = selectedIds.length ? ['id', 'not in', selectedIds] : null;
|
||||
const selectedIdsDomainIndex = this.createWidget.options.domain.findIndex(domain => domain[0] === 'id' && domain[1] === 'not in');
|
||||
if (selectedIdsDomainIndex > -1) {
|
||||
if (selectedIdsDomain) {
|
||||
this.createWidget.options.domain[selectedIdsDomainIndex] = selectedIdsDomain;
|
||||
} else {
|
||||
this.createWidget.options.domain.splice(selectedIdsDomainIndex, 1);
|
||||
}
|
||||
} else {
|
||||
if (selectedIdsDomain) {
|
||||
this.createWidget.options.domain = [...this.createWidget.options.domain, selectedIdsDomain];
|
||||
}
|
||||
}
|
||||
// rpc using it are cached. Similar domain components should be
|
||||
// written the same way for the cache to work.
|
||||
this.createWidget.options.domainComponents.selected = selectedIds.length ? ['id', 'not in', selectedIds] : null;
|
||||
this.createWidget.setValue('');
|
||||
this.createWidget.inputEl.value = '';
|
||||
$(this.createWidget.inputEl).trigger('input');
|
||||
@@ -2493,7 +2481,11 @@ const Many2oneUserValueWidget = SelectUserValueWidget.extend({
|
||||
}),
|
||||
// Data-attributes that will be read into `this.options` on init and not
|
||||
// transfered to inner buttons.
|
||||
configAttributes: ['model', 'fields', 'limit', 'domain', 'callWith', 'createMethod'],
|
||||
// `domain` is the static part of the domain used in searches, not
|
||||
// depending on already selected ids and other filters.
|
||||
configAttributes: [
|
||||
'model', 'fields', 'limit', 'domain', 'callWith', 'createMethod', 'filterInModel', 'filterInField'
|
||||
],
|
||||
|
||||
/**
|
||||
* @override
|
||||
@@ -2521,6 +2513,7 @@ const Many2oneUserValueWidget = SelectUserValueWidget.extend({
|
||||
options.fields.push('display_name');
|
||||
}
|
||||
options.domain = JSON.parse(options.domain);
|
||||
options.domainComponents = {};
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
@@ -2615,6 +2608,34 @@ const Many2oneUserValueWidget = SelectUserValueWidget.extend({
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
* Updates the domain with defined inclusive filter to make sure that only
|
||||
* records that are linked to specific records are retrieved.
|
||||
* Filtering-in is configured with
|
||||
* * a `filterInModel` attribute, the linked model
|
||||
* * a `filterInField` attribute, field of the linked model holding
|
||||
* allowed values for this widget
|
||||
*
|
||||
* @param {integer[]} linkedRecordsIds
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async setFilterInDomainIds(linkedRecordsIds) {
|
||||
const allowedIds = new Set();
|
||||
if (linkedRecordsIds) {
|
||||
const parentRecordsData = await this._rpc({
|
||||
model: this.options.filterInModel,
|
||||
method: 'search_read',
|
||||
fields: [this.options.filterInField],
|
||||
domain: [['id', 'in', linkedRecordsIds]],
|
||||
});
|
||||
parentRecordsData.forEach(record => {
|
||||
record[this.options.filterInField].forEach(item => allowedIds.add(item));
|
||||
});
|
||||
}
|
||||
if (allowedIds.size) {
|
||||
this.options.domainComponents.filterInModel = ['id', 'in', [...allowedIds]];
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
@@ -2643,7 +2664,9 @@ const Many2oneUserValueWidget = SelectUserValueWidget.extend({
|
||||
method: 'name_search',
|
||||
kwargs: {
|
||||
name: needle,
|
||||
args: this.options.domain,
|
||||
args: this.options.domain.concat(
|
||||
Object.values(this.options.domainComponents).filter(item => item !== null)
|
||||
),
|
||||
operator: "ilike",
|
||||
limit: this.options.limit + 1,
|
||||
},
|
||||
@@ -2819,7 +2842,7 @@ const Many2oneUserValueWidget = SelectUserValueWidget.extend({
|
||||
});
|
||||
|
||||
const Many2manyUserValueWidget = UserValueWidget.extend({
|
||||
configAttributes: ['model', 'recordId', 'm2oField', 'createMethod', 'fakem2m'],
|
||||
configAttributes: ['model', 'recordId', 'm2oField', 'createMethod', 'fakem2m', 'filterIn'],
|
||||
|
||||
/**
|
||||
* @override
|
||||
@@ -2832,6 +2855,12 @@ const Many2manyUserValueWidget = UserValueWidget.extend({
|
||||
delete dataAttributes[attr];
|
||||
}
|
||||
});
|
||||
this.filterIn = options.filterIn !== undefined;
|
||||
if (this.filterIn) {
|
||||
// Transfer filter-in values to child m2o.
|
||||
dataAttributes.filterInModel = options.model;
|
||||
dataAttributes.filterInField = options.m2oField;
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
@@ -2901,6 +2930,19 @@ const Many2manyUserValueWidget = UserValueWidget.extend({
|
||||
this.listWidget.el.querySelector('we-select').style.position = 'static';
|
||||
this.el.style.position = 'relative';
|
||||
},
|
||||
/**
|
||||
* Only allow to fetch/select records which are linked (via `m2oField`) to the
|
||||
* specified records.
|
||||
*
|
||||
* @param {integer[]} linkedRecordsIds
|
||||
* @returns {Promise}
|
||||
* @see Many2oneUserValueWidget.setFilterInDomainIds
|
||||
*/
|
||||
async setFilterInDomainIds(linkedRecordsIds) {
|
||||
if (this.filterIn) {
|
||||
return this.listWidget.createWidget.setFilterInDomainIds(linkedRecordsIds);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
|
||||
@@ -637,7 +637,7 @@
|
||||
<!-- /!\ drop-in here is partly duplicated for s_popup (see dedicated options) -->
|
||||
<!-- TODO should be improved -->
|
||||
|
||||
<t t-set="so_content_addition_selector" t-translation="off">blockquote, .s_card:not(.s_timeline_card), .s_alert, .o_facebook_page, .s_share, .s_social_media, .s_rating, .s_hr, .s_google_map, .s_map, .s_countdown, .s_chart, .s_text_highlight, .s_progress_bar, .s_badge, .s_embed_code, .s_donation, .s_add_to_cart</t>
|
||||
<t t-set="so_content_addition_selector" t-translation="off">blockquote, .s_card:not(.s_timeline_card), .s_alert, .o_facebook_page, .s_share, .s_social_media, .s_rating, .s_hr, .s_google_map, .s_map, .s_countdown, .s_chart, .s_text_highlight, .s_progress_bar, .s_badge, .s_embed_code, .s_donation, .s_add_to_cart, .s_online_appointment</t>
|
||||
<div id="so_content_addition"
|
||||
t-att-data-selector="so_content_addition_selector"
|
||||
t-attf-data-drop-near="p, h1, h2, h3, ul, ol, .row > div:not(.o_grid_item_image) > img, #{so_content_addition_selector}"
|
||||
|
||||
Reference in New Issue
Block a user