diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js index 5f47c0986c5..d65533fda78 100644 --- a/addons/web/static/tests/helpers/mock_server.js +++ b/addons/web/static/tests/helpers/mock_server.js @@ -234,6 +234,7 @@ export class MockServer { const processedNodes = params.processedNodes || []; const { arch, context, modelName } = params; const level = params.level || 0; + const editable = params.editable || true; const fields = deepCopy(params.fields); function isNodeProcessed(node) { return processedNodes.findIndex((n) => n.isSameNode(node)) > -1; @@ -249,6 +250,7 @@ export class MockServer { } else { doc = arch; } + const editableView = editable && this._editableNode(doc, modelName); const inTreeView = doc.tagName === "tree"; const inFormView = doc.tagName === "form"; // mock _postprocess_access_rights @@ -271,7 +273,11 @@ export class MockServer { const isGroupby = node.tagName === "groupby"; if (isField) { const fieldName = node.getAttribute("name"); - fieldNodes[fieldName] = { node, isInvisible: node.getAttribute("invisible") }; + fieldNodes[fieldName] = { + node, + isInvisible: node.getAttribute("invisible"), + isEditable: editableView && this._editableNode(node, modelName), + }; // 'transfer_field_to_modifiers' simulation const field = fields[fieldName]; if (!field) { @@ -352,9 +358,9 @@ export class MockServer { }); Object.keys(fieldNodes).forEach((field) => relatedModels[modelName].add(field)); let relModel, relFields; - Object.entries(fieldNodes).forEach(([name, { node, isInvisible }]) => { + Object.entries(fieldNodes).forEach(([name, { node, isInvisible, isEditable }]) => { const field = fields[name]; - if (field.type === "many2one" || field.type === "many2many") { + if (isEditable && (field.type === "many2one" || field.type === "many2many")) { const canCreate = node.getAttribute("can_create"); node.setAttribute("can_create", canCreate || "true"); const canWrite = node.getAttribute("can_write"); @@ -401,6 +407,7 @@ export class MockServer { context, processedNodes, level: level + 1, + editable: editableView, }); Object.entries(models).forEach(([modelName, fields]) => { relatedModels[modelName] = relatedModels[modelName] || new Set(); @@ -431,6 +438,7 @@ export class MockServer { fields: relFields, context, processedNodes, + editable: false, }); Object.entries(models).forEach(([modelName, fields]) => { relatedModels[modelName] = relatedModels[modelName] || new Set(); @@ -453,6 +461,30 @@ export class MockServer { }; } + _editableNode(node, modelName) { + switch (node.tagName) { + case "form": + return true; + case "tree": + return !!node.getAttribute("editable"); + case "field": { + const fname = node.getAttribute("name"); + const field = this.models[modelName].fields[fname]; + return ( + (!field.readonly || + (field.states && + Object.values(field.states).some((item) => + item.includes("readonly") + ))) && + (!["1", "True"].includes(node.getAttribute("readonly")) || + !_.isEmpty(evaluateExpr(node.getAttribute("attrs") || "{}"))) + ); + } + default: + return false; + } + } + /** * Converts an Object representing a record to actual return Object of the * python `onchange` method. diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index d9ea78d7945..a4d8274c8fd 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -1251,7 +1251,7 @@ actual arch. self._postprocess_view( child, field.comodel_name, editable=node_info['editable'], parent_name_manager=name_manager, ) - if field.type in ('many2one', 'many2many'): + if node_info['editable'] and field.type in ('many2one', 'many2many'): node.set('model_access_rights', field.comodel_name) name_manager.has_field(node, node.get('name'), attrs)