[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 {string[]} [options.fieldNames] the list of fields to fetch. If
|
||||
* 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
|
||||
* record.viewType
|
||||
* @returns {Deferred<Object>} resolves to the record or is rejected in
|
||||
@@ -2257,6 +2257,46 @@ var BasicModel = AbstractModel.extend({
|
||||
}
|
||||
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
|
||||
* model.
|
||||
@@ -2294,59 +2334,13 @@ var BasicModel = AbstractModel.extend({
|
||||
_fetchReferenceBatched: function (list, fieldName) {
|
||||
var self = this;
|
||||
list = this._applyX2ManyOperations(list);
|
||||
this._sortList(list);
|
||||
|
||||
// collect ids by model
|
||||
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 toFetch = this._getDataToFetchByModel(list, fieldName);
|
||||
var defs = [];
|
||||
var def;
|
||||
// one name_get by model
|
||||
_.each(toFetch, function (datapoints, model) {
|
||||
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];
|
||||
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);
|
||||
defs.push(self._fetchReferenceData(datapoints, model, fieldName));
|
||||
});
|
||||
|
||||
return $.when.apply($, defs);
|
||||
@@ -2368,6 +2362,94 @@ var BasicModel = AbstractModel.extend({
|
||||
}
|
||||
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
|
||||
* _fetchMany2OneData.
|
||||
@@ -2658,9 +2740,14 @@ var BasicModel = AbstractModel.extend({
|
||||
* Fetch all data in a ungrouped list
|
||||
*
|
||||
* @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 def;
|
||||
if (list.static) {
|
||||
@@ -2678,13 +2765,31 @@ var BasicModel = AbstractModel.extend({
|
||||
def = this._searchReadUngroupedList(list);
|
||||
}
|
||||
return def.then(function () {
|
||||
return $.when(
|
||||
self._fetchX2ManysBatched(list),
|
||||
self._fetchReferencesBatched(list));
|
||||
if (options.enableRelationalFetch) {
|
||||
return $.when(
|
||||
self._fetchX2ManysBatched(list, options),
|
||||
self._fetchReferencesBatched(list, options)
|
||||
);
|
||||
}
|
||||
}).then(function () {
|
||||
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
|
||||
* different models). This method takes a record, look at its x2many fields,
|
||||
@@ -2752,93 +2857,6 @@ var BasicModel = AbstractModel.extend({
|
||||
});
|
||||
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
|
||||
*
|
||||
@@ -2856,6 +2874,50 @@ var BasicModel = AbstractModel.extend({
|
||||
}
|
||||
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
|
||||
* 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();
|
||||
},
|
||||
/**
|
||||
* 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
|
||||
* retrieve them, evaluated.
|
||||
@@ -3448,7 +3594,7 @@ var BasicModel = AbstractModel.extend({
|
||||
return this._readGroup(dataPoint, options);
|
||||
}
|
||||
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;
|
||||
if (newGroup.isOpen && newGroup.count > 0) {
|
||||
openGroupCount++;
|
||||
options = _.defaults({enableRelationalFetch: false}, options);
|
||||
defs.push(self._load(newGroup, options));
|
||||
}
|
||||
});
|
||||
@@ -4138,6 +4285,7 @@ var BasicModel = AbstractModel.extend({
|
||||
emptyGroup.aggregateValues = {};
|
||||
});
|
||||
}
|
||||
|
||||
return $.when.apply($, defs).then(function () {
|
||||
if (!options || !options.onlyGroups) {
|
||||
// generate the res_ids of the main list, being the concatenation
|
||||
@@ -4147,6 +4295,10 @@ var BasicModel = AbstractModel.extend({
|
||||
}));
|
||||
}
|
||||
return list;
|
||||
}).then(function () {
|
||||
self._fetchX2ManysSingleBatch(list);
|
||||
self._fetchReferencesSingleBatch(list);
|
||||
return list;
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -4487,6 +4639,45 @@ var BasicModel = AbstractModel.extend({
|
||||
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
|
||||
* record (or list), then following all relations. This is useful when one
|
||||
|
||||
@@ -2297,6 +2297,138 @@ QUnit.module('Views', {
|
||||
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) {
|
||||
assert.expect(9);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user