diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 90854bc577e..b8970a2ec89 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -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 diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index 023ca593b19..6d8909ff2c3 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -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: '
', + 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: '', + 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: '', + 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); + }); }); + }); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index c84b8351990..cfe00bf9141 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -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: '