diff --git a/addons/web/static/src/js/core/py_utils.js b/addons/web/static/src/js/core/py_utils.js
index 590d5e62fc6..1cf1150ab76 100644
--- a/addons/web/static/src/js/core/py_utils.js
+++ b/addons/web/static/src/js/core/py_utils.js
@@ -254,14 +254,29 @@ function tz_offset() {
function pycontext() {
+ const d = new Date();
+ const today = `${
+ String(d.getFullYear()).padStart(4, "0")}-${
+ String(d.getMonth() + 1).padStart(2, "0")}-${
+ String(d.getDate()).padStart(2, "0")}`;
+ const now = `${
+ String(d.getUTCFullYear()).padStart(4, "0")}-${
+ String(d.getUTCMonth() + 1).padStart(2, "0")}-${
+ String(d.getUTCDate()).padStart(2, "0")} ${
+ String(d.getUTCHours()).padStart(2, "0")}:${
+ String(d.getUTCMinutes()).padStart(2, "0")}:${
+ String(d.getUTCSeconds()).padStart(2, "0")}`;
+
+ const { datetime, relativedelta, time } = py.extras;
return {
- datetime: py.extras.datetime,
- context_today: context_today,
- time: py.extras.time,
- relativedelta: py.extras.relativedelta,
- current_date: py.PY_call(
- py.extras.time.strftime, [py.str.fromJSON('%Y-%m-%d')]),
- tz_offset: tz_offset,
+ current_date: today,
+ datetime,
+ time,
+ now,
+ today,
+ relativedelta,
+ context_today,
+ tz_offset,
};
}
diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js
index adefa997e99..8878de55a3e 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -88,6 +88,7 @@ var concurrency = require('web.concurrency');
var Context = require('web.Context');
var core = require('web.core');
var Domain = require('web.Domain');
+const pyUtils = require('web.py_utils');
var session = require('web.session');
var utils = require('web.utils');
var viewUtils = require('web.viewUtils');
@@ -3696,19 +3697,27 @@ var BasicModel = AbstractModel.extend({
}
// Uses "current_company_id" because "company_id" would conflict with all the company_id fields
// in general, the actual "company_id" field of the form should be used for m2o domains, not this fallback
+ let current_company_id;
if (session.user_context.allowed_company_ids) {
- var current_company = session.user_context.allowed_company_ids[0];
+ current_company_id = session.user_context.allowed_company_ids[0];
} else {
- var current_company = session.user_companies ? session.user_companies.current_company[0] : false;
+ current_company_id = session.user_companies ?
+ session.user_companies.current_company[0] :
+ false;
}
- return _.extend({
- active_id: evalContext.id || false,
- active_ids: evalContext.id ? [evalContext.id] : [],
- active_model: element.model,
- current_date: moment().format('YYYY-MM-DD'),
- id: evalContext.id || false,
- current_company_id: current_company,
- }, session.user_context, element.context, evalContext);
+ return Object.assign(
+ {
+ active_id: evalContext.id || false,
+ active_ids: evalContext.id ? [evalContext.id] : [],
+ active_model: element.model,
+ current_company_id,
+ id: evalContext.id || false,
+ },
+ pyUtils.context(),
+ session.user_context,
+ element.context,
+ evalContext,
+ );
},
/**
* Returns the list of field names of the given element according to its
diff --git a/addons/web/static/tests/views/basic_model_tests.js b/addons/web/static/tests/views/basic_model_tests.js
index f57478e1252..b533c6587e9 100644
--- a/addons/web/static/tests/views/basic_model_tests.js
+++ b/addons/web/static/tests/views/basic_model_tests.js
@@ -2037,8 +2037,9 @@ odoo.define('web.basic_model_tests', function (require) {
});
QUnit.test('has a proper evaluation context', async function (assert) {
- assert.expect(1);
+ assert.expect(6);
+ const unpatchDate = testUtils.mock.patchDate(1997, 0, 9, 12, 0, 0);
this.params.fieldNames = Object.keys(this.data.partner.fields);
this.params.res_id = 1;
@@ -2048,8 +2049,24 @@ odoo.define('web.basic_model_tests', function (require) {
});
var resultID = await model.load(this.params);
- var record = model.get(resultID);
- assert.deepEqual(record.evalContext, {
+ const { evalContext } = model.get(resultID);
+ assert.strictEqual(typeof evalContext.datetime, "object");
+ assert.strictEqual(typeof evalContext.relativedelta, "object");
+ assert.strictEqual(typeof evalContext.time, "object");
+ assert.strictEqual(typeof evalContext.context_today, "function");
+ assert.strictEqual(typeof evalContext.tz_offset, "function");
+ const blackListedKeys = [
+ "time",
+ "datetime",
+ "relativedelta",
+ "context_today",
+ "tz_offset",
+ ];
+ // Remove uncomparable values from the evaluation context
+ for (const key of blackListedKeys) {
+ delete evalContext[key];
+ }
+ assert.deepEqual(evalContext, {
active: true,
active_id: 1,
active_ids: [1],
@@ -2058,6 +2075,8 @@ odoo.define('web.basic_model_tests', function (require) {
category: [12],
current_company_id: false,
current_date: moment().format('YYYY-MM-DD'),
+ today: moment().format('YYYY-MM-DD'),
+ now: moment().utc().format('YYYY-MM-DD HH:mm:ss'),
date: "2017-01-25",
display_name: "first partner",
foo: "blip",
@@ -2070,6 +2089,7 @@ odoo.define('web.basic_model_tests', function (require) {
x_active: true,
}, "should use the proper eval context");
model.destroy();
+ unpatchDate();
});
QUnit.test('x2manys in contexts and domains are correctly evaluated', async function (assert) {
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index e467d39d97b..7ecc9093b9a 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -10320,6 +10320,95 @@ QUnit.module('Views', {
list.destroy();
});
+
+ QUnit.test("Date in evaluation context works with date field", async function (assert) {
+ assert.expect(11);
+
+ const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
+ const unpatchDate = testUtils.mock.patchDate(1997, 0, 9, 12, 0, 0);
+ testUtils.mock.patch(BasicModel, {
+ _getEvalContext() {
+ const evalContext = this._super(...arguments);
+ assert.ok(dateRegex.test(evalContext.today));
+ assert.strictEqual(evalContext.current_date, evalContext.today);
+ return evalContext;
+ },
+ });
+
+ this.data.foo.fields.birthday = { string: "Birthday", type: 'date' };
+ this.data.foo.records[0].birthday = "1997-01-08";
+ this.data.foo.records[1].birthday = "1997-01-09";
+ this.data.foo.records[2].birthday = "1997-01-10";
+
+ const list = await createView({
+ arch: `
+
+
+ `,
+ data: this.data,
+ model: 'foo',
+ View: ListView,
+ });
+
+ assert.containsOnce(list, ".o_data_row .text-danger");
+
+ list.destroy();
+ unpatchDate();
+ testUtils.mock.unpatch(BasicModel);
+ });
+
+ QUnit.test("Datetime in evaluation context works with datetime field", async function (assert) {
+ assert.expect(6);
+
+ const datetimeRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
+ const unpatchDate = testUtils.mock.patchDate(1997, 0, 9, 12, 0, 0);
+ testUtils.mock.patch(BasicModel, {
+ _getEvalContext() {
+ const evalContext = this._super(...arguments);
+ assert.ok(datetimeRegex.test(evalContext.now));
+ return evalContext;
+ },
+ });
+
+ /**
+ * Returns "1997-01-DD HH:MM:00" with D, H and M holding current UTC values
+ * from patched date + (deltaMinutes) minutes.
+ * This is done to allow testing from any timezone since UTC values are
+ * calculated with the offset of the current browser.
+ */
+ function dateStringDelta(deltaMinutes) {
+ const d = new Date(Date.now() + 1000 * 60 * deltaMinutes);
+ return `1997-01-${
+ String(d.getUTCDate()).padStart(2, '0')
+ } ${
+ String(d.getUTCHours()).padStart(2, '0')
+ }:${
+ String(d.getUTCMinutes()).padStart(2, '0')
+ }:00`;
+ }
+
+ // "datetime" field may collide with "datetime" object in context
+ this.data.foo.fields.birthday = { string: "Birthday", type: 'datetime' };
+ this.data.foo.records[0].birthday = dateStringDelta(-30);
+ this.data.foo.records[1].birthday = dateStringDelta(0);
+ this.data.foo.records[2].birthday = dateStringDelta(+30);
+
+ const list = await createView({
+ arch: `
+
+
+ `,
+ data: this.data,
+ model: 'foo',
+ View: ListView,
+ });
+
+ assert.containsOnce(list, ".o_data_row .text-danger");
+
+ list.destroy();
+ unpatchDate();
+ testUtils.mock.unpatch(BasicModel);
+ });
});
});
diff --git a/odoo/tools/view_validation.py b/odoo/tools/view_validation.py
index 6a20044f939..61202fb6b61 100644
--- a/odoo/tools/view_validation.py
+++ b/odoo/tools/view_validation.py
@@ -36,6 +36,8 @@ def _get_attrs_symbols():
'datetime',
'relativedelta',
'current_date',
+ 'today',
+ 'now',
'abs',
'len',
'bool',