[IMP] web: invalidate JS cache for some models

Models concerned are 'ir.ui.view', 'ir.actions.act_window' and
'ir.filters'.

The DataManager's cache is invalidated at each create, update and
unlink operation. This cache stores actions (results of calls to
/web/action/load route), views (results of load_views RPCs) and
filters (fetched alonside views).

This rev. thus spares the user from reloading the webclient after
performing such operations on these models to see the changes in
the interface, as he only has to replay the action to see the
changes.
This commit is contained in:
Aaron Bohy
2018-02-07 08:49:58 +01:00
parent 20f2de8296
commit 7937347d75
3 changed files with 199 additions and 6 deletions
@@ -134,6 +134,14 @@ var x2ManyCommands = {
};
var BasicModel = AbstractModel.extend({
// list of models for which the DataManager's cache should be cleared on
// create, update and delete operations
noCacheModels: [
'ir.actions.act_window',
'ir.filters',
'ir.ui.view',
],
/**
* @override
*/
@@ -248,6 +256,8 @@ var BasicModel = AbstractModel.extend({
record.count--;
}
});
// optionally clear the DataManager's cache
self._invalidateCache(records[0]);
});
},
/**
@@ -909,11 +919,14 @@ var BasicModel = AbstractModel.extend({
// Erase changes as they have been applied
record._changes = {};
// Optionally clear the DataManager's cache
self._invalidateCache(record);
self.unfreezeOrder(record.id);
// Update the data directly or reload them
if (shouldReload) {
self._fetchRecord(record).then(function (record) {
self._fetchRecord(record).then(function () {
def.resolve(changedFields);
});
} else {
@@ -1023,8 +1036,11 @@ var BasicModel = AbstractModel.extend({
method: 'write',
args: [resIDs, { active: value }],
})
.then(this.reload.bind(this, parentID));
.then(function () {
// optionally clear the DataManager's cache
self._invalidateCache(parent);
return self.reload(parentID);
});
},
/**
* Toggle (open/close) a group in a grouped list, then fetches relevant
@@ -2937,6 +2953,21 @@ var BasicModel = AbstractModel.extend({
}
return context;
},
/**
* Invalidates the DataManager's cache if the main model (i.e. the model of
* its root parent) of the given dataPoint is a model in 'noCacheModels'.
*
* @private
* @param {Object} dataPoint
*/
_invalidateCache: function (dataPoint) {
while (dataPoint.parentID) {
dataPoint = this.localData[dataPoint.parentID];
}
if (_.contains(this.noCacheModels, dataPoint.model)) {
core.bus.trigger('clear_cache');
}
},
/**
* Returns true if the field is protected against changes, looking for a
* readonly modifier unless there is a force_save modifier (checking first
+110 -1
View File
@@ -1,13 +1,13 @@
odoo.define('web.form_tests', function (require) {
"use strict";
var BasicModel = require('web.BasicModel');
var concurrency = require('web.concurrency');
var config = require('web.config');
var core = require('web.core');
var fieldRegistry = require('web.field_registry');
var FormView = require('web.FormView');
var pyeval = require('web.pyeval');
var RainbowMan = require('web.RainbowMan');
var testUtils = require('web.test_utils');
var widgetRegistry = require('web.widget_registry');
var Widget = require('web.Widget');
@@ -6338,5 +6338,114 @@ QUnit.module('Views', {
form.destroy();
});
QUnit.test('edition in form view on a "noCache" model', function (assert) {
assert.expect(4);
testUtils.patch(BasicModel, {
noCacheModels: BasicModel.prototype.noCacheModels.concat(['partner']),
});
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<sheet>' +
'<field name="display_name"/>' +
'</sheet>' +
'</form>',
res_id: 1,
viewOptions: {
mode: 'edit',
},
mockRPC: function (route, args) {
if (args.method === 'write') {
assert.step('write');
}
return this._super.apply(this, arguments);
},
});
core.bus.on('clear_cache', form, assert.step.bind(assert, 'clear_cache'));
form.$('.o_field_widget[name=display_name]').val('new value').trigger('input');
form.$buttons.find('.o_form_button_save').click();
assert.verifySteps(['write', 'clear_cache']);
form.destroy();
testUtils.unpatch(BasicModel);
});
QUnit.test('creation in form view on a "noCache" model', function (assert) {
assert.expect(4);
testUtils.patch(BasicModel, {
noCacheModels: BasicModel.prototype.noCacheModels.concat(['partner']),
});
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<sheet>' +
'<field name="display_name"/>' +
'</sheet>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'create') {
assert.step('create');
}
return this._super.apply(this, arguments);
},
});
core.bus.on('clear_cache', form, assert.step.bind(assert, 'clear_cache'));
form.$('.o_field_widget[name=display_name]').val('value').trigger('input');
form.$buttons.find('.o_form_button_save').click();
assert.verifySteps(['create', 'clear_cache']);
form.destroy();
testUtils.unpatch(BasicModel);
});
QUnit.test('deletion in form view on a "noCache" model', function (assert) {
assert.expect(4);
testUtils.patch(BasicModel, {
noCacheModels: BasicModel.prototype.noCacheModels.concat(['partner']),
});
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<sheet>' +
'<field name="display_name"/>' +
'</sheet>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'unlink') {
assert.step('unlink');
}
return this._super.apply(this, arguments);
},
viewOptions: {
hasSidebar: true,
},
});
core.bus.on('clear_cache', form, assert.step.bind(assert, 'clear_cache'));
form.sidebar.$('a:contains(Delete)').click();
$('.modal .modal-footer .btn-primary').click(); // confirm
assert.verifySteps(['unlink', 'clear_cache']);
form.destroy();
testUtils.unpatch(BasicModel);
});
});
});
+55 -2
View File
@@ -1,7 +1,9 @@
odoo.define('web.list_tests', function (require) {
"use strict";
var BasicModel = require('web.BasicModel');
var config = require('web.config');
var core = require('web.core');
var basicFields = require('web.basic_fields');
var FormView = require('web.FormView');
var ListView = require('web.ListView');
@@ -2351,7 +2353,6 @@ QUnit.module('Views', {
list.destroy();
});
QUnit.test('navigation with tab on a one2many list with create="0"', function (assert) {
assert.expect(4);
@@ -3172,7 +3173,6 @@ QUnit.module('Views', {
list.destroy();
});
QUnit.test('basic support for widgets', function (assert) {
assert.expect(1);
@@ -3223,6 +3223,59 @@ QUnit.module('Views', {
list.destroy();
});
QUnit.test('list view on a "noCache" model', function (assert) {
assert.expect(8);
testUtils.patch(BasicModel, {
noCacheModels: BasicModel.prototype.noCacheModels.concat(['foo']),
});
var list = createView({
View: ListView,
model: 'foo',
data: this.data,
arch: '<tree editable="top">' +
'<field name="display_name"/>' +
'</tree>',
mockRPC: function (route, args) {
if (_.contains(['create', 'unlink', 'write'], args.method)) {
assert.step(args.method);
}
return this._super.apply(this, arguments);
},
viewOptions: {
hasSidebar: true,
},
});
core.bus.on('clear_cache', list, assert.step.bind(assert, 'clear_cache'));
// create a new record
list.$buttons.find('.o_list_button_add').click();
list.$('.o_selected_row .o_field_widget').val('some value').trigger('input');
list.$buttons.find('.o_list_button_save').click();
// edit an existing record
list.$('.o_data_cell:first').click();
list.$('.o_selected_row .o_field_widget').val('new value').trigger('input');
list.$buttons.find('.o_list_button_save').click();
// delete a record
list.$('.o_data_row:first .o_list_record_selector input').click();
list.sidebar.$('a:contains(Delete)').click();
$('.modal .modal-footer .btn-primary').click(); // confirm
assert.verifySteps([
'create',
'clear_cache',
'write',
'clear_cache',
'unlink',
'clear_cache',
]);
list.destroy();
testUtils.unpatch(BasicModel);
});
});
});