[IMP] web: fetch records alongside read_group

This rev. adds params to the 'web_read_group' method, allowing to
search_read records inside each groups in a single RPC, instead of
doing a 'web_read_group' RPC followed by n 'search_read' RPCs,
where n is the number of opened groups.

Part of task 1915702
This commit is contained in:
Aaron Bohy
2019-04-02 11:57:00 +00:00
parent d0164d834b
commit ca19b17fd3
7 changed files with 126 additions and 53 deletions
+2 -18
View File
@@ -878,8 +878,7 @@ class DataSet(http.Controller):
def search_read(self, model, fields=False, offset=0, limit=False, domain=None, sort=None):
return self.do_search_read(model, fields, offset, limit, domain, sort)
def do_search_read(self, model, fields=False, offset=0, limit=False, domain=None
, sort=None):
def do_search_read(self, model, fields=False, offset=0, limit=False, domain=None, sort=None):
""" Performs a search() followed by a read() (if needed) using the
provided search criteria
@@ -896,22 +895,7 @@ class DataSet(http.Controller):
:rtype: list
"""
Model = request.env[model]
records = Model.search_read(domain, fields,
offset=offset or 0, limit=limit or False, order=sort or False)
if not records:
return {
'length': 0,
'records': []
}
if limit and len(records) == limit:
length = Model.search_count(domain)
else:
length = len(records) + (offset or 0)
return {
'length': length,
'records': records
}
return Model.web_search_read(domain, fields, offset=offset, limit=limit, order=sort)
@http.route('/web/dataset/load', type='json', auth="user")
def load(self, model, id, fields):
+59 -6
View File
@@ -18,11 +18,41 @@ class Base(models.AbstractModel):
_inherit = 'base'
@api.model
def web_read_group(self, domain, fields, groupby, limit=None, offset=0, orderby=False,
lazy=True):
def web_search_read(self, domain=None, fields=None, offset=0, limit=None, order=None):
"""
Returns the result of a read_group and the total number of groups matching the search
domain.
Performs a search_read and a search_count.
:param domain: search domain
:param fields: list of fields to read
:param limit: maximum number of records to read
:param offset: number of records to skip
:param order: columns to sort results
:return: {
'records': array of read records (result of a call to 'search_read')
'length': number of records matching the domain (result of a call to 'search_count')
}
"""
records = self.search_read(domain, fields, offset=offset, limit=limit, order=order)
if not records:
return {
'length': 0,
'records': []
}
if limit and len(records) == limit:
length = self.search_count(domain)
else:
length = len(records) + offset
return {
'length': length,
'records': records
}
@api.model
def web_read_group(self, domain, fields, groupby, limit=None, offset=0, orderby=False,
lazy=True, expand=False, expand_limit=None, expand_orderby=False):
"""
Returns the result of a read_group (and optionally search for and read records inside each
group), and the total number of groups matching the search domain.
:param domain: search domain
:param fields: list of fields to read (see ``fields``` param of ``read_group``)
@@ -31,13 +61,16 @@ class Base(models.AbstractModel):
:param offset: see ``offset`` param of ``read_group``
:param orderby: see ``orderby`` param of ``read_group``
:param lazy: see ``lazy`` param of ``read_group``
:param expand: if true, and groupby only contains one field, read records inside each group
:param expand_limit: maximum number of records to read in each group
:param expand_orderby: order to apply when reading records in each group
:return: {
'groups': array of read groups
'length': total number of groups
}
"""
groups = self.read_group(domain, fields, groupby, offset=offset, limit=limit,
orderby=orderby, lazy=lazy)
groups = self._web_read_group(domain, fields, groupby, limit, offset, orderby, lazy, expand,
expand_limit, expand_orderby)
if not groups:
length = 0
@@ -51,6 +84,26 @@ class Base(models.AbstractModel):
'length': length
}
@api.model
def _web_read_group(self, domain, fields, groupby, limit=None, offset=0, orderby=False,
lazy=True, expand=False, expand_limit=None, expand_orderby=False):
"""
Performs a read_group and optionally a web_search_read for each group.
See ``web_read_group`` for params description.
:returns: array of groups
"""
groups = self.read_group(domain, fields, groupby, offset=offset, limit=limit,
orderby=orderby, lazy=lazy)
if expand and len(groupby) == 1:
for group in groups:
group['__data'] = self.web_search_read(domain=group['__domain'], fields=fields,
offset=0, limit=expand_limit,
order=expand_orderby)
return groups
@api.model
def read_progress_bar(self, domain, group_by, progress_bar):
"""
+7
View File
@@ -68,6 +68,13 @@ return {
orderBy = options.orderBy || params.orderBy || params.kwargs.orderby;
params.kwargs.orderby = orderBy ? this._serializeSort(orderBy) : orderBy;
params.kwargs.lazy = 'lazy' in options ? options.lazy : params.lazy;
if (options.method === 'web_read_group') {
params.kwargs.expand = options.expand || params.expand || params.kwargs.expand;
params.kwargs.expand_limit = options.expand_limit || params.expand_limit || params.kwargs.expand_limit;
var expandOrderBy = options.expand_orderby || params.expand_orderby || params.kwargs.expand_orderby;
params.kwargs.expand_orderby = expandOrderBy ? this._serializeSort(expandOrderBy) : expandOrderBy;
}
}
if (options.method === 'search_read') {
@@ -4247,6 +4247,7 @@ var BasicModel = AbstractModel.extend({
*/
_readGroup: function (list, options) {
var self = this;
options = options || {};
var groupByField = list.groupedBy[0];
var rawGroupBy = groupByField.split(':')[0];
var fields = _.uniq(list.getFieldNames().concat(rawGroupBy));
@@ -4254,6 +4255,7 @@ var BasicModel = AbstractModel.extend({
return order.name === rawGroupBy || list.fields[order.name].group_operator !== undefined;
});
var openGroupsLimit = list.groupsLimit || self.OPEN_GROUP_LIMIT;
var expand = list.openGroupByDefault && options.fetchRecordsWithGroups;
return this._rpc({
model: list.model,
method: 'web_read_group',
@@ -4265,6 +4267,9 @@ var BasicModel = AbstractModel.extend({
offset: list.groupsOffset,
orderBy: orderedBy,
lazy: true,
expand: expand,
expand_limit: expand ? list.limit : null,
expand_orderby: expand ? list.orderedBy : null,
})
.then(function (result) {
var groups = result.groups;
@@ -4318,7 +4323,7 @@ var BasicModel = AbstractModel.extend({
delete self.localData[newGroup.id];
// restore the internal state of the group
var updatedProps = _.pick(oldGroup, 'isOpen', 'offset', 'id');
if (options && options.onlyGroups || oldGroup.isOpen && newGroup.groupedBy.length) {
if (options.onlyGroups || oldGroup.isOpen && newGroup.groupedBy.length) {
// If the group is opened and contains subgroups,
// also keep its data to keep internal state of
// sub-groups
@@ -4343,11 +4348,16 @@ var BasicModel = AbstractModel.extend({
list.count += newGroup.count;
if (newGroup.isOpen && newGroup.count > 0) {
openGroupCount++;
if (group.__data) {
// bypass the search_read when the group's records have been obtained
// by the call to 'web_read_group' (see @_searchReadUngroupedList)
newGroup.__data = group.__data;
}
options = _.defaults({enableRelationalFetch: false}, options);
defs.push(self._load(newGroup, options));
}
});
if (options && options.keepEmptyGroups) {
if (options.keepEmptyGroups) {
// Find the groups that were available in a previous
// readGroup but are not there anymore.
// Note that these groups are put after existing groups so
@@ -4363,7 +4373,7 @@ var BasicModel = AbstractModel.extend({
}
return Promise.all(defs).then(function (groups) {
if (!options || !options.onlyGroups) {
if (!options.onlyGroups) {
// generate the res_ids of the main list, being the concatenation
// of the fetched res_ids in each group
list.res_ids = _.flatten(_.map(groups, function (group) {
@@ -4552,17 +4562,25 @@ var BasicModel = AbstractModel.extend({
_searchReadUngroupedList: function (list) {
var self = this;
var fieldNames = list.getFieldNames();
return this._rpc({
route: '/web/dataset/search_read',
model: list.model,
fields: fieldNames,
context: list.getContext(),
domain: list.domain || [],
limit: list.limit,
offset: list.loadMoreOffset + list.offset,
orderBy: list.orderedBy,
})
.then(function (result) {
var prom;
if (list.__data) {
// the data have already been fetched (alonside the groups by the
// call to 'web_read_group'), so we can bypass the search_read
prom = Promise.resolve(list.__data);
} else {
prom = this._rpc({
route: '/web/dataset/search_read',
model: list.model,
fields: fieldNames,
context: list.getContext(),
domain: list.domain || [],
limit: list.limit,
offset: list.loadMoreOffset + list.offset,
orderBy: list.orderedBy,
});
}
return prom.then(function (result) {
delete list.__data;
list.count = result.length;
var ids = _.pluck(result.records, 'id');
var data = _.map(result.records, function (record) {
@@ -89,9 +89,11 @@ odoo.define('web.ListModel', function (require) {
* @override
* @private
*/
_readGroup: function (list) {
_readGroup: function (list, options) {
var self = this;
return this._super.apply(this, arguments).then(function (result) {
options = options || {};
options.fetchRecordsWithGroups = true;
return this._super(list, options).then(function (result) {
var prom;
if (!list.parentID) {
// groupbys buttons are only displayed on the first level of
+18 -1
View File
@@ -1209,9 +1209,26 @@ var MockServer = Class.extend({
* @param {boolean} kwargs.lazy still mostly ignored
* @param {integer} [kwargs.limit]
* @param {integer} [kwargs.offset]
* @param {boolean} [kwargs.expand=false] if true, read records inside each
* group
* @param {integer} [kwargs.expand_limit]
* @param {integer} [kwargs.expand_orderby]
* @returns {Object[]}
*/
_mockWebReadGroup: function (model, kwargs) {
var self = this;
var groups = this._mockReadGroup(model, kwargs);
if (kwargs.expand && kwargs.groupby.length === 1) {
groups.forEach(function (group) {
group.__data = self._mockSearchReadController({
domain: group.__domain,
model: model,
fields: kwargs.fields,
limit: kwargs.expand_limit,
order: kwargs.expand_orderby,
});
});
}
var allGroups = this._mockReadGroup(model, {
domain: kwargs.domain,
fields: ['display_name'],
@@ -1219,7 +1236,7 @@ var MockServer = Class.extend({
lazy: kwargs.lazy,
});
return {
groups: this._mockReadGroup(model, kwargs),
groups: groups,
length: allGroups.length,
};
},
+4 -12
View File
@@ -4471,7 +4471,7 @@ QUnit.module('Views', {
});
QUnit.test('grouped list with expand attribute', async function (assert) {
assert.expect(6);
assert.expect(5);
var list = await createView({
View: ListView,
@@ -4487,11 +4487,10 @@ QUnit.module('Views', {
assert.containsN(list, '.o_group_header', 2);
assert.containsN(list, '.o_data_row', 4);
assert.strictEqual(list.$('.o_data_cell').text(), 'yopblipgnapblip');
assert.verifySteps([
'web_read_group',
'/web/dataset/search_read',
'/web/dataset/search_read',
'web_read_group', // records are fetched alongside groups
]);
list.destroy();
@@ -4525,13 +4524,12 @@ QUnit.module('Views', {
});
QUnit.test('grouped lists with expand attribute and a lot of groups', async function (assert) {
assert.expect(10);
assert.expect(8);
for (var i = 0; i < 15; i++) {
this.data.foo.records.push({foo: 'record ' + i, int_field: i});
}
var nbSearchRead = 0;
var list = await createView({
View: ListView,
model: 'foo',
@@ -4542,24 +4540,18 @@ QUnit.module('Views', {
if (args.method === 'web_read_group') {
assert.step(args.method);
}
if (route === '/web/dataset/search_read') {
nbSearchRead++;
}
return this._super.apply(this, arguments);
},
});
assert.containsN(list, '.o_group_header', 10); // page 1
assert.containsN(list, '.o_data_row', 11); // one group contains two records
assert.strictEqual(nbSearchRead, 10);
assert.containsOnce(list, '.o_pager_counter'); // has a pager
nbSearchRead = 0;
await testUtils.dom.click(list.$('.o_pager_next')); // switch to page 2
assert.containsN(list, '.o_group_header', 7); // page 2
assert.containsN(list, '.o_data_row', 7);
assert.strictEqual(nbSearchRead, 7);
assert.verifySteps([
'web_read_group', // read_group page 1