[REF] web: views: factorize sample data logic
This commit factorizes the sample data logic to the Model and useModel hook. That way, nothing as to be done in concrete models anymore. This commit also ensures that searchParams given to the view are a deep copy of what relies in the SearchModel. Before this commit, the PivotModel cloned them in its "load" method. It is now done in the SearchModel for every views. X-original-commit: 0f43b7994b6963644cea5b694ba6b07df7af8989 Part-of: odoo/odoo#76576
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
</t>
|
||||
|
||||
<t t-name="web.GraphView" owl="1">
|
||||
<div class="o_graph_view" t-att-class="{ o_view_sample_data: model.metaData.useSampleModel }">
|
||||
<div class="o_graph_view" t-att-class="{ o_view_sample_data: model.useSampleModel }">
|
||||
<ControlPanel t-if="env.searchModel.display.controlPanel" t-props="props.info">
|
||||
<t t-set-slot="control-panel-bottom-left">
|
||||
<t t-call="{{ constructor.buttonTemplate }}"/>
|
||||
@@ -55,14 +55,14 @@
|
||||
<div class="o_content">
|
||||
<SearchPanel t-if="env.searchModel.display.searchPanel" />
|
||||
<t t-if="model.data">
|
||||
<t t-if="model.metaData.useSampleModel and props.info.noContentHelp" t-call="web.ActionHelper">
|
||||
<t t-if="model.useSampleModel and props.info.noContentHelp" t-call="web.ActionHelper">
|
||||
<t t-set="noContentHelp" t-value="props.info.noContentHelp"/>
|
||||
</t>
|
||||
<Renderer
|
||||
class="o_renderer"
|
||||
data="model.data"
|
||||
metaData="model.metaData"
|
||||
t-att-class="{ o_sample_data_disabled: model.metaData.useSampleModel }"
|
||||
t-att-class="{ o_sample_data_disabled: model.useSampleModel }"
|
||||
t-on-inspect-domain-records="onInspectDomainRecords"
|
||||
/>
|
||||
</t>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<t t-name="web.PivotRenderer" owl="1">
|
||||
<div class="o_pivot table-responsive">
|
||||
<table class="table-hover table-sm table-bordered" t-att-class="{ o_enable_linking: !model.metaData.disableLinking, o_sample_data_disabled: model.metaData.useSampleModel }">
|
||||
<table class="table-hover table-sm table-bordered" t-att-class="{ o_enable_linking: !model.metaData.disableLinking, o_sample_data_disabled: model.useSampleModel }">
|
||||
<thead>
|
||||
<tr t-foreach="table.headers" t-as="row" t-key="'header_' + row_index">
|
||||
<t t-foreach="row" t-as="cell" t-key="'header_row_' + cell_index">
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</t>
|
||||
|
||||
<t t-name="web.PivotView" owl="1">
|
||||
<div class="o_pivot_view" t-att-class="{o_view_sample_data: model.metaData.useSampleModel }">
|
||||
<div class="o_pivot_view" t-att-class="{o_view_sample_data: model.useSampleModel }">
|
||||
<ControlPanel t-if="env.searchModel.display.controlPanel" t-props="props.info">
|
||||
<t t-set-slot="control-panel-bottom-left">
|
||||
<t t-call="{{ constructor.buttonTemplate }}"/>
|
||||
@@ -28,7 +28,7 @@
|
||||
<t t-set="displayNoContent" t-value="
|
||||
props.info.noContentHelp !== false and (
|
||||
!(model.hasData() and model.metaData.activeMeasures.length) or
|
||||
model.metaData.useSampleModel
|
||||
model.useSampleModel
|
||||
)" />
|
||||
<t t-if="displayNoContent">
|
||||
<t t-if="props.info.noContentHelp" t-call="web.ActionHelper">
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user