[FIX] web: one2many form view with action button

Assume a one2many field inside a form, displaying field A in its
list view, and a field B in its (dialog) form view. Moreover, there
is an action button in the dialog, which updates the value of B.

Before this rev., B wasn't correctly reloaded when the button was
clicked, because the BasicModel reloaded the fields on the list
view (i.e. field A), whereas it should reload the fields in both
views.

This fix is twofold. First, we force the viewType to 'form' when
the record is reloaded, instead of the default ('list'), so that
fields in the form view are correctly reloaded. Second, when we
open the form view in the dialog, we add the fields of the list
view to the dict of fields of the form view, so that the form
is aware of those fields and can ask the model to reload them
(still without displaying them).

Fixes #24189
This commit is contained in:
Aaron Bohy
2018-05-04 10:42:05 +02:00
parent 901658df26
commit 6e15a9cd24
5 changed files with 111 additions and 13 deletions
@@ -1293,10 +1293,7 @@ var FieldOne2Many = FieldX2Many.extend({
var self = this;
var id = ev.data.id;
var onSaved = function (record, hasChanged) {
if (!hasChanged) {
return;
}
var onSaved = function (record) {
if (_.some(self.value.data, {id: record.id})) {
// the record already exists in the relation, so trigger an
// empty 'UPDATE' operation when the user clicks on 'Save' in
@@ -224,6 +224,14 @@ var AbstractView = Class.extend({
_.each(this.loadParams.fieldsInfo.form, function (attrs, fieldName) {
var field = fields[fieldName];
if (!field) {
// when a one2many record is opened in a form view, the fields
// of the main one2many view (list or kanban) are added to the
// fieldsInfo of its form view, but those fields aren't in the
// loadParams.fields, as they are not displayed in the view, so
// we can ignore them.
return;
}
if (field.type !== 'one2many' && field.type !== 'many2many') {
return;
}
@@ -1054,7 +1054,13 @@ var BasicModel = AbstractModel.extend({
addFieldsInfo: function (recordID, viewInfo) {
var record = this.localData[recordID];
record.fields = _.extend({}, record.fields, viewInfo.fields);
record.fieldsInfo = _.extend({}, record.fieldsInfo, viewInfo.fieldsInfo);
// complete the given fieldsInfo with the fields of the main view, so
// that those field will be reloaded if a reload is triggered by the
// secondary view
var fieldsInfo = _.mapObject(viewInfo.fieldsInfo, function (fieldsInfo) {
return _.defaults({}, fieldsInfo, record.fieldsInfo[record.viewType]);
});
record.fieldsInfo = _.extend({}, record.fieldsInfo, fieldsInfo);
},
/**
* For list resources, this freezes the current records order.
@@ -1992,7 +1998,8 @@ var BasicModel = AbstractModel.extend({
*/
_fetchRecord: function (record, options) {
var self = this;
var fieldNames = options && options.fieldNames || record.getFieldNames();
options = options || {};
var fieldNames = options.fieldNames || record.getFieldNames(options);
fieldNames = _.uniq(fieldNames.concat(['display_name']));
return this._rpc({
model: record.model,
@@ -2492,8 +2499,9 @@ var BasicModel = AbstractModel.extend({
_fetchX2Manys: function (record, options) {
var self = this;
var defs = [];
var fieldNames = options && options.fieldNames || record.getFieldNames();
var viewType = options && options.viewType || record.viewType;
options = options || {};
var fieldNames = options.fieldNames || record.getFieldNames(options);
var viewType = options.viewType || record.viewType;
_.each(fieldNames, function (fieldName) {
var field = record.fields[fieldName];
if (field.type === 'one2many' || field.type === 'many2many') {
@@ -3016,11 +3024,15 @@ var BasicModel = AbstractModel.extend({
* default view type.
*
* @param {Object} element an element from the localData
* @param {Object} [options]
* @param {Object} [options.viewType] current viewType. If not set, we will
* assume main viewType from the record
* @returns {string[]} the list of field names
*/
_getFieldNames: function (element) {
_getFieldNames: function (element, options) {
var fieldsInfo = element.fieldsInfo;
return Object.keys(fieldsInfo && fieldsInfo[element.viewType] || {});
var viewType = options && options.viewType || element.viewType;
return Object.keys(fieldsInfo && fieldsInfo[viewType] || {});
},
/**
* Evaluate the record evaluation context. This method is supposed to be
@@ -3500,13 +3512,16 @@ var BasicModel = AbstractModel.extend({
* @see _fetchRecord @see _makeDefaultRecord
*
* @param {Object} record
* @param {Object} record
* @param {Object} [options]
* @param {Object} [options.viewType] current viewType. If not set, we will
* assume main viewType from the record
* @returns {Deferred<Object>} resolves to the finished resource
*/
_postprocess: function (record, options) {
var self = this;
var defs = [];
_.each(record.getFieldNames(), function (name) {
_.each(record.getFieldNames(options), function (name) {
var field = record.fields[name];
var fieldInfo = record.fieldsInfo[record.viewType][name] || {};
var options = fieldInfo.options || {};
@@ -213,6 +213,16 @@ var FormController = BasicController.extend({
return changedFields;
});
},
/**
* Overrides to force the viewType to 'form', so that we ensure that the
* correct fields are reloaded (this is only useful for one2many form views).
*
* @override
*/
update: function (params, options) {
params = _.extend({viewType: 'form'}, params);
return this._super(params, options);
},
//--------------------------------------------------------------------------
// Private
@@ -5692,7 +5692,7 @@ QUnit.module('relational_fields', {
rpcCount++;
if (args.method === 'write') {
assert.deepEqual(args.args[1].p, [[0, args.args[1].p[0][1], {
int_field: 123, product_id: 41
foo: false, int_field: 123, product_id: 41,
}]]);
}
return this._super(route, args);
@@ -8813,6 +8813,74 @@ QUnit.module('relational_fields', {
form.destroy();
});
QUnit.test('one2many form view with action button', function (assert) {
// once the action button is clicked, the record is reloaded (via the
// on_close handler, executed because the python method does not return
// any action, or an ir.action.act_window_close) ; this test ensures that
// it reloads the fields of the opened view (i.e. the form in this case).
// See https://github.com/odoo/odoo/issues/24189
assert.expect(7);
var data = this.data;
data.partner.records[0].p = [2];
var form = createView({
View: FormView,
model: 'partner',
data: data,
res_id: 1,
arch: '<form string="Partners">' +
'<field name="p">' +
'<tree><field name="display_name"/></tree>' +
'<form>' +
'<button type="action" string="Set Timmy"/>' +
'<field name="timmy"/>' +
'</form>' +
'</field>' +
'</form>',
archs: {
'partner_type,false,list': '<tree><field name="display_name"/></tree>',
},
intercepts: {
execute_action: function (ev) {
data.partner.records[1].display_name = 'new name';
data.partner.records[1].timmy = [12];
ev.data.on_closed();
},
},
viewOptions: {
mode: 'edit',
},
});
assert.strictEqual(form.$('.o_data_row').length, 1,
"there should be one record in the one2many");
assert.strictEqual(form.$('.o_data_cell').text(), 'second record',
"initial display_name of o2m record should be correct");
// open one2many record in form view
form.$('.o_data_cell:first').click();
assert.strictEqual($('.modal .o_form_view').length, 1,
"should have opened the form view in a dialog");
assert.strictEqual($('.modal .o_form_view .o_data_row').length, 0,
"there should be no record in the many2many");
// click on the action button
$('.modal .o_form_view button').click();
assert.strictEqual($('.modal .o_data_row').length, 1,
"fields in the o2m form view should have been read");
assert.strictEqual($('.modal .o_data_cell').text(), 'gold',
"many2many subrecord should have been fetched");
// save the dialog
$('.modal .modal-footer .btn-primary').click();
assert.strictEqual(form.$('.o_data_cell').text(), 'new name',
"fields in the o2m list view should have been read as well");
form.destroy();
});
QUnit.module('FieldMany2Many');
QUnit.test('many2many kanban: edition', function (assert) {