[IMP] web: load tags in kanban view in one batch
Add an 'enableRelationalFetch' option in _readGroup who force to make only one rpc for x2m and reference fields. Usefull to avoid useless (and blocking when too numerous) rpcs (tags field in a kanban view for example). Task #1878113 (2/2) closes odoo/odoo#27731
This commit is contained in:
@@ -2188,7 +2188,7 @@ var BasicModel = AbstractModel.extend({
|
|||||||
* @param {Object} [options]
|
* @param {Object} [options]
|
||||||
* @param {string[]} [options.fieldNames] the list of fields to fetch. If
|
* @param {string[]} [options.fieldNames] the list of fields to fetch. If
|
||||||
* not given, fetch all the fields in record.fieldNames (+ display_name)
|
* not given, fetch all the fields in record.fieldNames (+ display_name)
|
||||||
* @param {string} [optinos.viewType] the type of view for which the record
|
* @param {string} [options.viewType] the type of view for which the record
|
||||||
* is fetched (usefull to load the adequate fields), by defaults, uses
|
* is fetched (usefull to load the adequate fields), by defaults, uses
|
||||||
* record.viewType
|
* record.viewType
|
||||||
* @returns {Deferred<Object>} resolves to the record or is rejected in
|
* @returns {Deferred<Object>} resolves to the record or is rejected in
|
||||||
@@ -2257,6 +2257,46 @@ var BasicModel = AbstractModel.extend({
|
|||||||
}
|
}
|
||||||
return $.when(def);
|
return $.when(def);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Fetches data for reference fields and assigns these data to newly
|
||||||
|
* created datapoint.
|
||||||
|
* Then places datapoint reference into parent record.
|
||||||
|
*
|
||||||
|
* @param {Object} datapoints a collection of ids classed by model,
|
||||||
|
* @see _getDataToFetchByModel
|
||||||
|
* @param {string} model
|
||||||
|
* @param {string} fieldName
|
||||||
|
* @returns {Deferred}
|
||||||
|
*/
|
||||||
|
_fetchReferenceData: function (datapoints, model, fieldName) {
|
||||||
|
var self = this;
|
||||||
|
var ids = _.map(Object.keys(datapoints), function (id) { return parseInt(id); });
|
||||||
|
// we need one parent for the context (they all have the same)
|
||||||
|
var parent = datapoints[ids[0]][0];
|
||||||
|
var def = self._rpc({
|
||||||
|
model: model,
|
||||||
|
method: 'name_get',
|
||||||
|
args: [ids],
|
||||||
|
context: self.localData[parent].getContext({fieldName: fieldName}),
|
||||||
|
}).then(function (result) {
|
||||||
|
_.each(result, function (el) {
|
||||||
|
var parentIDs = datapoints[el[0]];
|
||||||
|
_.each(parentIDs, function (parentID) {
|
||||||
|
var parent = self.localData[parentID];
|
||||||
|
var referenceDp = self._makeDataPoint({
|
||||||
|
data: {
|
||||||
|
id: el[0],
|
||||||
|
display_name: el[1],
|
||||||
|
},
|
||||||
|
modelName: model,
|
||||||
|
parentID: parent,
|
||||||
|
});
|
||||||
|
parent.data[fieldName] = referenceDp.id;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return def;
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Fetch the extra data (`name_get`) for the reference fields of the record
|
* Fetch the extra data (`name_get`) for the reference fields of the record
|
||||||
* model.
|
* model.
|
||||||
@@ -2294,59 +2334,13 @@ var BasicModel = AbstractModel.extend({
|
|||||||
_fetchReferenceBatched: function (list, fieldName) {
|
_fetchReferenceBatched: function (list, fieldName) {
|
||||||
var self = this;
|
var self = this;
|
||||||
list = this._applyX2ManyOperations(list);
|
list = this._applyX2ManyOperations(list);
|
||||||
|
this._sortList(list);
|
||||||
|
|
||||||
// collect ids by model
|
var toFetch = this._getDataToFetchByModel(list, fieldName);
|
||||||
var toFetch = {};
|
|
||||||
_.each(list.data, function (dataPoint) {
|
|
||||||
var record = self.localData[dataPoint];
|
|
||||||
var value = record.data[fieldName];
|
|
||||||
// if the reference field has already been fetched, the value is a
|
|
||||||
// datapoint ID, and in this case there's nothing to do
|
|
||||||
if (value && !self.localData[value]) {
|
|
||||||
var model = value.split(',')[0];
|
|
||||||
var resID = value.split(',')[1];
|
|
||||||
if (!(model in toFetch)) {
|
|
||||||
toFetch[model] = {};
|
|
||||||
}
|
|
||||||
// there could be multiple datapoints with the same model/resID
|
|
||||||
if (toFetch[model][resID]) {
|
|
||||||
toFetch[model][resID].push(dataPoint);
|
|
||||||
} else {
|
|
||||||
toFetch[model][resID] = [dataPoint];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var defs = [];
|
var defs = [];
|
||||||
var def;
|
|
||||||
// one name_get by model
|
// one name_get by model
|
||||||
_.each(toFetch, function (datapoints, model) {
|
_.each(toFetch, function (datapoints, model) {
|
||||||
var ids = _.map(Object.keys(datapoints), function (id) { return parseInt(id); });
|
defs.push(self._fetchReferenceData(datapoints, model, fieldName));
|
||||||
// we need one parent for the context (they all have the same)
|
|
||||||
var parent = datapoints[ids[0]][0];
|
|
||||||
def = self._rpc({
|
|
||||||
model: model,
|
|
||||||
method: 'name_get',
|
|
||||||
args: [ids],
|
|
||||||
context: self.localData[parent].getContext({fieldName: fieldName}),
|
|
||||||
}).then(function (result) {
|
|
||||||
_.each(result, function (el) {
|
|
||||||
var parentIDs = datapoints[el[0]];
|
|
||||||
_.each(parentIDs, function (parentID) {
|
|
||||||
var parent = self.localData[parentID];
|
|
||||||
var referenceDp = self._makeDataPoint({
|
|
||||||
data: {
|
|
||||||
id: el[0],
|
|
||||||
display_name: el[1],
|
|
||||||
},
|
|
||||||
modelName: model,
|
|
||||||
parentID: parent,
|
|
||||||
});
|
|
||||||
parent.data[fieldName] = referenceDp.id;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
defs.push(def);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return $.when.apply($, defs);
|
return $.when.apply($, defs);
|
||||||
@@ -2368,6 +2362,94 @@ var BasicModel = AbstractModel.extend({
|
|||||||
}
|
}
|
||||||
return $.when.apply($, defs);
|
return $.when.apply($, defs);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Batch reference requests for all records in list.
|
||||||
|
*
|
||||||
|
* @see _fetchReferencesSingleBatch
|
||||||
|
* @param {Object} list a valid resource object
|
||||||
|
* @param {string} fieldName
|
||||||
|
* @returns {Deferred}
|
||||||
|
*/
|
||||||
|
_fetchReferenceSingleBatch: function (list, fieldName) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// collect ids by model
|
||||||
|
var toFetch = {};
|
||||||
|
_.each(list.data, function (groupIndex) {
|
||||||
|
var group = self.localData[groupIndex];
|
||||||
|
_.extend(toFetch, self._getDataToFetchByModel(group, fieldName));
|
||||||
|
});
|
||||||
|
|
||||||
|
var defs = [];
|
||||||
|
// one name_get by model
|
||||||
|
_.each(toFetch, function (datapoints, model) {
|
||||||
|
defs.push(self._fetchReferenceData(datapoints, model, fieldName));
|
||||||
|
});
|
||||||
|
|
||||||
|
return $.when.apply($, defs);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Batch requests for all reference field in list's children.
|
||||||
|
* Called by _readGroup to make only one 'name_get' rpc by fieldName.
|
||||||
|
*
|
||||||
|
* @param {Object} list a valid resource object
|
||||||
|
* @returns {Deferred}
|
||||||
|
*/
|
||||||
|
_fetchReferencesSingleBatch: function (list) {
|
||||||
|
var defs = [];
|
||||||
|
var fieldNames = list.getFieldNames();
|
||||||
|
for (var fIndex in fieldNames) {
|
||||||
|
var field = list.fields[fieldNames[fIndex]];
|
||||||
|
if (field.type === 'reference') {
|
||||||
|
defs.push(this._fetchReferenceSingleBatch(list, fieldNames[fIndex]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $.when.apply($, defs);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Fetch model data from server, relationally to fieldName and resulted
|
||||||
|
* field relation. For example, if fieldName is "tag_ids" and referred to
|
||||||
|
* project.tags, it will fetch project.tags' related fields where its id is
|
||||||
|
* contained in toFetch.ids array.
|
||||||
|
*
|
||||||
|
* @param {Object} list a valid resource object
|
||||||
|
* @param {Object} toFetch a list of records and res_ids,
|
||||||
|
* @see _getDataToFetch
|
||||||
|
* @param {string} fieldName
|
||||||
|
* @returns {Deferred}
|
||||||
|
*/
|
||||||
|
_fetchRelatedData: function (list, toFetch, fieldName) {
|
||||||
|
var self = this;
|
||||||
|
var ids = _.keys(toFetch);
|
||||||
|
for (var i = 0; i < ids.length; i++) {
|
||||||
|
ids[i] = Number(ids[i]);
|
||||||
|
}
|
||||||
|
var fieldInfo = list.fieldsInfo[list.viewType][fieldName];
|
||||||
|
|
||||||
|
if (!ids.length || fieldInfo.__no_fetch) {
|
||||||
|
return $.when();
|
||||||
|
}
|
||||||
|
|
||||||
|
var def;
|
||||||
|
var fieldNames = _.keys(fieldInfo.relatedFields);
|
||||||
|
if (fieldNames.length) {
|
||||||
|
var field = list.fields[fieldName];
|
||||||
|
def = this._rpc({
|
||||||
|
model: field.relation,
|
||||||
|
method: 'read',
|
||||||
|
args: [ids, fieldNames],
|
||||||
|
context: list.getContext() || {},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
def = $.when(_.map(ids, function (id) {
|
||||||
|
return {id:id};
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return def.then(function (result) {
|
||||||
|
var records = _.uniq(_.flatten(_.values(toFetch)));
|
||||||
|
self._updateRecordsData(records, fieldName, result);
|
||||||
|
});
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* This method is incorrectly named. It should be named something like
|
* This method is incorrectly named. It should be named something like
|
||||||
* _fetchMany2OneData.
|
* _fetchMany2OneData.
|
||||||
@@ -2658,9 +2740,14 @@ var BasicModel = AbstractModel.extend({
|
|||||||
* Fetch all data in a ungrouped list
|
* Fetch all data in a ungrouped list
|
||||||
*
|
*
|
||||||
* @param {Object} list a valid resource object
|
* @param {Object} list a valid resource object
|
||||||
* @returns {Deferred<Object>} resolves to the fecthed list
|
* @param {Object} [options]
|
||||||
|
* @param {boolean} [options.enableRelationalFetch=true] if false, will not
|
||||||
|
* fetch x2m and relational data (that will be done by _readGroup in this
|
||||||
|
* case).
|
||||||
|
* @returns {Deferred<Object>} resolves to the fetched list
|
||||||
*/
|
*/
|
||||||
_fetchUngroupedList: function (list) {
|
_fetchUngroupedList: function (list, options) {
|
||||||
|
options = _.defaults(options || {}, {enableRelationalFetch: true});
|
||||||
var self = this;
|
var self = this;
|
||||||
var def;
|
var def;
|
||||||
if (list.static) {
|
if (list.static) {
|
||||||
@@ -2678,13 +2765,31 @@ var BasicModel = AbstractModel.extend({
|
|||||||
def = this._searchReadUngroupedList(list);
|
def = this._searchReadUngroupedList(list);
|
||||||
}
|
}
|
||||||
return def.then(function () {
|
return def.then(function () {
|
||||||
return $.when(
|
if (options.enableRelationalFetch) {
|
||||||
self._fetchX2ManysBatched(list),
|
return $.when(
|
||||||
self._fetchReferencesBatched(list));
|
self._fetchX2ManysBatched(list, options),
|
||||||
|
self._fetchReferencesBatched(list, options)
|
||||||
|
);
|
||||||
|
}
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* batch requests for 1 x2m in list
|
||||||
|
*
|
||||||
|
* @see _fetchX2ManysBatched
|
||||||
|
* @param {Object} list
|
||||||
|
* @param {string} fieldName
|
||||||
|
* @returns {Deferred}
|
||||||
|
*/
|
||||||
|
_fetchX2ManyBatched: function (list, fieldName) {
|
||||||
|
list = this._applyX2ManyOperations(list);
|
||||||
|
this._sortList(list);
|
||||||
|
|
||||||
|
var toFetch = this._getDataToFetch(list, fieldName);
|
||||||
|
return this._fetchRelatedData(list, toFetch, fieldName);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* X2Manys have to be fetched by separate rpcs (their data are stored on
|
* X2Manys have to be fetched by separate rpcs (their data are stored on
|
||||||
* different models). This method takes a record, look at its x2many fields,
|
* different models). This method takes a record, look at its x2many fields,
|
||||||
@@ -2752,93 +2857,6 @@ var BasicModel = AbstractModel.extend({
|
|||||||
});
|
});
|
||||||
return $.when.apply($, defs);
|
return $.when.apply($, defs);
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* batch requests for 1 x2m in list
|
|
||||||
*
|
|
||||||
* @see _fetchX2ManysBatched
|
|
||||||
* @param {Object} list
|
|
||||||
* @param {string} fieldName
|
|
||||||
* @returns {Deferred}
|
|
||||||
*/
|
|
||||||
_fetchX2ManyBatched: function (list, fieldName) {
|
|
||||||
var self = this;
|
|
||||||
var field = list.fields[fieldName];
|
|
||||||
var fieldInfo = list.fieldsInfo[list.viewType][fieldName];
|
|
||||||
var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode];
|
|
||||||
var fieldsInfo = view ? view.fieldsInfo : fieldInfo.fieldsInfo;
|
|
||||||
var fields = view ? view.fields : fieldInfo.relatedFields;
|
|
||||||
var viewType = view ? view.type : fieldInfo.viewType;
|
|
||||||
list = this._applyX2ManyOperations(list);
|
|
||||||
this._sortList(list);
|
|
||||||
var x2mRecords = [];
|
|
||||||
|
|
||||||
// step 1: collect ids
|
|
||||||
var ids = [];
|
|
||||||
_.each(list.data, function (dataPoint) {
|
|
||||||
var record = self.localData[dataPoint];
|
|
||||||
if (typeof record.data[fieldName] === 'string') {
|
|
||||||
// in this case, the value is a local ID, which means that the
|
|
||||||
// record has already been processed. It can happen for example
|
|
||||||
// when a user adds a record in a m2m relation, or loads more
|
|
||||||
// records in a kanban column
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
x2mRecords.push(record);
|
|
||||||
ids = _.unique(ids.concat(record.data[fieldName] || []));
|
|
||||||
var m2mList = self._makeDataPoint({
|
|
||||||
fieldsInfo: fieldsInfo,
|
|
||||||
fields: fields,
|
|
||||||
modelName: field.relation,
|
|
||||||
parentID: record.id,
|
|
||||||
res_ids: record.data[fieldName],
|
|
||||||
static: true,
|
|
||||||
type: 'list',
|
|
||||||
viewType: viewType,
|
|
||||||
});
|
|
||||||
record.data[fieldName] = m2mList.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!ids.length || fieldInfo.__no_fetch) {
|
|
||||||
return $.when();
|
|
||||||
}
|
|
||||||
var def;
|
|
||||||
var fieldNames = _.keys(fieldInfo.relatedFields);
|
|
||||||
// step 2: fetch data from server
|
|
||||||
// if we want specific fields
|
|
||||||
// if not we return an array of objects with the id
|
|
||||||
// to avoid fetching all the relation fields and an useless rpc
|
|
||||||
if (fieldNames.length) {
|
|
||||||
def = this._rpc({
|
|
||||||
model: field.relation,
|
|
||||||
method: 'read',
|
|
||||||
args: [ids, fieldNames],
|
|
||||||
context: list.getContext() || {},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
def = $.when(_.map(ids, function (id) {
|
|
||||||
return {id:id};
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
return def.then(function (results) {
|
|
||||||
// step 3: assign values to correct datapoints
|
|
||||||
_.each(x2mRecords, function (record) {
|
|
||||||
var m2mList = self.localData[record.data[fieldName]];
|
|
||||||
m2mList.data = [];
|
|
||||||
_.each(m2mList.res_ids, function (res_id) {
|
|
||||||
var dataPoint = self._makeDataPoint({
|
|
||||||
modelName: field.relation,
|
|
||||||
data: _.findWhere(results, {id: res_id}),
|
|
||||||
fields: fields,
|
|
||||||
fieldsInfo: fieldsInfo,
|
|
||||||
parentID: m2mList.id,
|
|
||||||
viewType: viewType,
|
|
||||||
});
|
|
||||||
m2mList.data.push(dataPoint.id);
|
|
||||||
m2mList._cache[res_id] = dataPoint.id;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* batch request for x2ms for datapoint of type list
|
* batch request for x2ms for datapoint of type list
|
||||||
*
|
*
|
||||||
@@ -2856,6 +2874,50 @@ var BasicModel = AbstractModel.extend({
|
|||||||
}
|
}
|
||||||
return $.when.apply($, defs);
|
return $.when.apply($, defs);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* For a non-static list, batches requests for all its sublists' records.
|
||||||
|
* Make only one rpc for all records on the concerned field.
|
||||||
|
*
|
||||||
|
* @see _fetchX2ManysSingleBatch
|
||||||
|
* @param {Object} list a valid resource object, its data must be another
|
||||||
|
* list containing records
|
||||||
|
* @param {string} fieldName
|
||||||
|
* @returns {Deferred}
|
||||||
|
*/
|
||||||
|
_fetchX2ManySingleBatch: function (list, fieldName) {
|
||||||
|
var self = this;
|
||||||
|
var toFetch = {};
|
||||||
|
_.each(list.data, function (groupIndex) {
|
||||||
|
var group = self.localData[groupIndex];
|
||||||
|
var nextDataToFetch = self._getDataToFetch(group, fieldName);
|
||||||
|
_.each(_.keys(nextDataToFetch), function (id) {
|
||||||
|
if (toFetch[id]) {
|
||||||
|
toFetch[id] = toFetch[id].concat(nextDataToFetch[id]);
|
||||||
|
} else {
|
||||||
|
toFetch[id] = nextDataToFetch[id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return self._fetchRelatedData(list, toFetch, fieldName);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Batch requests for all x2m in list's children.
|
||||||
|
* Called by _readGroup to make only one 'read' rpc by fieldName.
|
||||||
|
*
|
||||||
|
* @param {Object} list a valid resource object
|
||||||
|
* @returns {Deferred}
|
||||||
|
*/
|
||||||
|
_fetchX2ManysSingleBatch: function (list) {
|
||||||
|
var defs = [];
|
||||||
|
var fieldNames = list.getFieldNames();
|
||||||
|
for (var i = 0; i < fieldNames.length; i++) {
|
||||||
|
var field = list.fields[fieldNames[i]];
|
||||||
|
if (field.type === 'many2many' || field.type === 'one2many'){
|
||||||
|
defs.push(this._fetchX2ManySingleBatch(list, fieldNames[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $.when.apply($, defs);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Generates an object mapping field names to their changed value in a given
|
* Generates an object mapping field names to their changed value in a given
|
||||||
* record (i.e. maps to the new value for basic fields, to the res_id for
|
* record (i.e. maps to the new value for basic fields, to the res_id for
|
||||||
@@ -3154,6 +3216,90 @@ var BasicModel = AbstractModel.extend({
|
|||||||
|
|
||||||
return context.eval();
|
return context.eval();
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Collects from a record a list of ids to fetch, according to fieldName,
|
||||||
|
* and a list of records where to set the result of the fetch.
|
||||||
|
*
|
||||||
|
* @param {Object} list a list containing records we want to get the ids,
|
||||||
|
* it assumes _applyX2ManyOperations and _sort have been already called on
|
||||||
|
* this list
|
||||||
|
* @param {string} fieldName
|
||||||
|
* @return {Object} a list of records and res_ids
|
||||||
|
*/
|
||||||
|
_getDataToFetch: function (list, fieldName) {
|
||||||
|
var self = this;
|
||||||
|
var field = list.fields[fieldName];
|
||||||
|
var fieldInfo = list.fieldsInfo[list.viewType][fieldName];
|
||||||
|
var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode];
|
||||||
|
var fieldsInfo = view ? view.fieldsInfo : fieldInfo.fieldsInfo;
|
||||||
|
var fields = view ? view.fields : fieldInfo.relatedFields;
|
||||||
|
var viewType = view ? view.type : fieldInfo.viewType;
|
||||||
|
|
||||||
|
var toFetch = {};
|
||||||
|
_.each(list.data, function (dataPoint) {
|
||||||
|
var record = self.localData[dataPoint];
|
||||||
|
if (typeof record.data[fieldName] === 'string'){
|
||||||
|
// in this case, the value is a local ID, which means that the
|
||||||
|
// record has already been processed. It can happen for example
|
||||||
|
// when a user adds a record in a m2m relation, or loads more
|
||||||
|
// records in a kanban column
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_.each(record.data[fieldName], function (id) {
|
||||||
|
toFetch[id] = toFetch[id] || [];
|
||||||
|
toFetch[id].push(record);
|
||||||
|
});
|
||||||
|
|
||||||
|
var m2mList = self._makeDataPoint({
|
||||||
|
fieldsInfo: fieldsInfo,
|
||||||
|
fields: fields,
|
||||||
|
modelName: field.relation,
|
||||||
|
parentID: record.id,
|
||||||
|
res_ids: record.data[fieldName],
|
||||||
|
static: true,
|
||||||
|
type: 'list',
|
||||||
|
viewType: viewType,
|
||||||
|
});
|
||||||
|
record.data[fieldName] = m2mList.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return toFetch;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Determines and returns from a list a collection of ids classed by
|
||||||
|
* their model.
|
||||||
|
*
|
||||||
|
* @param {Object} list a valid resource object
|
||||||
|
* @param {string} fieldName
|
||||||
|
* @returns {Object} each key represent a model and contain a sub-object
|
||||||
|
* where each key represent an id (res_id) containing an array of
|
||||||
|
* webclient id (referred to a datapoint, so not a res_id).
|
||||||
|
*/
|
||||||
|
_getDataToFetchByModel: function (list, fieldName) {
|
||||||
|
var self = this;
|
||||||
|
var toFetch = {};
|
||||||
|
_.each(list.data, function (dataPoint) {
|
||||||
|
var record = self.localData[dataPoint];
|
||||||
|
var value = record.data[fieldName];
|
||||||
|
// if the reference field has already been fetched, the value is a
|
||||||
|
// datapoint ID, and in this case there's nothing to do
|
||||||
|
if (value && !self.localData[value]) {
|
||||||
|
var model = value.split(',')[0];
|
||||||
|
var resID = value.split(',')[1];
|
||||||
|
if (!(model in toFetch)) {
|
||||||
|
toFetch[model] = {};
|
||||||
|
}
|
||||||
|
// there could be multiple datapoints with the same model/resID
|
||||||
|
if (toFetch[model][resID]) {
|
||||||
|
toFetch[model][resID].push(dataPoint);
|
||||||
|
} else {
|
||||||
|
toFetch[model][resID] = [dataPoint];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return toFetch;
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Some records are associated to a/some domain(s). This method allows to
|
* Some records are associated to a/some domain(s). This method allows to
|
||||||
* retrieve them, evaluated.
|
* retrieve them, evaluated.
|
||||||
@@ -3448,7 +3594,7 @@ var BasicModel = AbstractModel.extend({
|
|||||||
return this._readGroup(dataPoint, options);
|
return this._readGroup(dataPoint, options);
|
||||||
}
|
}
|
||||||
if (dataPoint.type === 'list' && !dataPoint.groupedBy.length) {
|
if (dataPoint.type === 'list' && !dataPoint.groupedBy.length) {
|
||||||
return this._fetchUngroupedList(dataPoint);
|
return this._fetchUngroupedList(dataPoint, options);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@@ -4121,6 +4267,7 @@ var BasicModel = AbstractModel.extend({
|
|||||||
list.count += newGroup.count;
|
list.count += newGroup.count;
|
||||||
if (newGroup.isOpen && newGroup.count > 0) {
|
if (newGroup.isOpen && newGroup.count > 0) {
|
||||||
openGroupCount++;
|
openGroupCount++;
|
||||||
|
options = _.defaults({enableRelationalFetch: false}, options);
|
||||||
defs.push(self._load(newGroup, options));
|
defs.push(self._load(newGroup, options));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -4138,6 +4285,7 @@ var BasicModel = AbstractModel.extend({
|
|||||||
emptyGroup.aggregateValues = {};
|
emptyGroup.aggregateValues = {};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return $.when.apply($, defs).then(function () {
|
return $.when.apply($, defs).then(function () {
|
||||||
if (!options || !options.onlyGroups) {
|
if (!options || !options.onlyGroups) {
|
||||||
// generate the res_ids of the main list, being the concatenation
|
// generate the res_ids of the main list, being the concatenation
|
||||||
@@ -4147,6 +4295,10 @@ var BasicModel = AbstractModel.extend({
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
|
}).then(function () {
|
||||||
|
self._fetchX2ManysSingleBatch(list);
|
||||||
|
self._fetchReferencesSingleBatch(list);
|
||||||
|
return list;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -4487,6 +4639,45 @@ var BasicModel = AbstractModel.extend({
|
|||||||
this._updateParentResIDs(parent);
|
this._updateParentResIDs(parent);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Helper method to create datapoints and assign them values, then link
|
||||||
|
* those datapoints into records' data.
|
||||||
|
*
|
||||||
|
* @param {Object[]} records a list of record where datapoints will be
|
||||||
|
* assigned, it assumes _applyX2ManyOperations and _sort have been
|
||||||
|
* already called on this list
|
||||||
|
* @param {string} fieldName concerned field in records
|
||||||
|
* @param {Object[]} values typically a list of values got from a rpc
|
||||||
|
*/
|
||||||
|
_updateRecordsData: function (records, fieldName, values) {
|
||||||
|
if (!records.length || !values) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var self = this;
|
||||||
|
var field = records[0].fields[fieldName];
|
||||||
|
var fieldInfo = records[0].fieldsInfo[records[0].viewType][fieldName];
|
||||||
|
var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode];
|
||||||
|
var fieldsInfo = view ? view.fieldsInfo : fieldInfo.fieldsInfo;
|
||||||
|
var fields = view ? view.fields : fieldInfo.relatedFields;
|
||||||
|
var viewType = view ? view.type : fieldInfo.viewType;
|
||||||
|
|
||||||
|
_.each(records, function (record) {
|
||||||
|
var x2mList = self.localData[record.data[fieldName]];
|
||||||
|
x2mList.data = [];
|
||||||
|
_.each(x2mList.res_ids, function (res_id) {
|
||||||
|
var dataPoint = self._makeDataPoint({
|
||||||
|
modelName: field.relation,
|
||||||
|
data: _.findWhere(values, {id: res_id}),
|
||||||
|
fields: fields,
|
||||||
|
fieldsInfo: fieldsInfo,
|
||||||
|
parentID: x2mList.id,
|
||||||
|
viewType: viewType,
|
||||||
|
});
|
||||||
|
x2mList.data.push(dataPoint.id);
|
||||||
|
x2mList._cache[res_id] = dataPoint.id;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Helper method. Recursively traverses the data, starting from the element
|
* Helper method. Recursively traverses the data, starting from the element
|
||||||
* record (or list), then following all relations. This is useful when one
|
* record (or list), then following all relations. This is useful when one
|
||||||
|
|||||||
@@ -2297,6 +2297,138 @@ QUnit.module('Views', {
|
|||||||
kanban.destroy();
|
kanban.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('o2m loaded in only one batch', function (assert) {
|
||||||
|
assert.expect(9);
|
||||||
|
|
||||||
|
this.data.subtask = {
|
||||||
|
fields: {
|
||||||
|
name: {string: 'Name', type: 'char'}
|
||||||
|
},
|
||||||
|
records: [
|
||||||
|
{id: 1, name: "subtask #1"},
|
||||||
|
{id: 2, name: "subtask #2"},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
this.data.partner.fields.subtask_ids = {
|
||||||
|
string: 'Subtasks',
|
||||||
|
type: 'one2many',
|
||||||
|
relation: 'subtask'
|
||||||
|
};
|
||||||
|
this.data.partner.records[0].subtask_ids = [1];
|
||||||
|
this.data.partner.records[1].subtask_ids = [2];
|
||||||
|
|
||||||
|
var kanban = createView({
|
||||||
|
View: KanbanView,
|
||||||
|
model: 'partner',
|
||||||
|
data: this.data,
|
||||||
|
arch: '<kanban class="o_kanban_test">' +
|
||||||
|
'<field name="product_id"/>' +
|
||||||
|
'<templates><t t-name="kanban-box">' +
|
||||||
|
'<div>' +
|
||||||
|
'<field name="subtask_ids" widget="many2many_tags"/>' +
|
||||||
|
'</div>' +
|
||||||
|
'</t></templates>' +
|
||||||
|
'</kanban>',
|
||||||
|
groupBy: ['product_id'],
|
||||||
|
mockRPC: function (route, args) {
|
||||||
|
assert.step(args.method || route);
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
kanban.reload();
|
||||||
|
assert.verifySteps([
|
||||||
|
'read_group',
|
||||||
|
'/web/dataset/search_read',
|
||||||
|
'/web/dataset/search_read',
|
||||||
|
'read',
|
||||||
|
'read_group',
|
||||||
|
'/web/dataset/search_read',
|
||||||
|
'/web/dataset/search_read',
|
||||||
|
'read',
|
||||||
|
]);
|
||||||
|
kanban.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('m2m loaded in only one batch', function (assert) {
|
||||||
|
assert.expect(9);
|
||||||
|
|
||||||
|
var kanban = createView({
|
||||||
|
View: KanbanView,
|
||||||
|
model: 'partner',
|
||||||
|
data: this.data,
|
||||||
|
arch: '<kanban class="o_kanban_test">' +
|
||||||
|
'<field name="product_id"/>' +
|
||||||
|
'<templates><t t-name="kanban-box">' +
|
||||||
|
'<div>' +
|
||||||
|
'<field name="category_ids" widget="many2many_tags"/>' +
|
||||||
|
'</div>' +
|
||||||
|
'</t></templates>' +
|
||||||
|
'</kanban>',
|
||||||
|
groupBy: ['product_id'],
|
||||||
|
mockRPC: function (route, args) {
|
||||||
|
assert.step(args.method || route);
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
kanban.reload();
|
||||||
|
assert.verifySteps([
|
||||||
|
'read_group',
|
||||||
|
'/web/dataset/search_read',
|
||||||
|
'/web/dataset/search_read',
|
||||||
|
'read',
|
||||||
|
'read_group',
|
||||||
|
'/web/dataset/search_read',
|
||||||
|
'/web/dataset/search_read',
|
||||||
|
'read',
|
||||||
|
]);
|
||||||
|
kanban.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('fetch reference in only one batch', function (assert) {
|
||||||
|
assert.expect(9);
|
||||||
|
|
||||||
|
this.data.partner.records[0].ref_product = 'product,3';
|
||||||
|
this.data.partner.records[1].ref_product = 'product,5';
|
||||||
|
this.data.partner.fields.ref_product = {
|
||||||
|
string: "Reference Field",
|
||||||
|
type: 'reference',
|
||||||
|
};
|
||||||
|
|
||||||
|
var kanban = createView({
|
||||||
|
View: KanbanView,
|
||||||
|
model: 'partner',
|
||||||
|
data: this.data,
|
||||||
|
arch: '<kanban class="o_kanban_test">' +
|
||||||
|
'<field name="product_id"/>' +
|
||||||
|
'<templates><t t-name="kanban-box">' +
|
||||||
|
'<div class="oe_kanban_global_click">' +
|
||||||
|
'<field name="ref_product"/>' +
|
||||||
|
'</div>' +
|
||||||
|
'</t></templates>' +
|
||||||
|
'</kanban>',
|
||||||
|
groupBy: ['product_id'],
|
||||||
|
mockRPC: function (route, args) {
|
||||||
|
assert.step(args.method || route);
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
kanban.reload();
|
||||||
|
assert.verifySteps([
|
||||||
|
'read_group',
|
||||||
|
'/web/dataset/search_read',
|
||||||
|
'/web/dataset/search_read',
|
||||||
|
'name_get',
|
||||||
|
'read_group',
|
||||||
|
'/web/dataset/search_read',
|
||||||
|
'/web/dataset/search_read',
|
||||||
|
'name_get',
|
||||||
|
]);
|
||||||
|
kanban.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
QUnit.test('can drag and drop a record from one column to the next', function (assert) {
|
QUnit.test('can drag and drop a record from one column to the next', function (assert) {
|
||||||
assert.expect(9);
|
assert.expect(9);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user