From 99a9cce9ba03a814dd0c474a63e3489ef541dbba Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Mon, 19 Sep 2022 11:06:27 +0000 Subject: [PATCH] [IMP] base, web: set can_create/can_write only on editable nodes Setting the attributes `can_create` and `can_write` in views which are not editable is useless as you cannot edit the record in these views. This allow to gain some KB when serving the view to the web client, as well as making the view cleaner. e.g. the graph view in the CRM > My Pipeline menu Before: ```xml ``` After: ```xml ``` This revision takes the opportunity to port the `editable` concept from the server to the web client MockServer, in order to be able to test the removal of these attributes from the views in the QUnit tests. The `_editableNode` JS function added here is the translation of the existing `def _editable_node` method in `addons/base/models/ir_ui_view.py` https://github.com/odoo/odoo/blob/f5edde3624f4fe40f87cc7fed7dcfc4bbce1f19f/odoo/addons/base/models/ir_ui_view.py#L1282-L1301 closes odoo/odoo#101902 X-original-commit: f0beddf534a57e972ac3a7bef6afa1caae1dcff3 Signed-off-by: Denis Ledoux (dle) --- .../web/static/tests/helpers/mock_server.js | 38 +++++++++++++++++-- odoo/addons/base/models/ir_ui_view.py | 2 +- 2 files changed, 36 insertions(+), 4 deletions(-) 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)