Skip to content
Permalink
Browse files

[IMP] web,board: enable pagination in grouped lists

With this rev., when there are a lot of groups in a grouped list
view, groups are displayed under several pages, whereas they were
all displayed in the same page before.

This is especially interesting with the new 'expand' attribute, to
ensure that we don't read records for a large number of groups.

By default, the groups limit is set to 80 (like records), and to 10
is the 'expand' attribute is set to true. This limit can be
overriden with the 'groups_limit' attribute.

Part of task 1915702
  • Loading branch information...
aab-odoo committed Mar 13, 2019
1 parent 5895d23 commit 2be28d4d033b5930e5a16ff02407315a116e9043
@@ -841,7 +841,7 @@ QUnit.test("Dashboard should use correct groupby", async function (assert) {
'</board>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'read_group') {
if (args.method === 'web_read_group') {
assert.deepEqual(args.kwargs.groupby, ['bar'],
'user defined groupby should have precedence on action groupby');
}
@@ -17,6 +17,40 @@ class IrActionsActWindowView(models.Model):
class Base(models.AbstractModel):
_inherit = 'base'

@api.model
def web_read_group(self, domain, fields, groupby, limit=None, offset=0, orderby=False,
lazy=True):
"""
Returns the result of a read_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``)
:param groupby: list of fields to group on (see ``groupby``` param of ``read_group``)
:param limit: see ``limit`` param of ``read_group``
:param offset: see ``offset`` param of ``read_group``
:param orderby: see ``orderby`` param of ``read_group``
:param lazy: see ``lazy`` param of ``read_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)

if not groups:
length = 0
elif limit and len(groups) == limit:
all_groups = self.read_group(domain, ['display_name'], groupby, lazy=True)
length = len(all_groups)
else:
length = len(groups) + offset
return {
'groups': groups,
'length': length
}

@api.model
def read_progress_bar(self, domain, group_by, progress_bar):
"""
@@ -37,6 +37,7 @@ return {
buildQuery: function (options) {
var route;
var params = options.params || {};
var orderBy;
if (options.route) {
route = options.route;
} else if (options.model && options.method) {
@@ -50,7 +51,7 @@ return {
params.kwargs.context = options.context || params.context || params.kwargs.context;
}

if (options.method === 'read_group') {
if (options.method === 'read_group' || options.method === 'web_read_group') {
if (!(params.args && params.args[0] !== undefined)) {
params.kwargs.domain = options.domain || params.domain || params.kwargs.domain || [];
}
@@ -64,7 +65,7 @@ return {
params.kwargs.limit = options.limit || params.limit || params.kwargs.limit;
// In kwargs, we look for "orderby" rather than "orderBy" (note the absence of capital B),
// since the Python argument to the actual function is "orderby".
var orderBy = options.orderBy || params.orderBy || params.kwargs.orderby;
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;
}
@@ -77,7 +78,7 @@ return {
params.kwargs.limit = options.limit || params.limit || params.kwargs.limit;
// In kwargs, we look for "order" rather than "orderBy" since the Python
// argument to the actual function is "order".
var orderBy = options.orderBy || params.orderBy || params.kwargs.order;
orderBy = options.orderBy || params.orderBy || params.kwargs.order;
params.kwargs.order = orderBy ? this._serializeSort(orderBy) : orderBy;
}

@@ -88,7 +89,7 @@ return {
params.fields = options.fields || params.fields;
params.limit = options.limit || params.limit;
params.offset = options.offset || params.offset;
var orderBy = options.orderBy || params.orderBy;
orderBy = options.orderBy || params.orderBy;
params.sort = orderBy ? this._serializeSort(orderBy) : orderBy;
params.context = options.context || params.context || {};
}
@@ -148,25 +148,28 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
*/
renderPager: function ($node, options) {
var self = this;
var data = this.model.get(this.handle, {raw: true});
this.pager = new Pager(this, data.count, data.offset + 1, data.limit, options);
var params = this._getPagerParams();
this.pager = new Pager(this, params.size, params.current_min, params.limit, options);

this.pager.on('pager_changed', this, function (newState) {
var self = this;
this.pager.disable();
data = this.model.get(this.handle, {raw: true});
var data = this.model.get(this.handle, {raw: true});
var limitChanged = (data.limit !== newState.limit);
this.reload({limit: newState.limit, offset: newState.current_min - 1})
.then(function () {
// Reset the scroll position to the top on page changed only
if (!limitChanged) {
self.trigger_up('scrollTo', {top: 0});
}
})
.then(this.pager.enable.bind(this.pager));
var reloadParams;
if (data.groupedBy && data.groupedBy.length) {
reloadParams = {groupsLimit: newState.limit, groupsOffset: newState.current_min - 1};
} else {
reloadParams = {limit: newState.limit, offset: newState.current_min - 1};
}
this.reload(reloadParams).then(function () {
// reset the scroll position to the top on page changed only
if (!limitChanged) {
self.trigger_up('scrollTo', {top: 0});
}
}).then(this.pager.enable.bind(this.pager));
});
return this.pager.appendTo($node).then(function() {
self._updatePager(); // to force proper visibility
return this.pager.appendTo($node).then(function () {
self._updatePager(); // to force proper visibility
});
},
/**
@@ -412,6 +415,22 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
resIds: env.ids,
});
},
/**
* Return the params (current_min, limit and size) to pass to the pager,
* according to the current state.
*
* @private
* @returns {Object}
*/
_getPagerParams: function () {
var state = this.model.get(this.handle, {raw: true});
var isGrouped = state.groupedBy && state.groupedBy.length;
return {
current_min: (isGrouped ? state.groupsOffset : state.offset) + 1,
limit: isGrouped ? state.groupsLimit : state.limit,
size: isGrouped ? state.groupsCount : state.count,
};
},
/**
* Returns the new sidebar env
*
@@ -425,6 +444,16 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
model: this.modelName,
};
},
/**
* Determine whether or not the pager must be displayed (probably depending
* on the current state). Controllers must override this to implement their
* own logic.
*
* @private
*/
_isPagerVisible: function () {
return true;
},
/**
* Helper function to display a warning that some fields have an invalid
* value. This is used when a save operation cannot be completed.
@@ -533,23 +562,14 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
}
},
/**
* Helper method, to make sure the information displayed by the pager is up
* to date.
* Update the pager with the current state.
*
* @private
*/
_updatePager: function () {
if (this.pager) {
var data = this.model.get(this.handle, {raw: true});
this.pager.updateState({
current_min: data.offset + 1,
size: data.count,
});
var isRecord = data.type === 'record';
var hasData = !!data.count;
var isGrouped = data.groupedBy ? !!data.groupedBy.length : false;
var isNew = this.model.isNew(this.handle);
var isPagerVisible = isRecord ? !isNew : (hasData && !isGrouped);

this.pager.do_toggle(isPagerVisible);
this.pager.updateState(this._getPagerParams());
this.pager.do_toggle(this._isPagerVisible());
}
},

@@ -88,7 +88,6 @@ var concurrency = require('web.concurrency');
var Context = require('web.Context');
var core = require('web.core');
var Domain = require('web.Domain');
var fieldUtils = require('web.field_utils');
var session = require('web.session');
var utils = require('web.utils');
var viewUtils = require('web.viewUtils');
@@ -639,6 +638,9 @@ var BasicModel = AbstractModel.extend({
getDomain: element.getDomain,
getFieldNames: element.getFieldNames,
groupedBy: element.groupedBy,
groupsCount: element.groupsCount,
groupsLimit: element.groupsLimit,
groupsOffset: element.groupsOffset,
id: element.id,
isDirty: element.isDirty,
isOpen: element.isOpen,
@@ -3732,6 +3734,9 @@ var BasicModel = AbstractModel.extend({
fields: fields,
fieldsInfo: params.fieldsInfo,
groupedBy: params.groupedBy || [],
groupsCount: 0,
groupsLimit: type === 'list' && params.groupsLimit || null,
groupsOffset: 0,
id: _.uniqueId(params.modelName + '_'),
isOpen: params.isOpen,
limit: type === 'record' ? 1 : params.limit,
@@ -4245,20 +4250,25 @@ var BasicModel = AbstractModel.extend({
var groupByField = list.groupedBy[0];
var rawGroupBy = groupByField.split(':')[0];
var fields = _.uniq(list.getFieldNames().concat(rawGroupBy));
var orderedBy = _.filter(list.orderedBy, function(order){
var orderedBy = _.filter(list.orderedBy, function (order) {
return order.name === rawGroupBy || list.fields[order.name].group_operator !== undefined;
});
var openGroupsLimit = list.groupsLimit || self.OPEN_GROUP_LIMIT;
return this._rpc({
model: list.model,
method: 'read_group',
method: 'web_read_group',
fields: fields,
domain: list.domain,
context: list.context,
groupBy: list.groupedBy,
limit: list.groupsLimit,
offset: list.groupsOffset,
orderBy: orderedBy,
lazy: true,
})
.then(function (groups) {
.then(function (result) {
var groups = result.groups;
list.groupsCount = result.length;
var previousGroups = _.map(list.data, function (groupID) {
return self.localData[groupID];
});
@@ -4321,7 +4331,7 @@ var BasicModel = AbstractModel.extend({
// form view) are reloaded
newGroup.limit = oldGroup.limit + oldGroup.loadMoreOffset;
self.localData[newGroup.id] = newGroup;
} else if (!newGroup.openGroupByDefault || openGroupCount >= self.OPEN_GROUP_LIMIT) {
} else if (!newGroup.openGroupByDefault || openGroupCount >= openGroupsLimit) {
newGroup.isOpen = false;
} else if ('__fold' in group) {
newGroup.isOpen = !group.__fold;
@@ -4486,6 +4496,12 @@ var BasicModel = AbstractModel.extend({
if (options.offset !== undefined) {
this._setOffset(element.id, options.offset);
}
if (options.groupsLimit !== undefined) {
element.groupsLimit = options.groupsLimit;
}
if (options.groupsOffset !== undefined) {
element.groupsOffset = options.groupsOffset;
}
if (options.loadMoreOffset !== undefined) {
element.loadMoreOffset = options.loadMoreOffset;
} else {
@@ -347,6 +347,15 @@ var FormController = BasicController.extend({
this._super.apply(this, arguments);
this.renderer.enableButtons();
},
/**
* Only display the pager if we are not on a new record.
*
* @override
* @private
*/
_isPagerVisible: function () {
return !this.model.isNew(this.handle);
},
/**
* Hook method, called when record(s) has been deleted.
*
@@ -137,6 +137,16 @@ var KanbanController = BasicController.extend({
}
return this.renderer.updateRecord(this.model.get(id));
},
/**
* Only display the pager in the ungrouped case, with data.
*
* @override
* @private
*/
_isPagerVisible: function () {
var state = this.model.get(this.handle, {raw: true});
return !!(state.count && !state.groupedBy.length);
},
/**
* @private
* @param {Widget} kanbanRecord
@@ -11,7 +11,6 @@ var core = require('web.core');
var BasicController = require('web.BasicController');
var DataExport = require('web.DataExport');
var Dialog = require('web.Dialog');
var pyUtils = require('web.py_utils');
var Sidebar = require('web.Sidebar');

var _t = core._t;
@@ -342,6 +341,16 @@ var ListController = BasicController.extend({
var record = this.model.get(this.handle);
return _.extend(env, {domain: record.getDomain()});
},
/**
* Only display the pager when there are data to display.
*
* @override
* @private
*/
_isPagerVisible: function () {
var state = this.model.get(this.handle, {raw: true});
return !!state.count;
},
/**
* Allows to change the mode of a single row.
*
@@ -395,11 +404,9 @@ var ListController = BasicController.extend({
* @returns {Promise}
*/
_update: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
self._toggleSidebar();
self._toggleCreateButton();
});
return this._super.apply(this, arguments)
.then(this._toggleSidebar.bind(this))
.then(this._toggleCreateButton.bind(this));
},
/**
* This helper simply makes sure that the control panel buttons matches the
@@ -38,6 +38,7 @@ var ListView = BasicView.extend({
var selectedRecords = []; // there is no selected records by default

var mode = this.arch.attrs.editable && !params.readonly ? "edit" : "readonly";
var expandGroups = !!JSON.parse(this.arch.attrs.expand || "0");

this.controllerParams.editable = this.arch.attrs.editable;
this.controllerParams.hasSidebar = params.hasSidebar;
@@ -54,8 +55,10 @@ var ListView = BasicView.extend({
this.rendererParams.addCreateLineInGroups = this.rendererParams.editable && this.controllerParams.activeActions.create;

this.loadParams.limit = this.loadParams.limit || 80;
this.loadParams.openGroupByDefault = !!JSON.parse(this.arch.attrs.expand || "0");
this.loadParams.openGroupByDefault = expandGroups;
this.loadParams.type = 'list';
var groupsLimit = parseInt(this.arch.attrs.groups_limit, 10);
this.loadParams.groupsLimit = groupsLimit || (expandGroups ? 10 : 80);
},

//--------------------------------------------------------------------------

0 comments on commit 2be28d4

Please sign in to comment.
You can’t perform that action at this time.