[FIX] web: o2m: save updates of one2many records

With this commit, updates of existing o2m records are now
correctly saved in DB when clicking on 'Save' in the main
form view.
This commit is contained in:
Aaron Bohy
2017-04-11 19:44:38 +02:00
parent 97b4da489d
commit 1fadf4a793
4 changed files with 52 additions and 32 deletions
@@ -201,7 +201,7 @@ var BasicModel = AbstractModel.extend({
})
.then(function () {
_.each(records, function (record) {
record.res_ids.splice(record.offset, 1)[0];
record.res_ids.splice(record.offset, 1);
record.res_id = record.res_ids[record.offset];
record.count--;
});
@@ -215,11 +215,15 @@ var BasicModel = AbstractModel.extend({
* everything that was stored in a _changes key.
*
* @param {string} id local resource id
* @param {Object} [options]
* @param {boolean} [options.rollback=false] if true, the changes will
* be reset to the last _savePoint, otherwise, they are reset to null
*/
discardChanges: function (id) {
discardChanges: function (id, options) {
options = options || {};
var element = this.localData[id];
this._visitChildren(element, function (elem) {
elem._changes = null;
elem._changes = options.rollback && elem._savePoint || null;
});
},
/**
@@ -550,9 +554,9 @@ var BasicModel = AbstractModel.extend({
* @param {string} record_id local resource
* @param {Object} [options]
* @param {boolean} [options.reload=true] if true, data will be reloaded
* @param {boolean} [options.localSave=false] if true, the record will only
* be 'locally' saved: its changes will move from the _changes key to the
* data key
* @param {boolean} [options.savePoint=false] if true, the record will only
* be 'locally' saved: its changes written in a _savePoint key that can
* be restored later by call discardChanges with option rollback to true
* @returns {Deferred}
*/
save: function (record_id, options) {
@@ -560,10 +564,9 @@ var BasicModel = AbstractModel.extend({
return this.mutex.exec(function () {
options = options || {};
var record = self.localData[record_id];
if (options.localSave) {
if (options.savePoint) {
if (record._changes) {
_.extend(record.data, record._changes);
record._changes = null;
record._savePoint = _.extend(record._savePoint || {}, record._changes);
}
return $.when();
}
@@ -735,7 +738,7 @@ var BasicModel = AbstractModel.extend({
* notifyChanges, it is not protected by a mutex. Every changes from the
* user to the model go through this method.
*
* @param {string} recordID
* @param {string} recordID
* @param {Object} changes
* @returns {Deferred}
*/
@@ -1647,25 +1650,25 @@ var BasicModel = AbstractModel.extend({
addedIds = _.difference(relIds, list.res_ids);
keptIds = _.intersection(list.res_ids, relIds);
for (i = 0; i < keptIds.length; i++) {
commands[fieldName].push(x2ManyCommands.link_to(keptIds[i]));
}
for (i = 0; i < removedIds.length; i++) {
commands[fieldName].push(x2ManyCommands.delete(removedIds[i]));
relRecord = _.findWhere(relData, {res_id: keptIds[i]});
var command;
if (!_.isEmpty(relRecord._changes)) {
var r = this.get(relRecord.id, {raw: true});
var changes = _.pick(r.data, Object.keys(relRecord._changes));
command = x2ManyCommands.update(relRecord.res_id, changes);
} else {
command = x2ManyCommands.link_to(keptIds[i]);
}
commands[fieldName].push(command);
}
for (i = 0; i < addedIds.length; i++) {
relRecord = _.findWhere(relData, {res_id: addedIds[i]});
relRecord = this.get(relRecord.id, {raw: true});
commands[fieldName].push(x2ManyCommands.create(_.omit(relRecord.data, 'id')));
}
// FIXME: update of records temporarily disabled as it doesn't work if we updated
// the record in a form view, as the _changes contains all the fields of the
// form view instead of the one that have changed
// _.each(relData, function (relRecord) {
// var new_values = relRecord._changes;
// if (!_.isEmpty(new_values)) {
// commands[fieldName].push(x2ManyCommands.update(relRecord.res_id, new_values));
// }
// });
for (i = 0; i < removedIds.length; i++) {
commands[fieldName].push(x2ManyCommands.delete(removedIds[i]));
}
}
}
}
@@ -209,7 +209,7 @@ var FormController = BasicController.extend({
* readonly mode after saving the record
* @param {boolean} [options.reload=true] if true, reload the record after
* saving
* @param {boolean} [options.localSave=false] if true, the record will only
* @param {boolean} [options.savePoint=false] if true, the record will only
* be 'locally' saved: its changes will move from the _changes key to the
* data key
* @returns {Deferred}
@@ -228,7 +228,7 @@ var FormController = BasicController.extend({
if (this.checkInvalidFields()) {
return $.Deferred().reject();
} else {
return this.model.save(this.handle, {reload: shouldReload, localSave: options.localSave})
return this.model.save(this.handle, {reload: shouldReload, savePoint: options.savePoint})
.then(function () {
if (!stayInEdit) {
self._toReadOnlyMode();
@@ -109,7 +109,9 @@ var FormViewDialog = ViewDialog.extend({
close: true,
click: function () {
if (!readonly) {
self.form_view.model.discardChanges(self.form_view.handle);
self.form_view.model.discardChanges(self.form_view.handle, {
rollback: self.shouldSaveLocally,
});
}
},
}];
@@ -180,7 +182,7 @@ var FormViewDialog = ViewDialog.extend({
self.form_view = formView;
var fragment = document.createDocumentFragment();
if (self.recordID && self.shouldSaveLocally) {
self.model.save(self.recordID, {localSave: true});
self.model.save(self.recordID, {savePoint: true});
}
self.form_view.appendTo(fragment)
.then(function () {
@@ -213,7 +215,7 @@ var FormViewDialog = ViewDialog.extend({
def = this.form_view.saveRecord({
stayInEdit: true,
reload: false,
localSave: this.shouldSaveLocally,
savePoint: this.shouldSaveLocally,
});
}
return $.when(def).then(function () {
@@ -1053,8 +1053,9 @@ QUnit.module('relational_fields', {
});
QUnit.test('one2many list (non editable): edition', function (assert) {
assert.expect(9);
assert.expect(12);
var nbWrite = 0;
this.data.partner.records[0].p = [2, 4];
var form = createView({
View: FormView,
@@ -1071,6 +1072,15 @@ QUnit.module('relational_fields', {
'</field>' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
if (args.method === 'write') {
nbWrite++;
assert.deepEqual(args.args[1], {
p: [[1, 2, {display_name: 'new name'}], [2, 4, false]]
}, "should have sent the correct commands");
}
return this._super.apply(this, arguments);
},
});
assert.ok(!form.$('.o_list_record_delete').length,
@@ -1096,16 +1106,21 @@ QUnit.module('relational_fields', {
$('.modal .modal-footer .btn-primary').click(); // save
assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name',
'value of subrecord should have been updated');
assert.strictEqual(nbWrite, 0, "should not have write anything in DB");
// create new subrecords
// TODO when 'Add an item' will be implemented
// delete subrecords
form.$('.o_list_record_delete:first()').click();
form.$('.o_list_record_delete:nth(1)').click();
assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 1,
'should contain 1 subrecord');
assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'aaa',
'the remaining subrecord should be "aaa"');
assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name',
'the remaining subrecord should be "new name"');
form.$buttons.find('.o_form_button_save').click(); // save the record
assert.strictEqual(nbWrite, 1, "should have write the changes in DB");
form.destroy();
});