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 () {