From 751ed08c3b0dceef6730a59e2dc8c39664a2819c Mon Sep 17 00:00:00 2001 From: Bruno Boi Date: Mon, 3 Oct 2022 11:37:17 +0000 Subject: [PATCH] [FIX] web: nested x2many context fix - Before this commit Suppose that in a form view, there is an x2many list view that would open a form view dialog when edited. For the dialog to open, the concerned record is duplicated and passed in props to a FormRenderer. Now suppose that this form view dialog also has an x2many list view rendered somewhere: the StaticList datapoint attached to this list will have the same context as its parent. This is not what we want because some of the keys of the context does not make sense with the list's model. In fact, it has led to tracebacks to be displayed in case of we want to open a record of this list in another form view dialog. - After this commit When those lists are created, their context are cleared from their parents' keys starting with "default_" or ending with "_view_ref". closes odoo/odoo#102557 X-original-commit: 1ba468706295f651105ca3fc8aae477b4f05c28e Signed-off-by: Aaron Bohy (aab) Signed-off-by: Bruno Boi (boi) --- .../src/legacy/js/views/basic/basic_model.js | 25 ++++-- .../tests/views/form/form_view_tests.js | 83 +++++++++++++++++++ 2 files changed, 102 insertions(+), 6 deletions(-) diff --git a/addons/web/static/src/legacy/js/views/basic/basic_model.js b/addons/web/static/src/legacy/js/views/basic/basic_model.js index cf63583bb49..ff6b8e61f4d 100644 --- a/addons/web/static/src/legacy/js/views/basic/basic_model.js +++ b/addons/web/static/src/legacy/js/views/basic/basic_model.js @@ -3096,9 +3096,18 @@ var BasicModel = AbstractModel.extend({ var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode]; var fieldsInfo = view ? view.fieldsInfo : (fieldInfo.fieldsInfo || {}); var ids = record.data[fieldName] || []; + + // remove default_* & *_view_ref keys from record context + const recordContext = { ...record.context }; + for (const key in recordContext) { + if (key.startsWith("default_") || key.endsWith("_view_ref")) { + delete recordContext[key]; + } + } + var list = self._makeDataPoint({ count: ids.length, - context: _.extend({}, record.context, field.context), + context: Object.assign({}, recordContext, field.context), fieldsInfo: fieldsInfo, fields: view ? view.fields : fieldInfo.relatedFields, limit: fieldInfo.limit, @@ -4599,12 +4608,16 @@ var BasicModel = AbstractModel.extend({ var fields = view ? view.fields : fieldInfo.relatedFields; var viewType = view ? view.type : fieldInfo.viewType; - // remove default_* keys from parent context to avoid issue of same field name in x2m - var parentContext = _.omit(record.context, function (val, key) { - return _.str.startsWith(key, 'default_'); - }); + // remove default_* & *_view_ref keys from record context + const recordContext = { ...record.context }; + for (const key in recordContext) { + if (key.startsWith("default_") || key.endsWith("_view_ref")) { + delete recordContext[key]; + } + } + var x2manyList = self._makeDataPoint({ - context: parentContext, + context: recordContext, fieldsInfo: fieldsInfo, fields: fields, limit: fieldInfo.limit, diff --git a/addons/web/static/tests/views/form/form_view_tests.js b/addons/web/static/tests/views/form/form_view_tests.js index 64933234af1..fb6927b5ef4 100644 --- a/addons/web/static/tests/views/form/form_view_tests.js +++ b/addons/web/static/tests/views/form/form_view_tests.js @@ -942,6 +942,89 @@ QUnit.module("Views", (hooks) => { await click(target.querySelector('.o_field_widget[name="product_id"] .o_external_button')); }); + QUnit.test("Form and subsubview with default_ or _view_ref contexts", async function (assert) { + serverData.models.partner_type.fields.company_ids = { + string: "one2many field", + type: "one2many", + relation: "res.company", + }; + serverData.views = { + "res.company,false,search": "", + "res.company,false,list": ``, + "res.company,bar_rescompany_form_view,form": `
`, + "partner_type,false,search": "", + "partner_type,false,list": ``, + "partner_type,foo_partner_type_form_view,form": ` +
+ + + `, + }; + + const userContext = { + lang: "en", + tz: "taht", + uid: 7, + }; + const expectedContexts = new Map(); + + // Make main form view + expectedContexts.set("partner", { ...userContext }); + expectedContexts.set("partner_type", { + ...userContext, + base_model_name: "partner", + form_view_ref: "foo_partner_type_form_view", + }); + await makeView({ + type: "form", + resModel: "partner", + serverData, + arch: ` +
+ + `, + resId: 2, + mockRPC: (route, { method, model, kwargs }) => { + if (["get_views", "onchange"].includes(method)) { + const { context } = kwargs; + assert.step(`${method} (${model})`); + assert.deepEqual(context, expectedContexts.get(model)); + } + }, + }); + assert.verifySteps(["get_views (partner)", "get_views (partner_type)"]); + + // Add a line in the x2many timmy field + expectedContexts.clear(); + expectedContexts.set("partner_type", { + ...userContext, + default_partner_id: 2, + form_view_ref: "foo_partner_type_form_view", + }); + await click(target, "[name=timmy] .o_field_x2many_list_row_add a"); + assert.verifySteps(["get_views (partner_type)"]); + + // Create a new timmy + await click(target, ".modal .o_create_button"); + assert.verifySteps(["get_views (partner_type)", "onchange (partner_type)"]); + + // Create a new company + expectedContexts.clear(); + expectedContexts.set("res.company", { + ...userContext, + default_color: 2, + form_view_ref: "bar_rescompany_form_view", + }); + await click(target, ".modal [name=company_ids] .o_field_x2many_list_row_add a"); + assert.verifySteps(["get_views (res.company)", "onchange (res.company)"]); + }); + QUnit.test("invisible fields are properly hidden", async function (assert) { await makeView({ type: "form",