[IMP] spreadsheet_dashboard_edition: load dashboard with user locale
Currently, spreadsheet dashboards always display numbers with the en_US format, no matter the user's lang. That's because the locale is hardcoded in the spreadsheet source file, in the source code (or it falls back to the default en_US locale if it's missing). With this commit, we dynamically change the spreadsheet locale with the user's locale when he loads a dashboard. We can change the locale with every user because the dashboard is readonly. It cannot create a giant mess with dates in various locales. Limitation ---------- Since hardcoded date formats are not changed when the locale changes, dates will keep the en_US format. However, most (if not all) dates in dashboards are coming from ODOO.PIVOT and ODOO.LIST functions, which compute the formats dynamically based on the current locale. closes odoo/odoo#135119 Task: 3484002 Related: odoo/enterprise#47284 Signed-off-by: Rémi Rahir (rar) <rar@odoo.com>
This commit is contained in:
@@ -68,7 +68,8 @@ class SpreadsheetMixin(models.AbstractModel):
|
||||
],
|
||||
"settings": {
|
||||
"locale": locale,
|
||||
}
|
||||
},
|
||||
"revisionId": "START_REVISION",
|
||||
}
|
||||
|
||||
def _zip_xslx_files(self, files):
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import json
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class SpreadsheetDashboard(models.Model):
|
||||
_name = 'spreadsheet.dashboard'
|
||||
_description = 'Spreadsheet Dashboard'
|
||||
@@ -10,3 +13,14 @@ class SpreadsheetDashboard(models.Model):
|
||||
dashboard_group_id = fields.Many2one('spreadsheet.dashboard.group', required=True)
|
||||
sequence = fields.Integer()
|
||||
group_ids = fields.Many2many('res.groups', default=lambda self: self.env.ref('base.group_user'))
|
||||
|
||||
|
||||
def get_readonly_dashboard(self):
|
||||
self.ensure_one()
|
||||
snapshot = json.loads(self.spreadsheet_data)
|
||||
user_locale = self.env['res.lang']._get_user_spreadsheet_locale()
|
||||
snapshot.setdefault('settings', {})['locale'] = user_locale
|
||||
return {
|
||||
'snapshot': snapshot,
|
||||
'revisions': [],
|
||||
}
|
||||
|
||||
+1
-17
@@ -25,9 +25,7 @@ export class SpreadsheetDashboardAction extends Component {
|
||||
// with the breadcrumb
|
||||
// TODO write a test
|
||||
/** @type {DashboardLoader}*/
|
||||
this.loader = useState(
|
||||
new DashboardLoader(this.env, this.env.services.orm, this._fetchDashboardData)
|
||||
);
|
||||
this.loader = useState(new DashboardLoader(this.env, this.env.services.orm));
|
||||
onWillStart(async () => {
|
||||
if (this.props.state && this.props.state.dashboardLoader) {
|
||||
const { groups, dashboards } = this.props.state.dashboardLoader;
|
||||
@@ -117,20 +115,6 @@ export class SpreadsheetDashboardAction extends Component {
|
||||
this.state.activeDashboard = this.loader.getDashboard(dashboardId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {number} dashboardId
|
||||
* @returns {Promise<{ data: string, revisions: object[] }>}
|
||||
*/
|
||||
async _fetchDashboardData(dashboardId) {
|
||||
const [record] = await this.orm.read(
|
||||
"spreadsheet.dashboard",
|
||||
[dashboardId],
|
||||
["spreadsheet_data"]
|
||||
);
|
||||
return { data: JSON.parse(record.spreadsheet_data), revisions: [] };
|
||||
}
|
||||
|
||||
async shareSpreadsheet(data, excelExport) {
|
||||
const url = await this.orm.call("spreadsheet.dashboard.share", "action_get_share_url", [
|
||||
{
|
||||
|
||||
+10
-11
@@ -37,8 +37,6 @@ export const Status = {
|
||||
* @property {string} name
|
||||
* @property {Array<Dashboard>} dashboards
|
||||
*
|
||||
* @typedef {(dashboardId: number) => Promise<{ data: string, revisions: object[] }>} FetchDashboardData
|
||||
*
|
||||
* @typedef {import("@web/env").OdooEnv} OdooEnv
|
||||
*
|
||||
* @typedef {import("@web/core/orm_service").ORM} ORM
|
||||
@@ -48,9 +46,8 @@ export class DashboardLoader {
|
||||
/**
|
||||
* @param {OdooEnv} env
|
||||
* @param {ORM} orm
|
||||
* @param {FetchDashboardData} fetchDashboardData
|
||||
*/
|
||||
constructor(env, orm, fetchDashboardData) {
|
||||
constructor(env, orm) {
|
||||
/** @private */
|
||||
this.env = env;
|
||||
/** @private */
|
||||
@@ -59,8 +56,6 @@ export class DashboardLoader {
|
||||
this.groups = [];
|
||||
/** @private @type {Object<number, Dashboard>} */
|
||||
this.dashboards = {};
|
||||
/** @private */
|
||||
this.fetchDashboardData = fetchDashboardData;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,8 +161,12 @@ export class DashboardLoader {
|
||||
const dashboard = this._getDashboard(dashboardId);
|
||||
dashboard.status = Status.Loading;
|
||||
try {
|
||||
const { data, revisions } = await this.fetchDashboardData(dashboardId);
|
||||
dashboard.model = this._createSpreadsheetModel(data, revisions);
|
||||
const { snapshot, revisions } = await this.orm.call(
|
||||
"spreadsheet.dashboard",
|
||||
"get_readonly_dashboard",
|
||||
[dashboardId]
|
||||
);
|
||||
dashboard.model = this._createSpreadsheetModel(snapshot, revisions);
|
||||
dashboard.status = Status.Loaded;
|
||||
} catch (error) {
|
||||
dashboard.error = error;
|
||||
@@ -193,14 +192,14 @@ export class DashboardLoader {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {string} data
|
||||
* @param {object} snapshot
|
||||
* @param {object[]} revisions
|
||||
* @returns {Model}
|
||||
*/
|
||||
_createSpreadsheetModel(data, revisions = []) {
|
||||
_createSpreadsheetModel(snapshot, revisions = []) {
|
||||
const dataSources = new DataSources(this.env);
|
||||
const model = new Model(
|
||||
migrate(data),
|
||||
migrate(snapshot),
|
||||
{
|
||||
custom: { env: this.env, orm: this.orm, dataSources },
|
||||
mode: "dashboard",
|
||||
|
||||
@@ -102,12 +102,8 @@ QUnit.test("display error message", async (assert) => {
|
||||
mockRPC: function (route, args) {
|
||||
if (
|
||||
args.model === "spreadsheet.dashboard" &&
|
||||
((args.method === "read" &&
|
||||
args.args[0][0] === 2 &&
|
||||
args.args[1][0] === "spreadsheet_data") ||
|
||||
// this is not correct from a module dependency POV but it's required for the test
|
||||
// to pass when `spreadsheet_dashboard_edition` module is installed
|
||||
(args.method === "join_spreadsheet_session" && args.args[0] === 2))
|
||||
args.method === "get_readonly_dashboard" &&
|
||||
args.args[0] === 2
|
||||
) {
|
||||
throw new Error("Bip");
|
||||
}
|
||||
|
||||
@@ -98,7 +98,14 @@ QUnit.test("load spreadsheet data", async (assert) => {
|
||||
QUnit.test("load spreadsheet data only once", async (assert) => {
|
||||
const loader = await createDashboardLoader({
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === "read" && args.model === "spreadsheet.dashboard") {
|
||||
if (args.model === "spreadsheet.dashboard" && args.method === "read") {
|
||||
// read names
|
||||
assert.step(`spreadsheet ${args.args[0]} loaded`);
|
||||
}
|
||||
if (
|
||||
args.model === "spreadsheet.dashboard" &&
|
||||
args.method === "get_readonly_dashboard"
|
||||
) {
|
||||
assert.step(`spreadsheet ${args.args[0]} loaded`);
|
||||
}
|
||||
},
|
||||
@@ -159,6 +166,13 @@ QUnit.test("load multiple spreadsheets", async (assert) => {
|
||||
assert.step("load groups");
|
||||
}
|
||||
if (args.method === "read" && args.model === "spreadsheet.dashboard") {
|
||||
// read names
|
||||
assert.step(`spreadsheet ${args.args[0]} loaded`);
|
||||
}
|
||||
if (
|
||||
args.model === "spreadsheet.dashboard" &&
|
||||
args.method === "get_readonly_dashboard"
|
||||
) {
|
||||
assert.step(`spreadsheet ${args.args[0]} loaded`);
|
||||
}
|
||||
},
|
||||
@@ -180,9 +194,8 @@ QUnit.test("load spreadsheet data with error", async (assert) => {
|
||||
const loader = await createDashboardLoader({
|
||||
mockRPC: function (route, args) {
|
||||
if (
|
||||
args.method === "read" &&
|
||||
args.model === "spreadsheet.dashboard" &&
|
||||
args.args[1][0] === "spreadsheet_data"
|
||||
args.method === "get_readonly_dashboard" &&
|
||||
args.model === "spreadsheet.dashboard"
|
||||
) {
|
||||
throw new Error("Bip");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry
|
||||
.category("mock_server")
|
||||
.add("spreadsheet.dashboard/get_readonly_dashboard", function (route, args) {
|
||||
const [id] = args.args;
|
||||
const dashboard = this.models["spreadsheet.dashboard"].records.find(
|
||||
(record) => record.id === id
|
||||
);
|
||||
return {
|
||||
snapshot: JSON.parse(dashboard.spreadsheet_data),
|
||||
revisions: [],
|
||||
};
|
||||
});
|
||||
@@ -1,9 +1,11 @@
|
||||
import json
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
class TestSpreadsheetDashboard(TransactionCase):
|
||||
from .common import DashboardTestCommon
|
||||
|
||||
|
||||
class TestSpreadsheetDashboard(DashboardTestCommon):
|
||||
def test_create_with_default_values(self):
|
||||
group = self.env["spreadsheet.dashboard.group"].create(
|
||||
{"name": "a group"}
|
||||
@@ -32,3 +34,18 @@ class TestSpreadsheetDashboard(TransactionCase):
|
||||
})
|
||||
with self.assertRaises(UserError, msg="You cannot delete a_group as it is used in another module"):
|
||||
group.unlink()
|
||||
|
||||
def test_load_with_user_locale(self):
|
||||
dashboard = self.create_dashboard().with_user(self.user)
|
||||
self.user.lang = "en_US"
|
||||
data = dashboard.get_readonly_dashboard()
|
||||
locale = data["snapshot"]["settings"]["locale"]
|
||||
self.assertEqual(locale["code"], "en_US")
|
||||
self.assertEqual(len(data["revisions"]), 0)
|
||||
|
||||
self.env.ref("base.lang_fr").active = True
|
||||
self.user.lang = "fr_FR"
|
||||
data = dashboard.get_readonly_dashboard()
|
||||
locale = data["snapshot"]["settings"]["locale"]
|
||||
self.assertEqual(locale["code"], "fr_FR")
|
||||
self.assertEqual(len(data["revisions"]), 0)
|
||||
|
||||
Reference in New Issue
Block a user