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 192532bf165..e162b92f760 100644
--- a/addons/web/static/src/views/list/list_arch_parser.js
+++ b/addons/web/static/src/views/list/list_arch_parser.js
@@ -203,7 +203,7 @@ export class ListArchParser extends XMLParser {
treeAttr.groupsLimit = groupsLimitAttr && parseInt(groupsLimitAttr, 10);
treeAttr.noOpen = archParseBoolean(node.getAttribute("no_open") || "");
- treeAttr.expand = archParseBoolean(xmlDoc.getAttribute("expand") || "");
+ treeAttr.rawExpand = xmlDoc.getAttribute("expand");
treeAttr.decorations = getDecoration(xmlDoc);
treeAttr.defaultGroupBy = xmlDoc.getAttribute("default_group_by");
diff --git a/addons/web/static/src/views/list/list_controller.js b/addons/web/static/src/views/list/list_controller.js
index 4f81794fcb9..2dd180ad127 100644
--- a/addons/web/static/src/views/list/list_controller.js
+++ b/addons/web/static/src/views/list/list_controller.js
@@ -2,6 +2,7 @@
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { download } from "@web/core/network/download";
+import { evaluateExpr } from "@web/core/py_js/py";
import { useService } from "@web/core/utils/hooks";
import { omit } from "@web/core/utils/objects";
import { sprintf } from "@web/core/utils/strings";
@@ -37,6 +38,7 @@ export class ListController extends Component {
this.activeActions = this.archInfo.activeActions;
const fields = this.props.fields;
const { rootState } = this.props.state || {};
+ const { defaultGroupBy, rawExpand } = this.archInfo;
this.model = useModel(this.props.Model, {
resModel: this.props.resModel,
fields,
@@ -47,8 +49,8 @@ export class ListController extends Component {
groupByInfo: this.archInfo.groupBy.fields,
limit: this.archInfo.limit || this.props.limit,
defaultOrder: this.archInfo.defaultOrder,
- defaultGroupBy: this.props.searchMenuTypes.includes("groupBy") && this.props.archInfo.defaultGroupBy,
- expand: this.archInfo.expand,
+ defaultGroupBy: this.props.searchMenuTypes.includes("groupBy") ? defaultGroupBy : false,
+ expand: rawExpand ? evaluateExpr(rawExpand, this.props.context) : false,
groupsLimit: this.archInfo.groupsLimit,
multiEdit: this.multiEdit,
rootState,
diff --git a/addons/web/static/tests/views/list_view_tests.js b/addons/web/static/tests/views/list_view_tests.js
index 8a9c52ae1bc..79cb61e63b1 100644
--- a/addons/web/static/tests/views/list_view_tests.js
+++ b/addons/web/static/tests/views/list_view_tests.js
@@ -12347,6 +12347,38 @@ QUnit.module("Views", (hooks) => {
]);
});
+ QUnit.test("grouped list with dynamic expand attribute (eval true)", async function (assert) {
+ await makeView({
+ type: "list",
+ resModel: "foo",
+ serverData,
+ arch: ``,
+ context: {
+ expand: true,
+ },
+ groupBy: ["bar"],
+ });
+
+ assert.containsN(target, ".o_group_header", 2);
+ assert.containsN(target, ".o_data_row", 4);
+ });
+
+ QUnit.test("grouped list with dynamic expand attribute (eval false)", async function (assert) {
+ await makeView({
+ type: "list",
+ resModel: "foo",
+ serverData,
+ arch: ``,
+ context: {
+ expand: false,
+ },
+ groupBy: ["bar"],
+ });
+
+ assert.containsN(target, ".o_group_header", 2);
+ assert.containsNone(target, ".o_data_row");
+ });
+
QUnit.test("grouped list (two levels) with expand attribute", async function (assert) {
// the expand attribute only opens the first level groups
await makeView({