From 0133f4c1833bb39c0753cec2d77bc7622d3c22a6 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Fri, 3 Dec 2021 13:51:14 +0000 Subject: [PATCH] [FIX] web: FieldDomain in debug: domains with dynamic content In debug mode, the FieldDomain displays a textarea to allow the manual edition of complex domains. However, when those domains contain dynamic content (e.g. datetime stuff), the evaluated version of that content was displayed, which isn't what we want. We want to see the raw version of the domain as written in db, and be able to edit it. This commit fixes that issue. Moreover, we also ensure to keep whitespaces and new lines, which can be useful for large domains. closes odoo/odoo#80866 X-original-commit: 15301a1ddaf00e51688d8134b7cb19baf386577c Signed-off-by: Lucas Perais (lpe) Signed-off-by: Aaron Bohy (aab) --- .../src/legacy/js/fields/basic_fields.js | 2 +- .../src/legacy/js/widgets/domain_selector.js | 29 ++++++++--- .../tests/legacy/fields/basic_fields_tests.js | 49 +++++++++++++++++++ .../legacy/widgets/domain_selector_tests.js | 8 ++- 4 files changed, 76 insertions(+), 12 deletions(-) diff --git a/addons/web/static/src/legacy/js/fields/basic_fields.js b/addons/web/static/src/legacy/js/fields/basic_fields.js index 9e0b87c752f..5e79aec3d91 100644 --- a/addons/web/static/src/legacy/js/fields/basic_fields.js +++ b/addons/web/static/src/legacy/js/fields/basic_fields.js @@ -3795,7 +3795,7 @@ var FieldDomain = AbstractField.extend({ // we don't want to recompute the count if the domain has been edited // from the debug textarea (for performance reasons, as it might be costly) this.debugEdition = !!e.data.debug; - this._setValue(Domain.prototype.arrayToString(e.data.domain)); + this._setValue(e.data.domain); }, /** * Called when the in-dialog domain selector value is confirmed diff --git a/addons/web/static/src/legacy/js/widgets/domain_selector.js b/addons/web/static/src/legacy/js/widgets/domain_selector.js index 5f444d59e76..7994e476956 100644 --- a/addons/web/static/src/legacy/js/widgets/domain_selector.js +++ b/addons/web/static/src/legacy/js/widgets/domain_selector.js @@ -377,10 +377,11 @@ var DomainTree = DomainNode.extend({ */ _renderChildrenTo: function ($to) { var $div = $("
"); - return Promise.all(_.map(this.children, (function (child) { + const children = this.children; + return Promise.all(_.map(children, (function (child) { return child.appendTo($div); }).bind(this))).then((function () { - _.each(this.children, function (child) { + _.each(children, function (child) { child.$el.appendTo($to); // Forced to do it this way so that the // children are not misordered }); @@ -469,6 +470,12 @@ var DomainSelector = DomainTree.extend({ domain_changed: "_onDomainChange", }), + init(parent, model, domain) { + this._super(...arguments); + this.rawDomain = domain; + this._redrawId = 0; + }, + start: function () { var self = this; return this._super.apply(this, arguments).then(function () { @@ -541,7 +548,7 @@ var DomainSelector = DomainTree.extend({ // Display technical domain if in debug mode this.$debugInput = this.$(".o_domain_debug_input"); if (this.$debugInput.length) { - this.$debugInput.val(Domain.prototype.arrayToString(this.getDomain())); + this.$debugInput.val(this.rawDomain); dom.autoresize(this.$debugInput); } @@ -557,9 +564,13 @@ var DomainSelector = DomainTree.extend({ * @returns {Promise} */ _redraw: function (domain) { + const _redrawId = ++this._redrawId; var oldChildren = this.children.slice(); this._initialize(domain || this.getDomain()); return this._renderChildrenTo($("
")).then((function () { + if (_redrawId !== this._redrawId) { + return; + } _.each(oldChildren, function (child) { child.destroy(); }); this.renderElement(); this._postRender(); @@ -589,14 +600,19 @@ var DomainSelector = DomainTree.extend({ // is syntax-valid a "domain_changed" event is triggered to notify the // parent, but the widget isn't redrawn. // If the domain is not valid, a warning is shown to the user. - var domain; + const rawDomain = e.currentTarget.value; try { - domain = Domain.prototype.stringToArray(e.currentTarget.value); + Domain.prototype.stringToArray(rawDomain); } catch (err) { // If there is a syntax error, just ignore the change this.displayNotification({ title: _t("Syntax error"), message: _t("Domain not properly formed"), type: 'danger' }); return; } - this.trigger_up("domain_changed", { child: this, noRedraw: true, domain, debug: true }); + this.trigger_up("domain_changed", { + child: this, + noRedraw: true, + domain: rawDomain, + debug: true, + }); }, /** * Called when a (child's) domain has changed -> redraw the entire tree @@ -607,6 +623,7 @@ var DomainSelector = DomainTree.extend({ _onDomainChange: function (e) { // Add the current domain to the payload if not already there e.data.domain = e.data.domain || this.getDomain(); + this.rawDomain = Domain.prototype.arrayToString(e.data.domain); // If a subdomain notifies that it underwent some modifications, the // DomainSelector catches the message and performs a full re-rendering. if (!e.data.noRedraw) { diff --git a/addons/web/static/tests/legacy/fields/basic_fields_tests.js b/addons/web/static/tests/legacy/fields/basic_fields_tests.js index 159f9885311..5b584c5fcc9 100644 --- a/addons/web/static/tests/legacy/fields/basic_fields_tests.js +++ b/addons/web/static/tests/legacy/fields/basic_fields_tests.js @@ -8056,6 +8056,55 @@ QUnit.module('basic_fields', { form.destroy(); }); + QUnit.test('domain field: edit domain with dynamic content', async function (assert) { + assert.expect(2); + + const originalDebug = odoo.debug; + odoo.debug = true; + let rawDomain = ` + [ + ["date", ">=", datetime.datetime.combine(context_today() + relativedelta(days = -365), datetime.time(0, 0, 0)).to_utc().strftime("%Y-%m-%d %H:%M:%S")] + ] + `; + this.data.partner.records[0].foo = rawDomain; + this.data.partner.fields.bar.type = "char"; + this.data.partner.records[0].bar = "partner"; + + const form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: ` +
+ + + `, + async mockRPC(route, args) { + if (args.method === 'write') { + assert.strictEqual(args.args[1].foo, rawDomain); + } + return this._super.apply(this, arguments); + }, + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + assert.strictEqual(form.$(".o_domain_debug_input").val(), rawDomain); + + rawDomain = ` + [ + ["date", ">=", datetime.datetime.combine(context_today() + relativedelta(days = -1), datetime.time(0, 0, 0)).to_utc().strftime("%Y-%m-%d %H:%M:%S")] + ] + `; + await testUtils.fields.editAndTrigger(form.$('.o_domain_debug_input'), rawDomain, ["change"]); + await testUtils.form.clickSave(form); + + form.destroy(); + odoo.debug = originalDebug; + }); + QUnit.module('FieldProgressBar'); QUnit.test('Field ProgressBar: max_value should update', async function (assert) { diff --git a/addons/web/static/tests/legacy/widgets/domain_selector_tests.js b/addons/web/static/tests/legacy/widgets/domain_selector_tests.js index 7ce2c2372df..50e1fb85210 100644 --- a/addons/web/static/tests/legacy/widgets/domain_selector_tests.js +++ b/addons/web/static/tests/legacy/widgets/domain_selector_tests.js @@ -272,15 +272,13 @@ QUnit.module('DomainSelector', { assert.expect(5); const $target = $("#qunit-fixture"); + let newValue; // Create the domain selector and its mock environment const Parent = Widget.extend({ custom_events: { domain_changed: (e) => { - assert.deepEqual(e.data.domain, [ - ["product_id", "ilike", 1], - ["id", "=", 0] - ]); + assert.deepEqual(e.data.domain, newValue); assert.ok(e.data.debug); }, }, @@ -294,7 +292,7 @@ QUnit.module('DomainSelector', { await domainSelector.appendTo($target); assert.containsOnce(domainSelector, ".o_domain_node", "should have a single domain node"); - const newValue = ` + newValue = ` [ ['product_id', 'ilike', 1], ['id', '=', 0]