From 2063b26b0908c4dec490de357517e7c8e4b78e10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 5 Jul 2021 12:53:44 +0000 Subject: [PATCH] [FIX] web: add context in pyeval context When the owl refactoring landed, we rewrote the pyjs evaluation system. Then, we used the new system in the new code. In particular, the action service uses it to evaluate the action domain. But the previous code had a subtle behaviour in the special case of evaluating domain: it added the context as a pydict object in itself, so one could evaluate expression such as `context.get('a', 14)` to read the value of a key in the context with a fallback. With this commit, we uniformize this behaviour across all python expression: the `context` key is now reserved, and used to access anything in the current evaluation context. closes odoo/odoo#73227 Signed-off-by: Aaron Bohy (aab) --- addons/web/static/src/core/py_js/py.js | 4 ++-- .../static/src/core/py_js/py_interpreter.js | 20 ++++++++++++++----- .../tests/core/py_js/py_interpreter_tests.js | 13 ++++++------ 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/addons/web/static/src/core/py_js/py.js b/addons/web/static/src/core/py_js/py.js index 322e5287bfa..0c482d90068 100644 --- a/addons/web/static/src/core/py_js/py.js +++ b/addons/web/static/src/core/py_js/py.js @@ -29,10 +29,10 @@ export function parseExpr(expr) { * Evaluates a python expression * * @param {string} expr - * @param {Object} context + * @param {Object} [context] * @returns {any} */ -export function evaluateExpr(expr, context) { +export function evaluateExpr(expr, context = {}) { const ast = parseExpr(expr); return evaluate(ast, context); } diff --git a/addons/web/static/src/core/py_js/py_interpreter.js b/addons/web/static/src/core/py_js/py_interpreter.js index 6343310a34a..d2cac83b4f1 100644 --- a/addons/web/static/src/core/py_js/py_interpreter.js +++ b/addons/web/static/src/core/py_js/py_interpreter.js @@ -2,7 +2,7 @@ import { BUILTINS } from "./py_builtin"; import { PyDate, PyDateTime, PyRelativeDelta, PyTime } from "./py_date"; -import { parseArgs, PY_DICT } from "./py_utils"; +import { parseArgs, PY_DICT, toPyDict } from "./py_utils"; // ----------------------------------------------------------------------------- // Types @@ -199,6 +199,16 @@ const DICT = { */ export function evaluate(ast, context = {}) { const dicts = new Set(); + let pyContext; + const evalContext = Object.create(context); + Object.defineProperty(evalContext, "context", { + get() { + if (!pyContext) { + pyContext = toPyDict(context); + } + return pyContext; + }, + }); /** * @param {AST} ast @@ -209,8 +219,8 @@ export function evaluate(ast, context = {}) { case 1 /* String */: return ast.value; case 5 /* Name */: - if (ast.value in context) { - return context[ast.value]; + if (ast.value in evalContext) { + return evalContext[ast.value]; } else if (ast.value in BUILTINS) { return BUILTINS[ast.value]; } else { @@ -221,9 +231,9 @@ export function evaluate(ast, context = {}) { case 2 /* Boolean */: return ast.value; case 6 /* UnaryOperator */: - return applyUnaryOp(ast, context); + return applyUnaryOp(ast, evalContext); case 7 /* BinaryOperator */: - return applyBinaryOp(ast, context); + return applyBinaryOp(ast, evalContext); case 14 /* BooleanOperator */: const left = _evaluate(ast.left); if (ast.op === "and") { diff --git a/addons/web/static/tests/core/py_js/py_interpreter_tests.js b/addons/web/static/tests/core/py_js/py_interpreter_tests.js index 5d969a7d9e8..d9d3b9c115a 100644 --- a/addons/web/static/tests/core/py_js/py_interpreter_tests.js +++ b/addons/web/static/tests/core/py_js/py_interpreter_tests.js @@ -1,7 +1,6 @@ /** @odoo-module **/ import { evaluateExpr } from "@web/core/py_js/py"; -import { toPyDict } from "@web/core/py_js/py_utils"; QUnit.module("py", {}, () => { QUnit.module("interpreter", () => { @@ -138,11 +137,13 @@ QUnit.module("py", {}, () => { assert.deepEqual(evaluateExpr("foo", { foo: [1, 2, 3] }), [1, 2, 3]); }); - QUnit.test("python values in context", (assert) => { - const context = toPyDict({ b: 3 }); - assert.strictEqual(evaluateExpr("context.get('b', 54)", { context }), 3); - assert.strictEqual(evaluateExpr("context.get('c', 54)", { context }), 54); - }); + QUnit.test( + "special case for context: the eval context can be accessed as 'context'", + (assert) => { + assert.strictEqual(evaluateExpr("context.get('b', 54)", { b: 3 }), 3); + assert.strictEqual(evaluateExpr("context.get('c', 54)", { b: 3 }), 54); + } + ); QUnit.test("throw error if name is not defined", (assert) => { assert.throws(() => evaluateExpr("a"));