[IMP] web: add relatedFields in x2m sub-views
The goal of this commit is to allow relatedFields to be added to a one2many field using a sub-view. Before this commit, if an x2many custom contains a sub-view and relatedFields, we ignore the sub-view and use only the relatedFields. Now we're going to add the relatedFields to the fields/activeFields defined in the sub-view. Use case: Created a custom x2many field dependent on the 'display_name' of a many2one field that should not be present in the sub-view. For example, to simulate a groupBy on this many2one field. closes odoo/odoo#132351 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
@@ -225,6 +225,22 @@ export function extractFieldsFromArchInfo({ fieldNodes, widgetNodes }, fields) {
|
||||
);
|
||||
Object.assign(activeField.related.fields, formArchInfo.fields);
|
||||
}
|
||||
|
||||
if (fieldNode.viewMode !== "default" && fieldNode.views.default) {
|
||||
const defaultArchInfo = extractFieldsFromArchInfo(
|
||||
fieldNode.views.default,
|
||||
fieldNode.views.default.fields
|
||||
);
|
||||
activeField.related.activeFields = {
|
||||
...defaultArchInfo.activeFields,
|
||||
...activeField.related.activeFields,
|
||||
};
|
||||
activeField.related.fields = Object.assign(
|
||||
{},
|
||||
defaultArchInfo.fields,
|
||||
activeField.related.fields
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fieldNode.field?.useSubView) {
|
||||
|
||||
@@ -312,39 +312,47 @@ Field.parseFieldNode = function (node, models, modelName, viewType, jsClass) {
|
||||
if (relatedFields instanceof Function) {
|
||||
relatedFields = relatedFields(fieldInfo);
|
||||
}
|
||||
for (const relatedField of relatedFields) {
|
||||
if (!("readonly" in relatedField)) {
|
||||
relatedField.readonly = true;
|
||||
}
|
||||
}
|
||||
relatedFields = Object.fromEntries(relatedFields.map((f) => [f.name, f]));
|
||||
views.default = { fieldNodes: relatedFields, fields: relatedFields };
|
||||
fieldInfo.viewMode = "default";
|
||||
} else {
|
||||
for (const child of node.children) {
|
||||
const viewType = child.tagName === "tree" ? "list" : child.tagName;
|
||||
const { ArchParser } = viewRegistry.get(viewType);
|
||||
const xmlSerializer = new XMLSerializer();
|
||||
const subArch = xmlSerializer.serializeToString(child);
|
||||
const archInfo = new ArchParser().parse(subArch, models, fields[name].relation);
|
||||
views[viewType] = {
|
||||
...archInfo,
|
||||
limit: archInfo.limit || 40,
|
||||
fields: models[fields[name].relation],
|
||||
};
|
||||
if (!fieldInfo.field.useSubView) {
|
||||
fieldInfo.viewMode = "default";
|
||||
}
|
||||
}
|
||||
for (const child of node.children) {
|
||||
const viewType = child.tagName === "tree" ? "list" : child.tagName;
|
||||
const { ArchParser } = viewRegistry.get(viewType);
|
||||
const xmlSerializer = new XMLSerializer();
|
||||
const subArch = xmlSerializer.serializeToString(child);
|
||||
const archInfo = new ArchParser().parse(subArch, models, fields[name].relation);
|
||||
views[viewType] = {
|
||||
...archInfo,
|
||||
limit: archInfo.limit || 40,
|
||||
fields: models[fields[name].relation],
|
||||
};
|
||||
}
|
||||
|
||||
let viewMode = node.getAttribute("mode");
|
||||
if (!viewMode) {
|
||||
if (views.list && !views.kanban) {
|
||||
viewMode = "list";
|
||||
} else if (!views.list && views.kanban) {
|
||||
viewMode = "kanban";
|
||||
} else if (views.list && views.kanban) {
|
||||
viewMode = isSmall() ? "kanban" : "list";
|
||||
}
|
||||
let viewMode = node.getAttribute("mode");
|
||||
if (viewMode) {
|
||||
if (viewMode.split(",").length !== 1) {
|
||||
viewMode = isSmall() ? "kanban" : "list";
|
||||
} else {
|
||||
if (viewMode.split(",").length !== 1) {
|
||||
viewMode = isSmall() ? "kanban" : "list";
|
||||
} else {
|
||||
viewMode = viewMode === "tree" ? "list" : viewMode;
|
||||
}
|
||||
viewMode = viewMode === "tree" ? "list" : viewMode;
|
||||
}
|
||||
} else {
|
||||
if (views.list && !views.kanban) {
|
||||
viewMode = "list";
|
||||
} else if (!views.list && views.kanban) {
|
||||
viewMode = "kanban";
|
||||
} else if (views.list && views.kanban) {
|
||||
viewMode = isSmall() ? "kanban" : "list";
|
||||
}
|
||||
}
|
||||
if (viewMode) {
|
||||
fieldInfo.viewMode = viewMode;
|
||||
}
|
||||
if (Object.keys(views).length) {
|
||||
|
||||
@@ -274,7 +274,7 @@ export const many2ManyTagsField = {
|
||||
relatedFields: ({ options }) => {
|
||||
const relatedFields = [{ name: "display_name", type: "char" }];
|
||||
if (options.color_field) {
|
||||
relatedFields.push({ name: options.color_field, type: "integer" });
|
||||
relatedFields.push({ name: options.color_field, type: "integer", readonly: false });
|
||||
}
|
||||
return relatedFields;
|
||||
},
|
||||
|
||||
@@ -26,8 +26,8 @@ import {
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import {
|
||||
toggleActionMenu,
|
||||
toggleSearchBarMenu,
|
||||
toggleMenuItem,
|
||||
toggleSearchBarMenu,
|
||||
} from "@web/../tests/search/helpers";
|
||||
import { makeView, makeViewInDialog, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
|
||||
@@ -41,10 +41,11 @@ import { tooltipService } from "@web/core/tooltip/tooltip_service";
|
||||
import { SIZES } from "@web/core/ui/ui_service";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { session } from "@web/session";
|
||||
import { Field } from "@web/views/fields/field";
|
||||
import { CharField } from "@web/views/fields/char/char_field";
|
||||
import { DateTimeField } from "@web/views/fields/datetime/datetime_field";
|
||||
import { Field } from "@web/views/fields/field";
|
||||
import { IntegerField } from "@web/views/fields/integer/integer_field";
|
||||
import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field";
|
||||
import { FormController } from "@web/views/form/form_controller";
|
||||
import { companyService } from "@web/webclient/company_service";
|
||||
|
||||
@@ -13985,4 +13986,122 @@ QUnit.module("Views", (hooks) => {
|
||||
assert.equal(target.querySelectorAll(".o_data_cell")[0].innerText, "updated");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("custom x2many with relatedFields and list view inline", async function (assert) {
|
||||
class MyField extends X2ManyField {}
|
||||
fieldRegistry.add("my_widget", {
|
||||
...x2ManyField,
|
||||
component: MyField,
|
||||
relatedFields: [
|
||||
{ name: "trululu", type: "many2one", relation: "partner" },
|
||||
{ name: "int_field", type: "integer" },
|
||||
],
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="p" widget='my_widget'>
|
||||
<tree editable="bottom" >
|
||||
<field name="foo"/>
|
||||
<field name="int_field" />
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
resId: 2,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "web_read") {
|
||||
assert.step("web_read");
|
||||
assert.deepEqual(args.kwargs.specification.p.fields, {
|
||||
trululu: { fields: { display_name: {} } },
|
||||
foo: {},
|
||||
int_field: {},
|
||||
});
|
||||
} else if (args.method === "write") {
|
||||
assert.step("write");
|
||||
assert.deepEqual(args.args[1].p[0][2], {
|
||||
foo: "new record",
|
||||
int_field: 0,
|
||||
});
|
||||
} else if (args.method === "web_save") {
|
||||
assert.step("web_save");
|
||||
assert.deepEqual(args.kwargs.specification.p.fields, {
|
||||
trululu: { fields: { display_name: {} } },
|
||||
foo: {},
|
||||
int_field: {},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await addRow(target);
|
||||
await editInput(target, ".o_data_row [name='foo'] input", "new record");
|
||||
await clickSave(target);
|
||||
assert.verifySteps(["web_read", "web_save"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"custom x2many with relatedFields and list view not inline",
|
||||
async function (assert) {
|
||||
class MyField extends X2ManyField {}
|
||||
fieldRegistry.add("my_widget", {
|
||||
...x2ManyField,
|
||||
component: MyField,
|
||||
relatedFields: [
|
||||
{ name: "trululu", type: "many2one", relation: "partner" },
|
||||
{ name: "int_field", type: "integer" },
|
||||
],
|
||||
});
|
||||
|
||||
serverData.views = {
|
||||
"partner,false,list": `
|
||||
<tree editable="bottom" >
|
||||
<field name="foo"/>
|
||||
<field name="int_field" />
|
||||
</tree>`,
|
||||
};
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="p" widget='my_widget'/>
|
||||
</form>`,
|
||||
resId: 2,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "web_read") {
|
||||
assert.step("web_read");
|
||||
assert.deepEqual(args.kwargs.specification.p.fields, {
|
||||
trululu: { fields: { display_name: {} } },
|
||||
foo: {},
|
||||
int_field: {},
|
||||
});
|
||||
} else if (args.method === "write") {
|
||||
assert.step("write");
|
||||
assert.deepEqual(args.args[1].p[0][2], {
|
||||
foo: "new record",
|
||||
int_field: 0,
|
||||
});
|
||||
} else if (args.method === "web_save") {
|
||||
assert.step("web_save");
|
||||
assert.deepEqual(args.kwargs.specification.p.fields, {
|
||||
trululu: { fields: { display_name: {} } },
|
||||
foo: {},
|
||||
int_field: {},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await addRow(target);
|
||||
await editInput(target, ".o_data_row [name='foo'] input", "new record");
|
||||
await clickSave(target);
|
||||
assert.verifySteps(["web_read", "web_save"]);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user