diff --git a/addons/web/static/src/views/list/list_arch_parser.js b/addons/web/static/src/views/list/list_arch_parser.js
index d697c0649fb..fe415739c4e 100644
--- a/addons/web/static/src/views/list/list_arch_parser.js
+++ b/addons/web/static/src/views/list/list_arch_parser.js
@@ -163,6 +163,11 @@ export class ListArchParser extends XMLParser {
treeAttr.noOpen = archParseBoolean(node.getAttribute("no_open") || "");
treeAttr.expand = archParseBoolean(xmlDoc.getAttribute("expand") || "");
treeAttr.decorations = getDecoration(xmlDoc);
+
+ // custom open action when clicking on record row
+ const action = xmlDoc.getAttribute("action");
+ const type = xmlDoc.getAttribute("type");
+ treeAttr.openAction = action && type ? { action, type } : null;
}
});
diff --git a/addons/web/static/src/views/list/list_controller.js b/addons/web/static/src/views/list/list_controller.js
index 9af25c26907..fb985f67b13 100644
--- a/addons/web/static/src/views/list/list_controller.js
+++ b/addons/web/static/src/views/list/list_controller.js
@@ -167,8 +167,23 @@ export class ListController extends Component {
}
async openRecord(record) {
- const activeIds = this.model.root.records.map((datapoint) => datapoint.resId);
- this.props.selectRecord(record.resId, { activeIds });
+ if (this.archInfo.openAction) {
+ this.actionService.doActionButton({
+ name: this.archInfo.openAction.action,
+ type: this.archInfo.openAction.type,
+ resModel: record.resModel,
+ resId: record.resId,
+ resIds: record.resIds,
+ context: record.context,
+ onClose: async () => {
+ await record.model.root.load();
+ record.model.notify();
+ },
+ });
+ } else {
+ const activeIds = this.model.root.records.map((datapoint) => datapoint.resId);
+ this.props.selectRecord(record.resId, { activeIds });
+ }
}
onClickCreate() {
diff --git a/addons/web/static/tests/views/list_view_tests.js b/addons/web/static/tests/views/list_view_tests.js
index c99a27a3f06..6c70921fb00 100644
--- a/addons/web/static/tests/views/list_view_tests.js
+++ b/addons/web/static/tests/views/list_view_tests.js
@@ -1799,6 +1799,52 @@ QUnit.module("Views", (hooks) => {
assert.verifySteps(["openRecord", "openRecord"]);
});
+ QUnit.test("action/type attributes on tree arch, type='object'", async (assert) => {
+ const list = await makeView({
+ type: "list",
+ resModel: "foo",
+ serverData,
+ arch: '',
+ mockRPC(route, args) {
+ assert.step(args.method);
+ },
+ });
+
+ patchWithCleanup(list.env.services.action, {
+ doActionButton(params) {
+ assert.step(`doActionButton type ${params.type} name ${params.name}`);
+ params.onClose();
+ },
+ });
+
+ assert.verifySteps(["get_views", "web_search_read"]);
+ await click(target.querySelector(".o_data_cell"));
+ assert.verifySteps(["doActionButton type object name a1", "web_search_read"]);
+ });
+
+ QUnit.test("action/type attributes on tree arch, type='action'", async (assert) => {
+ const list = await makeView({
+ type: "list",
+ resModel: "foo",
+ serverData,
+ arch: '',
+ mockRPC(route, args) {
+ assert.step(args.method);
+ },
+ });
+
+ patchWithCleanup(list.env.services.action, {
+ doActionButton(params) {
+ assert.step(`doActionButton type ${params.type} name ${params.name}`);
+ params.onClose();
+ },
+ });
+
+ assert.verifySteps(["get_views", "web_search_read"]);
+ await click(target.querySelector(".o_data_cell"));
+ assert.verifySteps(["doActionButton type action name a1", "web_search_read"]);
+ });
+
QUnit.test("editable list view: readonly fields cannot be edited", async function (assert) {
serverData.models.foo.fields.foo.readonly = true;
diff --git a/odoo/addons/base/rng/tree_view.rng b/odoo/addons/base/rng/tree_view.rng
index 8985ea827ea..6caca915e2e 100644
--- a/odoo/addons/base/rng/tree_view.rng
+++ b/odoo/addons/base/rng/tree_view.rng
@@ -46,6 +46,8 @@
+
+