[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) <aab@odoo.com>
Signed-off-by: Bruno Boi (boi) <boi@odoo.com>
This commit is contained in:
Bruno Boi
2022-10-07 13:37:07 +02:00
parent 26ea19804c
commit 751ed08c3b
2 changed files with 102 additions and 6 deletions
@@ -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,
@@ -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": "<search></search>",
"res.company,false,list": `<tree><field name="name"/></tree>`,
"res.company,bar_rescompany_form_view,form": `<form><field name="name"/></form>`,
"partner_type,false,search": "<search></search>",
"partner_type,false,list": `<tree><field name="name"/></tree>`,
"partner_type,foo_partner_type_form_view,form": `
<form>
<field name="color"/>
<field name="company_ids" context="{
'default_color': 2,
'form_view_ref': 'bar_rescompany_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: `
<form>
<field string="Partner Types" name="timmy" widget="one2many" context="{
'default_partner_id': active_id,
'form_view_ref': 'foo_partner_type_form_view'
}"/>
</form>`,
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",