From 45780793dd5e043b1cc3ec20a3d45cfee0eeae63 Mon Sep 17 00:00:00 2001 From: "Xavier BOL (xbo)" Date: Thu, 16 Jun 2022 12:10:29 +0000 Subject: [PATCH] [FIX] web: create string ast for PyDate(time) object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this commit, when a new domain is created with a list of rules and in one of those rules contains a PyDate/PyDatetime object in a right leaf, this leaf will be converted into a plain object instead of keeping the initial object. For instance: ```js a = new Domain([['date', '=', PyDate.create({day: 1, month: 1, year: 1970})]]) a.toList() // result: `[['date', '=', {day: 1, month: 1, year: 1970}]]` ``` Since it is a plain object, when the result will be passed to the server, the final result will be an object in a string instead of giving the utc date formatted. This commit fixes this issue by checking if the object instances of the `PyDate` or `PyDatetime`. If it is the case, then the ast type will be a string and the value will stay the same instead of creating a plain object and the ast type which determines a JSON. closes odoo/odoo#95570 X-original-commit: be5c8d0fc340b3e2ad9721d21ef1133870d8a774 Signed-off-by: Géry Debongnie Signed-off-by: Xavier --- addons/web/static/src/core/py_js/py_date.js | 40 +++++++--- .../static/src/core/py_js/py_interpreter.js | 3 +- addons/web/static/src/core/py_js/py_parser.js | 15 ++++ addons/web/static/src/core/py_js/py_utils.js | 20 ++--- addons/web/static/tests/core/domain_tests.js | 9 ++- .../static/tests/core/py_js/py_utils_tests.js | 73 +++++++++++++++++++ 6 files changed, 131 insertions(+), 29 deletions(-) diff --git a/addons/web/static/src/core/py_js/py_date.js b/addons/web/static/src/core/py_js/py_date.js index 4bce732be12..ade9b37380f 100644 --- a/addons/web/static/src/core/py_js/py_date.js +++ b/addons/web/static/src/core/py_js/py_date.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import { parseArgs } from "./py_utils"; +import { parseArgs } from "./py_parser"; // ----------------------------------------------------------------------------- // Errors @@ -224,10 +224,18 @@ export class PyDate { * @returns {PyDate} */ static today() { - const now = new Date(); - const year = now.getUTCFullYear(); - const month = now.getUTCMonth() + 1; - const day = now.getUTCDate(); + return this.convertDate(new Date()); + } + + /** + * Convert a date object into PyDate + * @param {Date} date + * @returns {PyDate} + */ + static convertDate(date) { + const year = date.getUTCFullYear(); + const month = date.getUTCMonth() + 1; + const day = date.getUTCDate(); return new PyDate(year, month, day); } @@ -323,13 +331,21 @@ export class PyDateTime { * @returns {PyDateTime} */ static now() { - const now = new Date(); - const year = now.getUTCFullYear(); - const month = now.getUTCMonth() + 1; - const day = now.getUTCDate(); - const hour = now.getUTCHours(); - const minute = now.getUTCMinutes(); - const second = now.getUTCSeconds(); + return this.convertDate(new Date()); + } + + /** + * Convert a date object into PyDateTime + * @param {Date} date + * @returns {PyDateTime} + */ + static convertDate(date) { + const year = date.getUTCFullYear(); + const month = date.getUTCMonth() + 1; + const day = date.getUTCDate(); + const hour = date.getUTCHours(); + const minute = date.getUTCMinutes(); + const second = date.getUTCSeconds(); return new PyDateTime(year, month, day, hour, minute, second, 0); } 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 7be649c56fa..3eb480c6a88 100644 --- a/addons/web/static/src/core/py_js/py_interpreter.js +++ b/addons/web/static/src/core/py_js/py_interpreter.js @@ -9,7 +9,8 @@ import { PyTime, PyTimeDelta, } from "./py_date"; -import { parseArgs, PY_DICT, toPyDict } from "./py_utils"; +import { PY_DICT, toPyDict } from "./py_utils"; +import { parseArgs } from './py_parser'; // ----------------------------------------------------------------------------- // Types diff --git a/addons/web/static/src/core/py_js/py_parser.js b/addons/web/static/src/core/py_js/py_parser.js index 52b4d776deb..259a3bb0682 100644 --- a/addons/web/static/src/core/py_js/py_parser.js +++ b/addons/web/static/src/core/py_js/py_parser.js @@ -369,3 +369,18 @@ export function parse(tokens) { } throw new ParserError("Missing token"); } + +/** + * @param {any[]} args + * @param {string[]} spec + * @returns {{[name: string]: any}} + */ +export function parseArgs(args, spec) { + const last = args[args.length - 1]; + const unnamedArgs = typeof last === "object" ? args.slice(0, -1) : args; + const kwargs = typeof last === "object" ? last : {}; + for (let [index, val] of unnamedArgs.entries()) { + kwargs[spec[index]] = val; + } + return kwargs; +} diff --git a/addons/web/static/src/core/py_js/py_utils.js b/addons/web/static/src/core/py_js/py_utils.js index cbd0ec2d1a0..9f8919ec9f4 100644 --- a/addons/web/static/src/core/py_js/py_utils.js +++ b/addons/web/static/src/core/py_js/py_utils.js @@ -1,6 +1,7 @@ /** @odoo-module **/ import { bp } from "./py_parser"; +import { PyDate, PyDateTime } from './py_date'; // ----------------------------------------------------------------------------- // Types @@ -14,21 +15,6 @@ import { bp } from "./py_parser"; // Utils // ----------------------------------------------------------------------------- -/** - * @param {any[]} args - * @param {string[]} spec - * @returns {{[name: string]: any}} - */ -export function parseArgs(args, spec) { - const last = args[args.length - 1]; - const unnamedArgs = typeof last === "object" ? args.slice(0, -1) : args; - const kwargs = typeof last === "object" ? last : {}; - for (let [index, val] of unnamedArgs.entries()) { - kwargs[spec[index]] = val; - } - return kwargs; -} - /** * Represent any value as a primitive AST * @@ -48,6 +34,10 @@ export function toPyValue(value) { return { type: 4 /* List */, value: value.map(toPyValue) }; } else if (value === null) { return { type: 3 /* None */ }; + } else if (value instanceof Date) { + return { type: 1, value: PyDateTime.convertDate(value) }; + } else if (value instanceof PyDate || value instanceof PyDateTime) { + return { type: 1, value }; } else { const content = {}; for (let key in value) { diff --git a/addons/web/static/tests/core/domain_tests.js b/addons/web/static/tests/core/domain_tests.js index d70f0d435b9..b8dc91de9af 100644 --- a/addons/web/static/tests/core/domain_tests.js +++ b/addons/web/static/tests/core/domain_tests.js @@ -281,9 +281,16 @@ QUnit.module("domain", {}, () => { return new PyDate(2013, 4, 24); }, }); - const domainStr = + let domainStr = "[('date','>=', (context_today() - datetime.timedelta(days=30)).strftime('%Y-%m-%d'))]"; assert.deepEqual(new Domain(domainStr).toList(), [["date", ">=", "2013-03-25"]]); + domainStr = "[('date', '>=', context_today() - relativedelta(days=30))]"; + const domainList = new Domain(domainStr).toList(); // domain creation using `parseExpr` function since the parameter is a string. + assert.deepEqual(domainList[0][2], PyDate.create({ day: 25, month: 3, year: 2013 }), 'The right item in the rule in the domain should be a PyDate object'); + assert.deepEqual(JSON.stringify(domainList), '[["date",">=","2013-03-25"]]'); + const domainList2 = new Domain(domainList).toList(); // domain creation using `toAST` function since the parameter is a list. + assert.deepEqual(domainList2[0][2], PyDate.create({ day: 25, month: 3, year: 2013 }), 'The right item in the rule in the domain should be a PyDate object'); + assert.deepEqual(JSON.stringify(domainList2), '[["date",">=","2013-03-25"]]'); }); QUnit.test("Check that there is no dependency between two domains", function (assert) { diff --git a/addons/web/static/tests/core/py_js/py_utils_tests.js b/addons/web/static/tests/core/py_js/py_utils_tests.js index 0cecedd0d2c..95687325f05 100644 --- a/addons/web/static/tests/core/py_js/py_utils_tests.js +++ b/addons/web/static/tests/core/py_js/py_utils_tests.js @@ -2,6 +2,7 @@ import { evaluateExpr, formatAST, parseExpr } from "@web/core/py_js/py"; import { toPyValue } from "@web/core/py_js/py_utils"; +import { PyDate, PyDateTime } from '@web/core/py_js/py_date'; QUnit.module("py", {}, () => { QUnit.module("formatAST"); @@ -131,4 +132,76 @@ QUnit.module("py", {}, () => { QUnit.test("null value", function (assert) { assert.strictEqual(formatAST(toPyValue(null)), "None"); }); + + QUnit.module('toPyValue'); + + QUnit.test('toPyValue a string', function (assert) { + const ast = toPyValue('test'); + assert.strictEqual(ast.type, 1); + assert.strictEqual(ast.value, 'test'); + assert.strictEqual(formatAST(ast), '"test"'); + }); + + QUnit.test('toPyValue a number', function (assert) { + const ast = toPyValue(1); + assert.strictEqual(ast.type, 0); + assert.strictEqual(ast.value, 1); + assert.strictEqual(formatAST(ast), "1"); + }); + + QUnit.test('toPyValue a boolean', function (assert) { + let ast = toPyValue(true); + assert.strictEqual(ast.type, 2); + assert.strictEqual(ast.value, true); + assert.strictEqual(formatAST(ast), "True"); + + ast = toPyValue(false); + assert.strictEqual(ast.type, 2); + assert.strictEqual(ast.value, false); + assert.strictEqual(formatAST(ast), "False"); + }); + + QUnit.test('toPyValue a object', function (assert) { + const ast = toPyValue({a: 1}); + assert.strictEqual(ast.type, 11); + assert.ok('a' in ast.value); + assert.ok(['type', 'value'].every(prop => prop in ast.value.a)); + assert.strictEqual(ast.value.a.type, 0); + assert.strictEqual(ast.value.a.value, 1); + assert.strictEqual(formatAST(ast), '{"a": 1}'); + }); + + QUnit.test('toPyValue a date', function (assert) { + const date = new Date(Date.UTC(2000, 0, 1)); + const ast = toPyValue(date); + assert.strictEqual(ast.type, 1); + const expectedValue = PyDateTime.convertDate(date); + assert.ok(ast.value.isEqual(expectedValue)); + assert.strictEqual(formatAST(ast), JSON.stringify(expectedValue)); + }); + + QUnit.test('toPyValue a dateime', function (assert) { + const datetime = new Date(Date.UTC(2000, 0, 1, 1, 0, 0, 0)); + const ast = toPyValue(datetime); + assert.strictEqual(ast.type, 1); + const expectedValue = PyDateTime.convertDate(datetime); + assert.ok(ast.value.isEqual(expectedValue)); + assert.strictEqual(formatAST(ast), JSON.stringify(expectedValue)); + }); + + QUnit.test('toPyValue a PyDate', function (assert) { + const value = new PyDate(2000, 1, 1); + const ast = toPyValue(value); + assert.strictEqual(ast.type, 1); + assert.strictEqual(ast.value, value); + assert.strictEqual(formatAST(ast), JSON.stringify(value)); + }); + + QUnit.test('toPyValue a PyDateTime', function (assert) { + const value = new PyDateTime(2000, 1, 1, 1, 0, 0, 0); + const ast = toPyValue(value); + assert.strictEqual(ast.type, 1); + assert.strictEqual(ast.value, value); + assert.strictEqual(formatAST(ast), JSON.stringify(value)); + }); });