From 50421af796e9354cbfc6691cba0bf0dc5c989540 Mon Sep 17 00:00:00 2001 From: jpp-odoo <jpp@odoo.com> Date: Mon, 19 Jun 2023 11:46:57 +0000 Subject: [PATCH] [FIX] web: Settings headers should not be dirty When a user changes a header setting, the record should not be marked as dirty. If the user clicks on a button, the save dialog should not be shown. Next commit will bring a functional test and "steps to reproduce" of this issue in website. task-3265100 Part-of: odoo/odoo#125550 Co-authored-by: "Guillaume (gdi)" <gdi@odoo.com> --- .../settings_form_arch_parser.js | 16 ++++ .../settings_form_view/settings_form_view.js | 15 ++++ .../settings_form_view_tests.js | 81 +++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 addons/web/static/src/webclient/settings_form_view/settings_form_arch_parser.js diff --git a/addons/web/static/src/webclient/settings_form_view/settings_form_arch_parser.js b/addons/web/static/src/webclient/settings_form_view/settings_form_arch_parser.js new file mode 100644 index 000000000000..f5e8457908de --- /dev/null +++ b/addons/web/static/src/webclient/settings_form_view/settings_form_arch_parser.js @@ -0,0 +1,16 @@ +/** @odoo-module */ + +import { evaluateExpr } from "@web/core/py_js/py"; +import { formView } from "@web/views/form/form_view"; + +export class SettingsArchParser extends formView.ArchParser { + parseXML() { + const result = super.parseXML(...arguments); + Array.from(result.querySelectorAll(".app_settings_header field")).forEach((el) => { + const options = evaluateExpr(el.getAttribute("options") || "{}"); + options.isHeaderField = true; + el.setAttribute("options", JSON.stringify(options)); + }); + return result; + } +} diff --git a/addons/web/static/src/webclient/settings_form_view/settings_form_view.js b/addons/web/static/src/webclient/settings_form_view/settings_form_view.js index 41924b4c206e..0ed51eae429a 100644 --- a/addons/web/static/src/webclient/settings_form_view/settings_form_view.js +++ b/addons/web/static/src/webclient/settings_form_view/settings_form_view.js @@ -7,6 +7,7 @@ import { SettingsFormController } from "./settings_form_controller"; import { SettingsFormRenderer } from "./settings_form_renderer"; import { SettingsFormCompiler } from "./settings_form_compiler"; import BasicModel from "web.BasicModel"; +import { SettingsArchParser } from "./settings_form_arch_parser"; const BaseSettingsModel = BasicModel.extend({ isNew(id) { @@ -14,6 +15,19 @@ const BaseSettingsModel = BasicModel.extend({ ? true : this._super.apply(this, arguments); }, + _applyChange: function (recordID, changes, options) { + // Check if the changes isHeaderField. + const record = this.localData[recordID]; + let isHeaderField = false; + for (const fieldName of Object.keys(changes)) { + const fieldInfo = record.fieldsInfo[options.viewType][fieldName]; + isHeaderField = fieldInfo.options && fieldInfo.options.isHeaderField; + } + if (isHeaderField) { + options.doNotSetDirty = true; + } + return this._super.apply(this, arguments); + }, }); class SettingsRelationalModel extends formView.Model {} @@ -23,6 +37,7 @@ export const settingsFormView = { ...formView, display: {}, buttonTemplate: "web.SettingsFormView.Buttons", + ArchParser: SettingsArchParser, Model: SettingsRelationalModel, ControlPanel: ControlPanel, Controller: SettingsFormController, diff --git a/addons/web/static/tests/webclient/settings_form_view/settings_form_view_tests.js b/addons/web/static/tests/webclient/settings_form_view/settings_form_view_tests.js index 325b252bfca7..c3431b9e0427 100644 --- a/addons/web/static/tests/webclient/settings_form_view/settings_form_view_tests.js +++ b/addons/web/static/tests/webclient/settings_form_view/settings_form_view_tests.js @@ -1044,6 +1044,87 @@ QUnit.module("SettingsFormView", (hooks) => { } ); + QUnit.test("header field don't dirty settings", async (assert) => { + assert.expect(6); + + serverData.actions = { + 1: { + id: 1, + name: "Settings view", + res_model: "res.config.settings", + type: "ir.actions.act_window", + views: [[1, "form"]], + }, + 4: { + id: 4, + name: "Other action", + res_model: "task", + type: "ir.actions.act_window", + views: [[2, "list"]], + }, + }; + + serverData.views = { + "res.config.settings,1,form": ` + <form string="Settings" js_class="base_settings"> + <div class="settings"> + <div class="app_settings_block" string="CRM" data-key="crm"> + <div class="app_settings_header pt-1 pb-1" style="background-color: #FEF0D0;"> + <div class="col-xs-12 col-md-6 ms-0 o_setting_box"> + <div class="o_setting_right_pane border-start-0 ms-0 ps-0"> + <div class="content-group"> + <div class="row flex-row flex-nowrap mt8 align-items-center"> + <label class="col text-nowrap ml8 flex-nowrap" string="Foo" for="foo_config_id"/> + <field name="foo" title="Foo?."/> + </div> + </div> + </div> + </div> + </div> + <button name="4" string="Execute action" type="action"/> + </div> + </div> + </form>`, + "task,2,list": '<tree><field name="display_name"/></tree>', + "res.config.settings,false,search": "<search></search>", + "task,false,search": "<search></search>", + }; + + const mockRPC = (route, args) => { + if (args.method === "create") { + assert.deepEqual( + args.args[0], + { foo: true }, + "should create a record with foo=true" + ); + } + }; + + const webClient = await createWebClient({ serverData, mockRPC }); + + await doAction(webClient, 1); + + assert.containsNone( + target, + ".o_field_boolean input:checked", + "checkbox should not be checked" + ); + + await click(target.querySelector(".o_field_boolean input")); + assert.containsOnce(target, ".o_field_boolean input:checked", "checkbox should be checked"); + + assert.containsNone( + target, + ".modal-title", + "should not say that there are unsaved changes" + ); + + await click(target.querySelector("button[name='4']")); + assert.containsNone(document.body, ".modal", "should not open a warning dialog"); + + assert.containsOnce(target, ".o_list_view", "should be open list view"); + }); + QUnit.test("clicking a button with dirty settings -- save", async (assert) => { registry.category("services").add( "action", -- GitLab