From 72ed3a153a1f4c718fee33ac8550298e8a29f6cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Wed, 26 Jul 2017 11:27:11 +0200 Subject: [PATCH] [FIX] web,pad: record is not considered dirty when pad is loaded The pad widget does something unconventional: it creates the pad url only when rendering in edit mode. This was done to prevent creating pad by default for record in readonly mode. Because of that, if the user clicks on create, then discard, the record was considered dirty, so a confirmation modal popped up. In this commit, we just make sure that the initial setValue from the rendering does not 'dirty' the record. --- addons/pad/static/src/js/pad.js | 2 +- addons/pad/static/tests/pad_tests.js | 36 +++++++++++++++++++ .../static/src/js/fields/abstract_field.js | 6 ++++ .../static/src/js/views/basic/basic_model.js | 25 ++++++++----- .../src/js/views/field_manager_mixin.js | 3 +- 5 files changed, 61 insertions(+), 11 deletions(-) diff --git a/addons/pad/static/src/js/pad.js b/addons/pad/static/src/js/pad.js index 9637072274e..b59f3f0bc04 100644 --- a/addons/pad/static/src/js/pad.js +++ b/addons/pad/static/src/js/pad.js @@ -111,7 +111,7 @@ var FieldPad = AbstractField.extend({ // We need to write the url of the pad to trigger // the write function which updates the actual value // of the field to the value of the pad content - self._setValue(result.url); + self._setValue(result.url, {doNotSetDirty: true}); }); } }, diff --git a/addons/pad/static/tests/pad_tests.js b/addons/pad/static/tests/pad_tests.js index 69342f5de6e..2aa8f7fe1db 100644 --- a/addons/pad/static/tests/pad_tests.js +++ b/addons/pad/static/tests/pad_tests.js @@ -148,4 +148,40 @@ QUnit.module('pad widget', { delete FieldPad.prototype.isPadConfigured; }); + QUnit.test('pad widget is not considered dirty at creation', function (assert) { + assert.expect(2); + + var form = createView({ + View: FormView, + model: 'task', + data: this.data, + arch:'
' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (!args.method) { + return $.when(true); + } + return this._super.apply(this, arguments); + }, + session: { + userName: "batman", + }, + }); + var def = form.canBeDiscarded(); + + assert.strictEqual($('.modal').length, 0, + "should have no confirmation modal opened"); + + assert.strictEqual(def.state(), 'resolved', + "can be discarded was succesfully resolved"); + form.destroy(); + delete FieldPad.prototype.isPadConfigured; + }); + + }); diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js index fa2f37a123f..426d42d664b 100644 --- a/addons/web/static/src/js/fields/abstract_field.js +++ b/addons/web/static/src/js/fields/abstract_field.js @@ -379,6 +379,11 @@ var AbstractField = Widget.extend({ * @private * @param {any} value * @param {Object} [options] + * @param {boolean} [options.doNotSetDirty=false] if true, the basic model + * will not consider that this field is dirty, even though it was changed. + * Please do not use this flag unless you really need it. Our only use + * case is currently the pad widget, which does a _setValue in the + * renderEdit method. * @param {boolean} [options.forceChange=false] if true, the change event will be * triggered even if the new value is the same as the old one * @returns {Deferred} @@ -407,6 +412,7 @@ var AbstractField = Widget.extend({ dataPointID: this.dataPointID, changes: changes, viewType: this.viewType, + doNotSetDirty: options && options.doNotSetDirty, onSuccess: def.resolve.bind(def), onFailure: def.reject.bind(def), }); 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 8f679207f00..73cd2863f21 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -628,12 +628,12 @@ var BasicModel = AbstractModel.extend({ * * @param {string} record_id * @param {Object} changes a map field => new value - * @param {string} [viewType] current viewType. If not set, we will assume - * main viewType from the record + * @param {Object} [options] will be transferred to the applyChange method + * @see _applyChange * @returns {string[]} list of changed fields */ - notifyChanges: function (record_id, changes, viewType) { - return this.mutex.exec(this._applyChange.bind(this, record_id, changes, viewType)); + notifyChanges: function (record_id, changes, options) { + return this.mutex.exec(this._applyChange.bind(this, record_id, changes, options)); }, /** * Reload all data for a given resource @@ -956,23 +956,30 @@ var BasicModel = AbstractModel.extend({ * * @param {string} recordID * @param {Object} changes - * @param {string} [viewType] current viewType. If not set, we will assume + * @param {Object} [options] + * @param {boolean} [options.doNotSetDirty=false] if this flag is set to + * true, then we will not tag the record as dirty. This should be avoided + * for most situations. + * @param {string} [options.viewType] current viewType. If not set, we will assume * main viewType from the record * @returns {Deferred} */ - _applyChange: function (recordID, changes, viewType) { + _applyChange: function (recordID, changes, options) { var self = this; var record = this.localData[recordID]; var field; var defs = []; + options = options || {}; record._changes = record._changes || {}; - record._isDirty = true; + if (!options.doNotSetDirty) { + record._isDirty = true; + } // apply changes to local data for (var fieldName in changes) { field = record.fields[fieldName]; if (field.type === 'one2many' || field.type === 'many2many') { - defs.push(this._applyX2ManyChange(record, fieldName, changes[fieldName], viewType)); + defs.push(this._applyX2ManyChange(record, fieldName, changes[fieldName], options.viewType)); } else if (field.type === 'many2one' || field.type === 'reference') { defs.push(this._applyX2OneChange(record, fieldName, changes[fieldName])); } else { @@ -993,7 +1000,7 @@ var BasicModel = AbstractModel.extend({ } var onchangeDef; if (onChangeFields.length) { - onchangeDef = self._performOnChange(record, onChangeFields, viewType).then(function (result) { + onchangeDef = self._performOnChange(record, onChangeFields, options.viewType).then(function (result) { delete record._warning; return _.keys(changes).concat(Object.keys(result && result.value || {})); }); diff --git a/addons/web/static/src/js/views/field_manager_mixin.js b/addons/web/static/src/js/views/field_manager_mixin.js index 422af121d96..b4bf1ddbf94 100644 --- a/addons/web/static/src/js/views/field_manager_mixin.js +++ b/addons/web/static/src/js/views/field_manager_mixin.js @@ -43,7 +43,8 @@ var FieldManagerMixin = { */ _applyChanges: function (dataPointID, changes, event) { var self = this; - return this.model.notifyChanges(dataPointID, changes, event.data.viewType) + var options = _.pick(event.data, 'viewType', 'doNotSetDirty'); + return this.model.notifyChanges(dataPointID, changes, options) .then(function (result) { if (event.data.force_save) { return self.model.save(dataPointID).then(function () {