diff --git a/addons/web/static/src/search/search_model.js b/addons/web/static/src/search/search_model.js index 69f59c20230..0a41a9052da 100644 --- a/addons/web/static/src/search/search_model.js +++ b/addons/web/static/src/search/search_model.js @@ -427,7 +427,7 @@ export class SearchModel extends EventBus { if (!this._context) { this._context = this._getContext(); } - return this._context; + return deepCopy(this._context); } /** @@ -437,7 +437,7 @@ export class SearchModel extends EventBus { if (!this._domain) { this._domain = this._getDomain(); } - return this._domain; + return deepCopy(this._domain); } /** @@ -476,7 +476,7 @@ export class SearchModel extends EventBus { } } } - return this._comparison; + return deepCopy(this._comparison); } get facets() { @@ -506,7 +506,7 @@ export class SearchModel extends EventBus { if (!this._groupBy) { this._groupBy = this._getGroupBy(); } - return this._groupBy; + return deepCopy(this._groupBy); } /** @@ -516,7 +516,7 @@ export class SearchModel extends EventBus { if (!this._orderBy) { this._orderBy = this._getOrderBy(); } - return this._orderBy; + return deepCopy(this._orderBy); } //-------------------------------------------------------------------------- @@ -1851,9 +1851,7 @@ export class SearchModel extends EventBus { case "favorite": case "filter": { //Return a deep copy of the filter/favorite to avoid the view to modify the context - return makeContext( - searchItem.context && JSON.parse(JSON.stringify(searchItem.context)) - ); + return makeContext(searchItem.context && deepCopy(searchItem.context)); } default: { return null; diff --git a/addons/web/static/src/views/graph/graph_model.js b/addons/web/static/src/views/graph/graph_model.js index 6e75041e5c9..909fce47610 100644 --- a/addons/web/static/src/views/graph/graph_model.js +++ b/addons/web/static/src/views/graph/graph_model.js @@ -6,7 +6,6 @@ import { rankInterval } from "@web/search/utils/dates"; import { getGroupBy } from "@web/search/utils/group_by"; import { GROUPABLE_TYPES } from "@web/search/utils/misc"; import { Model } from "@web/views/helpers/model"; -import { buildSampleORM } from "@web/views/helpers/sample_server"; import { computeReportMeasures, processMeasure } from "@web/views/helpers/utils"; export const SEP = " / "; @@ -85,13 +84,8 @@ export class GraphModel extends Model { /** * @override */ - setup(params, services) { - const { orm, user } = services; - this.orm = orm; - this.user = user; - + setup(params) { this.keepLast = new KeepLast(); - this._fakeORM = null; this.initialGroupBy = null; @@ -137,6 +131,13 @@ export class GraphModel extends Model { this._prepareData(); } + /** + * @override + */ + hasData() { + return this.dataPoints.length > 0; + } + /** * Only supposed to be called to change one or several parameters among * "measure", "mode", "order", and "stacked". @@ -162,22 +163,14 @@ export class GraphModel extends Model { //-------------------------------------------------------------------------- /** - * Fetch the data points (possibly sample ones) determined by the metaData. - * This function has several side effects. It can alter metaData - * (e.g. change metaData.useSampleModel value) and set this.metaData and - * this.dataPoints (sometimes this._fakeORM). + * Fetch the data points determined by the metaData. This function has + * several side effects. It can alter this.metaData and set this.dataPoints. * @protected * @param {Object} metaData */ async _fetchDataPoints(metaData) { - let dataPoints = await this.keepLast.add(this._loadDataPoints(metaData)); - if (metaData.useSampleModel && dataPoints.length === 0) { - dataPoints = await this.keepLast.add(this._loadDataPoints(metaData, true)); - } else { - metaData.useSampleModel = false; - } + this.dataPoints = await this.keepLast.add(this._loadDataPoints(metaData)); this.metaData = metaData; - this.dataPoints = dataPoints; } /** @@ -376,21 +369,11 @@ export class GraphModel extends Model { * with an aggregation function, such as my_date:week. * @protected * @param {Object} metaData - * @param {boolean} [sample=false] * @returns {Object[]} */ - async _loadDataPoints(metaData, sample = false) { + async _loadDataPoints(metaData) { const { measure, domains, fields, groupBy, resModel } = metaData; - if (sample && !this._fakeORM) { - this._fakeORM = buildSampleORM( - resModel, - Object.assign({ __count: { type: "integer" } }, fields), - this.user - ); - } - const orm2use = sample ? this._fakeORM : this.orm; - const measures = ["__count"]; if (measure !== "__count") { let { group_operator, type } = fields[measure]; @@ -410,7 +393,7 @@ export class GraphModel extends Model { // for instance [1, "ABC"] [3, "ABC"] should be distinguished. domains.forEach((domain, originIndex) => { proms.push( - orm2use + this.orm .webReadGroup( resModel, domain.arrayRepr, @@ -538,4 +521,3 @@ export class GraphModel extends Model { } } } -GraphModel.services = ["orm", "user"]; diff --git a/addons/web/static/src/views/graph/graph_view.js b/addons/web/static/src/views/graph/graph_view.js index 919fc29a88c..6513da03892 100644 --- a/addons/web/static/src/views/graph/graph_view.js +++ b/addons/web/static/src/views/graph/graph_view.js @@ -30,7 +30,6 @@ const KEYS = [ "resModel", "stacked", "title", - "useSampleModel", ]; export class GraphView extends Component { @@ -38,8 +37,9 @@ export class GraphView extends Component { this.actionService = useService("action"); let modelParams; - if (this.props.state) { - modelParams = this.props.state; + if (this.props.state && this.props.state.metaData) { + // check this (dashboard testss) + modelParams = this.props.state.metaData; } else { const { arch, fields } = this.props; const parser = new this.constructor.ArchParser(); @@ -53,7 +53,9 @@ export class GraphView extends Component { this.model = useModel(this.constructor.Model, modelParams); useSetupView({ - exportLocalState: () => this.model.metaData, + exportLocalState: () => { + return { metaData: this.model.metaData }; + }, saveParams: () => this.saveParams(), }); } diff --git a/addons/web/static/src/views/graph/graph_view.xml b/addons/web/static/src/views/graph/graph_view.xml index e07699eca6f..dcd4f870bca 100644 --- a/addons/web/static/src/views/graph/graph_view.xml +++ b/addons/web/static/src/views/graph/graph_view.xml @@ -46,7 +46,7 @@ -
+
@@ -55,14 +55,14 @@
- + diff --git a/addons/web/static/src/views/helpers/model.js b/addons/web/static/src/views/helpers/model.js index 77d51201fc1..3f0d4c56910 100644 --- a/addons/web/static/src/views/helpers/model.js +++ b/addons/web/static/src/views/helpers/model.js @@ -2,6 +2,8 @@ import { SEARCH_KEYS } from "@web/search/with_search/with_search"; import { useBus, useService } from "@web/core/utils/hooks"; +import { buildSampleORM } from "@web/views/helpers/sample_server"; +import { useSetupView } from "@web/views/helpers/view_hook"; const { core, hooks } = owl; const { EventBus } = core; @@ -19,6 +21,8 @@ export class Model extends EventBus { constructor(env, params, services) { super(); this.env = env; + this.orm = services.orm; + this.useSampleModel = false; // will be set to true by the "useModel" hook if necessary this.setup(params, services); } @@ -31,7 +35,20 @@ export class Model extends EventBus { /** * @param {SearchParams} searchParams */ - load(/* searchParams */) {} + async load(/* searchParams */) {} + + /** + * This function is meant to be overriden by models that want to implement + * the sample data feature. It should return true iff the last loaded state + * actually contains data. If not, another load will be done (if the sample + * feature is enabled) with the orm service substituted by another using the + * SampleServer, to have sample data to display instead of an empty screen. + * + * @returns {boolean} + */ + hasData() { + return true; + } notify() { this.trigger("update"); @@ -68,18 +85,51 @@ export function useModel(ModelClass, params, options = {}) { for (const key of ModelClass.services) { services[key] = useService(key); } + services.orm = services.orm || useService("orm"); + const model = new ModelClass(component.env, params, services); useBus(model, "update", options.onUpdate || component.render); + const globalState = component.props.globalState || {}; + let useSampleModel = Boolean( + "useSampleModel" in globalState + ? globalState.useSampleModel + : component.props.useSampleModel + ); + model.useSampleModel = useSampleModel; + const orm = model.orm; + let sampleORM = globalState.sampleORM; + const user = useService("user"); + async function load(props) { + model.orm = orm; + const searchParams = getSearchParams(props); + await model.load(searchParams); + if (useSampleModel && !model.hasData()) { + sampleORM = + sampleORM || buildSampleORM(component.props.resModel, component.props.fields, user); + model.orm = sampleORM; + await model.load(searchParams); + } else { + useSampleModel = false; + } + model.useSampleModel = useSampleModel; + } onWillStart(() => { - const searchParams = getSearchParams(component.props); - return model.load(searchParams); + return load(component.props); + }); + onWillUpdateProps((nextProps) => { + useSampleModel = false; + return load(nextProps); }); - onWillUpdateProps((nextProps) => { - const searchParams = getSearchParams(nextProps); - searchParams.useSampleModel = false; - return model.load(searchParams); + useSetupView({ + exportGlobalState() { + return { + sampleORM, + useSampleModel: model.useSampleModel, + }; + }, }); + return model; } diff --git a/addons/web/static/src/views/helpers/standard_view_props.js b/addons/web/static/src/views/helpers/standard_view_props.js index ae2ae2063ae..30b10349405 100644 --- a/addons/web/static/src/views/helpers/standard_view_props.js +++ b/addons/web/static/src/views/helpers/standard_view_props.js @@ -29,6 +29,7 @@ export const standardViewProps = { orderBy: { type: Array, elements: String }, useSampleModel: { type: Boolean }, state: { type: Object, optional: 1 }, + globalState: { type: Object, optional: 1 }, resId: { type: Number, optional: 1 }, resIds: { type: Array, optional: 1 }, bannerRoute: { type: String, optional: 1 }, diff --git a/addons/web/static/src/views/pivot/pivot_model.js b/addons/web/static/src/views/pivot/pivot_model.js index 5527587bd46..68a4ef04b6a 100644 --- a/addons/web/static/src/views/pivot/pivot_model.js +++ b/addons/web/static/src/views/pivot/pivot_model.js @@ -6,7 +6,6 @@ import { KeepLast } from "@web/core/utils/concurrency"; import { computeVariation } from "@web/core/utils/numbers"; import { DEFAULT_INTERVAL } from "@web/search/utils/dates"; import { Model } from "@web/views/helpers/model"; -import { buildSampleORM } from "@web/views/helpers/sample_server"; import { computeReportMeasures, processMeasure } from "@web/views/helpers/utils"; /** @@ -338,10 +337,7 @@ export class PivotModel extends Model { * @param {Object|null} [params.metaData.sortedColumn=null] * @param {Object} [params.data] previously exported data */ - setup(params, { orm, user }) { - this.realORM = orm; - this.orm2Use = this.realORM; - this.user = user; + setup(params) { this.keepLast = new KeepLast(); let sortedColumn = params.metaData.sortedColumn || null; @@ -681,8 +677,7 @@ export class PivotModel extends Model { * @param {SearchParams} searchParams */ async load(searchParams) { - this.orm2Use = this.realORM; - this.searchParams = JSON.parse(JSON.stringify(searchParams)); + this.searchParams = searchParams; const activeMeasures = processMeasure(searchParams.context.pivot_measures) || this.metaData.activeMeasures; @@ -954,7 +949,7 @@ export class PivotModel extends Model { const measureSpecs = this._getMeasureSpecs(config); const groupBy = rowGroupBy.concat(colGroupBy); const options = { lazy: false }; - const subGroups = await this.orm2Use.readGroup( + const subGroups = await this.orm.readGroup( config.metaData.resModel, groupDomain, measureSpecs, @@ -1382,14 +1377,6 @@ export class PivotModel extends Model { return this._subdivideGroup(group, divisors.slice(1), config); }) .then(async () => { - if (metaData.useSampleModel && !this._hasData(config.data)) { - const fakeORM = buildSampleORM(metaData.resModel, metaData.fields, this.user); - this.orm2Use = fakeORM; - await this._loadData(config); - } else if (this.orm2Use.rpc.name !== "fakeRPC") { - metaData.useSampleModel = false; - } - // keep folded groups folded after the reload if the structure of the table is the same if (prune && this._hasData(data) && this._hasData(this.data)) { if ( @@ -1653,4 +1640,3 @@ export class PivotModel extends Model { }); } } -PivotModel.services = ["orm", "user"]; diff --git a/addons/web/static/src/views/pivot/pivot_renderer.xml b/addons/web/static/src/views/pivot/pivot_renderer.xml index dafe3249a2f..494cb2f6c63 100644 --- a/addons/web/static/src/views/pivot/pivot_renderer.xml +++ b/addons/web/static/src/views/pivot/pivot_renderer.xml @@ -3,7 +3,7 @@
- +
diff --git a/addons/web/static/src/views/pivot/pivot_view.js b/addons/web/static/src/views/pivot/pivot_view.js index a3c228f53ce..2782205fb32 100644 --- a/addons/web/static/src/views/pivot/pivot_view.js +++ b/addons/web/static/src/views/pivot/pivot_view.js @@ -46,7 +46,6 @@ export class PivotView extends Component { resModel: this.props.resModel, rowGroupBys: archInfo.rowGroupBys, title: this.props.title || archInfo.title || this.env._t("Untitled"), - useSampleModel: Boolean(this.props.useSampleModel), widgets: archInfo.widgets, }; } diff --git a/addons/web/static/src/views/pivot/pivot_view.xml b/addons/web/static/src/views/pivot/pivot_view.xml index 47dacb8515b..f53659259ab 100644 --- a/addons/web/static/src/views/pivot/pivot_view.xml +++ b/addons/web/static/src/views/pivot/pivot_view.xml @@ -17,7 +17,7 @@ -
+
@@ -28,7 +28,7 @@ diff --git a/addons/web/static/src/views/view.js b/addons/web/static/src/views/view.js index 6bcaffcd5fb..345a817ff90 100644 --- a/addons/web/static/src/views/view.js +++ b/addons/web/static/src/views/view.js @@ -222,11 +222,14 @@ export class View extends Component { displayName: this.props.displayName, }, arch, + bannerRoute, fields, resModel, useSampleModel: false, - bannerRoute, }; + if (this.props.globalState) { + viewProps.globalState = this.props.globalState; + } if ("useSampleModel" in this.props) { viewProps.useSampleModel = this.props.useSampleModel;