From 5a68d254949f1d0ff545023cfd14890efff10f5f Mon Sep 17 00:00:00 2001
From: Denis Ledoux <dle@odoo.com>
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#106766

Signed-off-by: Denis Ledoux (dle) <dle@odoo.com>
---
 .../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 66c9d2178cc5..51c9218694bc 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 f71c5572e94e..a1576b7a741b 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: `
+                <tree>
+                    <field name="hex_color" widget="color" />
+                </tree>`,
+        });
+
+        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 d26051e5166b..d98b2e1332c8 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))
@@ -1145,6 +1144,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'),
         }
 
@@ -1291,7 +1291,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)
@@ -1372,6 +1372,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 9d97ea718896..a3e6952404d8 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)
-- 
GitLab