[FIX] web: Many2one: wait for name_create before saving
The problem occurred in form views with a many2one field, when the user reproduced the following steps: - type some text in the many2one - click on 'Create ...' in the many2one dropdown to quick create (name_create RPC) the record - before the name_create returns, click on 'Save'. When this happened, the record was saved before the many2one was updated with its new value. So the record was incorrectly saved, and the view was again dirty as soon as the name_create returned, even if it was in 'readonly'. So after saving, a 'Changes will be discarded' dialog opened when the user tried to leave the form. We fix this issue by making the controller wait for the name_create to return, and for the many2one to update its value, before saving. This issue could also occur in list views.
This commit is contained in:
@@ -213,17 +213,20 @@ var FieldMany2One = AbstractField.extend({
|
||||
var self = this;
|
||||
var slowCreate = this._searchCreatePopup.bind(this, "form", false, this._createContext(name));
|
||||
if (this.nodeOptions.quick_create) {
|
||||
return this._rpc({
|
||||
model: this.field.relation,
|
||||
method: 'name_create',
|
||||
args: [name],
|
||||
context: this.record.getContext(this.recordParams),
|
||||
})
|
||||
.then(function (result) {
|
||||
if (self.mode === "edit") {
|
||||
self.reinitialize({id: result[0], display_name: result[1]});
|
||||
}
|
||||
}, slowCreate);
|
||||
this.trigger_up('mutexify', {
|
||||
action: function () {
|
||||
return self._rpc({
|
||||
model: self.field.relation,
|
||||
method: 'name_create',
|
||||
args: [name],
|
||||
context: self.record.getContext(self.recordParams),
|
||||
}).then(function (result) {
|
||||
if (self.mode === "edit") {
|
||||
self.reinitialize({id: result[0], display_name: result[1]});
|
||||
}
|
||||
}, slowCreate);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
slowCreate();
|
||||
}
|
||||
|
||||
@@ -186,8 +186,12 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
// user if some discarding has to be made). This operation must also be
|
||||
// mutex-protected as commitChanges function of x2m has to be aware of
|
||||
// all final changes made to a row.
|
||||
this.mutex.exec(this.renderer.commitChanges.bind(this.renderer, recordID || this.handle)); // TODO write a test for this
|
||||
return this.mutex.exec(this._saveRecord.bind(this, recordID, options));
|
||||
var self = this;
|
||||
return this.mutex
|
||||
.exec(this.renderer.commitChanges.bind(this.renderer, recordID || this.handle))
|
||||
.then(function () {
|
||||
return self.mutex.exec(self._saveRecord.bind(self, recordID, options));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
|
||||
@@ -482,6 +482,183 @@ QUnit.module('relational_fields', {
|
||||
// assert.strictEqual(form.$('input').eq(1).val(), 'xphone', "onchange should have been applied");
|
||||
// });
|
||||
|
||||
QUnit.test('form: quick create then save directly', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(5);
|
||||
|
||||
var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY;
|
||||
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0;
|
||||
|
||||
var def = $.Deferred();
|
||||
var newRecordID;
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<field name="trululu"/>' +
|
||||
'</form>',
|
||||
mockRPC: function (route, args) {
|
||||
var result = this._super.apply(this, arguments);
|
||||
if (args.method === 'name_create') {
|
||||
assert.step('name_create');
|
||||
return def.then(_.constant(result)).then(function (nameGet) {
|
||||
newRecordID = nameGet[0];
|
||||
return nameGet;
|
||||
});
|
||||
}
|
||||
if (args.method === 'create') {
|
||||
assert.step('create');
|
||||
assert.strictEqual(args.args[0].trululu, newRecordID,
|
||||
"should create with the correct m2o id");
|
||||
}
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
var $dropdown = form.$('.o_field_many2one input').autocomplete('widget');
|
||||
form.$('.o_field_many2one input').val('b').trigger('keydown');
|
||||
concurrency.delay(0).then(function () {
|
||||
$dropdown.find('li:first()').click(); // quick create 'b'
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
|
||||
assert.verifySteps(['name_create'],
|
||||
"should wait for the name_create before creating the record");
|
||||
|
||||
def.resolve();
|
||||
|
||||
assert.verifySteps(['name_create', 'create']);
|
||||
|
||||
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY;
|
||||
form.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('list: quick create then save directly', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(8);
|
||||
|
||||
var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY;
|
||||
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0;
|
||||
|
||||
var def = $.Deferred();
|
||||
var newRecordID;
|
||||
var list = createView({
|
||||
View: ListView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<tree editable="top">' +
|
||||
'<field name="trululu"/>' +
|
||||
'</tree>',
|
||||
mockRPC: function (route, args) {
|
||||
var result = this._super.apply(this, arguments);
|
||||
if (args.method === 'name_create') {
|
||||
assert.step('name_create');
|
||||
return def.then(_.constant(result)).then(function (nameGet) {
|
||||
newRecordID = nameGet[0];
|
||||
return nameGet;
|
||||
});
|
||||
}
|
||||
if (args.method === 'create') {
|
||||
assert.step('create');
|
||||
assert.strictEqual(args.args[0].trululu, newRecordID,
|
||||
"should create with the correct m2o id");
|
||||
}
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
list.$buttons.find('.o_list_button_add').click();
|
||||
|
||||
var $dropdown = list.$('.o_field_many2one input').autocomplete('widget');
|
||||
list.$('.o_field_many2one input').val('b').trigger('keydown');
|
||||
concurrency.delay(0).then(function () {
|
||||
$dropdown.find('li:first()').click(); // quick create 'b'
|
||||
list.$buttons.find('.o_list_button_add').click();
|
||||
|
||||
assert.verifySteps(['name_create'],
|
||||
"should wait for the name_create before creating the record");
|
||||
assert.strictEqual(list.$('.o_data_row').length, 4,
|
||||
"should wait for the name_create before adding the new row");
|
||||
|
||||
def.resolve();
|
||||
|
||||
assert.verifySteps(['name_create', 'create']);
|
||||
assert.strictEqual(list.$('.o_data_row:nth(1) .o_data_cell').text(), 'b',
|
||||
"created row should have the correct m2o value");
|
||||
assert.strictEqual(list.$('.o_data_row').length, 5,
|
||||
"should have added the fifth row");
|
||||
|
||||
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY;
|
||||
list.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('list in form: quick create then save directly', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(6);
|
||||
|
||||
var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY;
|
||||
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0;
|
||||
|
||||
var def = $.Deferred();
|
||||
var newRecordID;
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<sheet>' +
|
||||
'<field name="p">' +
|
||||
'<tree editable="bottom">' +
|
||||
'<field name="trululu"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
mockRPC: function (route, args) {
|
||||
var result = this._super.apply(this, arguments);
|
||||
if (args.method === 'name_create') {
|
||||
assert.step('name_create');
|
||||
return def.then(_.constant(result)).then(function (nameGet) {
|
||||
newRecordID = nameGet[0];
|
||||
return nameGet;
|
||||
});
|
||||
}
|
||||
if (args.method === 'create') {
|
||||
assert.step('create');
|
||||
assert.strictEqual(args.args[0].p[0][2].trululu, newRecordID,
|
||||
"should create with the correct m2o id");
|
||||
}
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
form.$('.o_field_x2many_list_row_add a').click();
|
||||
|
||||
var $dropdown = form.$('.o_field_many2one input').autocomplete('widget');
|
||||
form.$('.o_field_many2one input').val('b').trigger('keydown');
|
||||
concurrency.delay(0).then(function () {
|
||||
$dropdown.find('li:first()').click(); // quick create 'b'
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
|
||||
assert.verifySteps(['name_create'],
|
||||
"should wait for the name_create before creating the record");
|
||||
|
||||
def.resolve();
|
||||
|
||||
assert.verifySteps(['name_create', 'create']);
|
||||
assert.strictEqual(form.$('.o_data_row:first .o_data_cell').text(), 'b',
|
||||
"first row should have the correct m2o value");
|
||||
|
||||
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY;
|
||||
form.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('autocompletion in a many2one, in form view with a domain', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user