From 59e32699884dffb2a0ae70b197b7e370938760da Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Tue, 29 Nov 2022 09:24:12 +0000 Subject: [PATCH] [FIX] base, web: transfer readonly/required modifiers for kanban/tree Since odoo/odoo@69c3d5aa25655173ed66a52570559322c650c7df the readonly and required modifiers were no longer passed for non-editable views. Which means: - kanban: never passed, - tree: passed only when `editable="1"` or `multi_edit="1"`, - form: always passed. The rationale behind that is that a view which is considered as readonly doesn't need to know if its field are readonly or required, as by definition, if the view is readonly, the user shouldn't be able to modify anything in the view. This was to make the views sent to the web client more light-weight, not sending unused information, and save KB of transfers. However, some widgets, used in the kanban and non-editable tree, allow to modify fields, even in readonly considered views, and therefore need to know these readonly/required modifiers from the field definition in the Python model. e.g. the `widget="color"`, odoo/odoo#103478 Ideally, these readonly/required modifiers should be transferred only when the widget requires it, to avoid transferring the readonly/required modifiers when they are not useful in kanban/tree views (which is 99% of the time). However, this would require a bigger refactoring, which is considered too risky compared to the added value for a stable release. task-3013110 closes odoo/odoo#106838 X-original-commit: 5a68d254949f1d0ff545023cfd14890efff10f5f Signed-off-by: Denis Ledoux (dle) --- .../web/static/tests/helpers/mock_server.js | 12 ++++++++++- .../tests/views/fields/color_field_tests.js | 20 +++++++++++++++++++ odoo/addons/base/models/ir_ui_view.py | 12 ++++++++--- odoo/addons/base/tests/test_views.py | 2 +- 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js index 8fab67c638a..5517dcd4c1d 100644 --- a/addons/web/static/tests/helpers/mock_server.js +++ b/addons/web/static/tests/helpers/mock_server.js @@ -252,6 +252,7 @@ export class MockServer { } const editableView = editable && this._editableNode(doc, modelName); const onchangeAbleView = this._onchangeAbleView(doc); + const modifiersFromModel = this._modifiersFromModel(doc); const inTreeView = ["tree", "list"].includes(doc.tagName); const inFormView = doc.tagName === "form"; // mock _postprocess_access_rights @@ -286,7 +287,8 @@ export class MockServer { } const defaultValues = {}; const stateExceptions = {}; // what is this ? - ((editableView && modifiersNames) || ["invisible"]).forEach((attr) => { + + modifiersFromModel.forEach((attr) => { stateExceptions[attr] = []; defaultValues[attr] = !!field[attr]; }); @@ -496,6 +498,14 @@ export class MockServer { } } + _modifiersFromModel(node) { + const modifiersNames = ['invisible']; + if (['kanban', 'tree', 'form'].includes(node.tagName)) { + modifiersNames.push(...['readonly', 'required']); + } + return modifiersNames; + } + /** * Converts an Object representing a record to actual return Object of the * python `onchange` method. diff --git a/addons/web/static/tests/views/fields/color_field_tests.js b/addons/web/static/tests/views/fields/color_field_tests.js index f71c5572e94..a1576b7a741 100644 --- a/addons/web/static/tests/views/fields/color_field_tests.js +++ b/addons/web/static/tests/views/fields/color_field_tests.js @@ -119,6 +119,26 @@ QUnit.module("Fields", (hooks) => { ); }); + QUnit.test("color field read-only in model definition, in non-editable list", async function (assert) { + serverData.models.partner.fields.hex_color.readonly = true; + await makeView({ + type: "list", + serverData, + resModel: "partner", + arch: ` + + + `, + }); + + assert.containsN( + target, + '.o_field_color input:disabled', + 2, + "the field should not be editable" + ); + }); + QUnit.test("color field change via another field's onchange", async (assert) => { serverData.models.partner.onchanges = { foo: (rec) => { diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index 5c26c00d457..f3fd90e8738 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -59,10 +59,9 @@ def att_names(name): yield f"t-attf-{name}" -def transfer_field_to_modifiers(field, modifiers, view_editable=True): +def transfer_field_to_modifiers(field, modifiers, attributes): default_values = {} state_exceptions = {} - attributes = ('invisible', 'readonly', 'required') if view_editable else ('invisible',) for attr in attributes: state_exceptions[attr] = [] default_values[attr] = bool(field.get(attr)) @@ -1155,6 +1154,7 @@ actual arch. root_info = { 'view_type': root.tag, 'view_editable': editable and self._editable_node(root, name_manager), + 'view_modifiers_from_model': self._modifiers_from_model(root), 'mobile': options.get('mobile'), } @@ -1301,7 +1301,7 @@ actual arch. field_info = name_manager.field_info.get(node.get('name')) if field_info: - transfer_field_to_modifiers(field_info, node_info['modifiers'], node_info['view_editable']) + transfer_field_to_modifiers(field_info, node_info['modifiers'], node_info['view_modifiers_from_model']) def _postprocess_tag_form(self, node, name_manager, node_info): result = name_manager.model.view_header_get(False, node.tag) @@ -1382,6 +1382,12 @@ actual arch. def _onchange_able_view_kanban(self, node): return True + def _modifiers_from_model(self, node): + modifier_names = ['invisible'] + if node.tag in ('kanban', 'tree', 'form'): + modifier_names += ['readonly', 'required'] + return modifier_names + #------------------------------------------------------------------- # view validation #------------------------------------------------------------------- diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py index 9d97ea71889..a3e6952404d 100644 --- a/odoo/addons/base/tests/test_views.py +++ b/odoo/addons/base/tests/test_views.py @@ -1738,7 +1738,7 @@ class TestViews(ViewCase): def _test_modifiers(what, expected): modifiers = {} if isinstance(what, dict): - transfer_field_to_modifiers(what, modifiers) + transfer_field_to_modifiers(what, modifiers, ['invisible', 'readonly', 'required']) else: node = etree.fromstring(what) if isinstance(what, str) else what transfer_node_to_modifiers(node, modifiers)