[FIX] web: properly transfer field properties

Before this commit, the field properties (attrs, fieldNames, fields)
were not correctly given to sub views in many case

expected:
* use widget many2many on one2many field
* create record when click on save in formDialog
* do not send create command to the server
* use the fields defined in the field node (instead of tree view fields)
* use the fieldNames instead of the record to get field values
This commit is contained in:
Christophe Matthieu
2017-04-11 19:44:38 +02:00
committed by Aaron Bohy
parent 53d8d38f93
commit 454b215389
4 changed files with 169 additions and 31 deletions
@@ -302,6 +302,9 @@ var BasicModel = AbstractModel.extend({
for (var fieldName in element.data) {
field = element.fields[fieldName];
if (!field) {
continue;
}
// get relational datapoint
if (field.type === 'many2one') {
@@ -842,18 +845,32 @@ var BasicModel = AbstractModel.extend({
});
},
/**
* Set and return the fieldattrs on a record element.
* Set fieldNames.
* Set fields on a record element (current fields as defaults)
* Set fieldattrs.
* It is mostly useful for the cases where a record element is shared
* between various views, such as a one2many with a tree and a form view.
*
* @param {string} recordID a valid element ID
* @param {Object} attrs the new field attrs
* @return {Object} the previous field attrs
* @param {Object} props
* @param {Object} props.fieldNames
* @param {Object} props.fields
* @param {Object} props.fieldAttrs
* @return {Object}
* @return {Object} result.fieldNames the previous field names
* @return {Object} result.fields the previous description fields
* @return {Object} result.fieldattrs the previous field attrs
*/
setFieldAttrs: function (recordID, attrs) {
setFieldProps: function (recordID, props) {
var record = this.localData[recordID];
var result = record.fieldAttrs;
record.fieldAttrs = attrs;
var result = {
fieldNames: record.fieldNames,
fields: record.fields,
fieldAttrs: record.fieldAttrs,
};
record.fieldNames = props.fieldNames;
record.fields = _.defaults(props.fields, record.fields);
record.fieldAttrs = props.fieldAttrs;
return result;
},
/**
@@ -2100,6 +2117,9 @@ var BasicModel = AbstractModel.extend({
if (element.type === 'record') {
for (var fieldName in element.data) {
var field = element.fields[fieldName];
if (!field) {
continue;
}
if (_.contains(['one2many', 'many2one', 'many2many'], field.type)) {
var relationalElement = this.localData[element.data[fieldName]];
@@ -63,24 +63,28 @@ return AbstractRenderer.extend({
* @returns {Object}
*/
_getFieldValues: function (record) {
return _.mapObject(record.data, function (value, name) {
var result = {};
_.each(record.fieldNames, function (name) {
var field = record.fields[name];
var value = record.data[name];
result[name] = value;
if (field.type === 'many2one') {
return value.data ? value.data.id || false : false;
result[name] = value.data ? value.data.id || false : false;
}
if (field.type === 'one2many' || field.type === 'many2many') {
if (value) {
return _.pluck(value.data, 'res_id');
result[name] = _.pluck(value.data, 'res_id');
} else {
return [];
result[name] = [];
}
}
if (field.type === 'boolean') {
// we want an explicit false value, not null if it is unset
return value || false;
result[name] = value || false;
}
return value;
});
result.id = record.data.id;
return result;
},
/**
* Render the view
@@ -141,7 +141,7 @@ var FormViewDialog = ViewDialog.extend({
*/
destroy: function () {
if (this.recordID && this.fieldAttrs) {
this.model.setFieldAttrs(this.recordID, this.fieldAttrs);
this.setFieldProps(this.recordID, this.previousFieldProps);
}
this._super.apply(this, arguments);
},
@@ -168,8 +168,11 @@ var FormViewDialog = ViewDialog.extend({
fields_view_def.then(function (fields_view) {
if (self.recordID) {
self.fieldAttrs =
self.model.setFieldAttrs(self.recordID, fields_view.fieldAttrs);
self.previousFieldProps = self.model.setFieldProps(self.recordID, {
fieldNames: Object.keys(fields_view.fields),
fields: fields_view.fields,
fieldAttrs: fields_view.fieldAttrs
});
}
var formview = new FormView(fields_view.arch, fields_view.fields, {
modelName: self.res_model,
@@ -20,6 +20,7 @@ QUnit.module('relational_fields', {
int_field: {string: "int_field", type: "integer", sortable: true},
qux: {string: "Qux", type: "float", digits: [16,1] },
p: {string: "one2many field", type: "one2many", relation: 'partner', relation_field: 'trululu'},
turtles: {string: "one2many turtle field", type: "one2many", relation: 'turtle'},
trululu: {string: "Trululu", type: "many2one", relation: 'partner'},
timmy: { string: "pokemon", type: "many2many", relation: 'partner_type'},
product_id: {string: "Product", type: "many2one", relation: 'product'},
@@ -39,6 +40,7 @@ QUnit.module('relational_fields', {
int_field: 10,
qux: 0.44,
p: [],
turtles: [2],
timmy: [],
trululu: 4,
}, {
@@ -83,6 +85,36 @@ QUnit.module('relational_fields', {
{id: 14, display_name: "silver", color: 5},
]
},
turtle: {
fields: {
display_name: { string: "Displayed name", type: "char" },
turtle_foo: {string: "Foo", type: "char", default: "My little Foo Value"},
turtle_bar: {string: "Bar", type: "boolean", default: true},
turtle_int: {string: "int", type: "integer", sortable: true, required: true},
turtle_qux: {string: "Qux", type: "float", digits: [16,1], required: true},
turtle_trululu: {string: "Trululu", type: "many2one", relation: 'partner', required: true, default: 1},
product_id: {string: "Product", type: "many2one", relation: 'product', required: true},
},
records: [{
id: 1,
display_name: "leonardo",
turtle_bar: true,
turtle_foo: "yop",
}, {
id: 2,
display_name: "donatello",
turtle_bar: true,
turtle_foo: "blip",
turtle_int: 9,
}, {
id: 3,
display_name: "raphael",
turtle_bar: false,
turtle_foo: "kawa",
turtle_int: 21,
turtle_qux: 9.8,
}],
}
};
}
}, function () {
@@ -1573,42 +1605,44 @@ QUnit.module('relational_fields', {
form.destroy();
});
QUnit.test('one2many with many2many widget', function (assert) {
assert.expect(9);
this.data.partner.records[0].p = [2];
QUnit.test('one2many with many2many widget: create', function (assert) {
assert.expect(10);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="p" widget="many2many">' +
'<field name="turtles" widget="many2many">' +
'<tree>' +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'<field name="turtle_foo"/>' +
'<field name="turtle_qux"/>' +
'<field name="turtle_int"/>' +
'<field name="product_id"/>' +
'</tree>' +
'<form>' +
'<group>' +
'<field name="foo"/>' +
'<field name="bar"/>' +
'<field name="int_field"/>' +
'<field name="turtle_foo"/>' +
'<field name="turtle_bar"/>' +
'<field name="turtle_int"/>' +
'<field name="product_id"/>' +
'</group>' +
'</form>' +
'</field>' +
'</form>',
archs: {
'partner,false,list': '<tree><field name="foo"/><field name="bar"/><field name="product_id"/></tree>',
'partner,false,search': '<search><field name="foo"/><field name="bar"/><field name="product_id"/></search>',
'turtle,false,list': '<tree><field name="display_name"/><field name="turtle_foo"/><field name="turtle_bar"/><field name="product_id"/></tree>',
'turtle,false,search': '<search><field name="turtle_foo"/><field name="turtle_bar"/><field name="product_id"/></search>',
},
session: {},
res_id: 1,
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/turtle/create') {
assert.ok(args.args, "should write on the turtle record");
}
if (route === '/web/dataset/call_kw/partner/write') {
assert.strictEqual(args.args[0][0], 1, "should write on the partner record 1");
assert.strictEqual(args.args[1].p[0][0], 6, "should send only a 'replace with' command");
assert.strictEqual(args.args[1].turtles[0][0], 6, "should send only a 'replace with' command");
}
return this._super.apply(this, arguments);
},
@@ -1630,8 +1664,8 @@ QUnit.module('relational_fields', {
"sould have 1 record in the select view");
$('.modal-footer button:eq(1)').click();
$('.modal input.o_form_field[name="foo"]').val('tototo').trigger('input');
$('.modal input.o_form_field[name="int_field"]').val(50).trigger('input');
$('.modal input.o_form_field[name="turtle_foo"]').val('tototo').trigger('input');
$('.modal input.o_form_field[name="turtle_int"]').val(50).trigger('input');
var $many2one = $('.modal [name="product_id"] input').click();
var $dropdown = $many2one.autocomplete('widget');
$dropdown.find('li:first a').mouseenter();
@@ -1643,7 +1677,7 @@ QUnit.module('relational_fields', {
assert.strictEqual(form.$('.o_data_row').length, 3,
"sould have 3 records in one2many list");
assert.strictEqual(form.$('.o_data_row').text(), "blip9xphoneyop10tototo50xphone",
assert.strictEqual(form.$('.o_data_row').text(), "blip0.09yop0.0tototo0.050xphone",
"sould display the record values in one2many list");
$('.o_form_button_save').click();
@@ -1651,6 +1685,83 @@ QUnit.module('relational_fields', {
form.destroy();
});
QUnit.test('one2many with many2many widget: edition', function (assert) {
assert.expect(6);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<field name="turtles" widget="many2many">' +
'<tree>' +
'<field name="turtle_foo"/>' +
'<field name="turtle_qux"/>' +
'<field name="turtle_int"/>' +
'<field name="product_id"/>' +
'</tree>' +
'<form>' +
'<group>' +
'<field name="turtle_foo"/>' +
'<field name="turtle_bar"/>' +
'<field name="turtle_int"/>' +
'<field name="product_id"/>' +
'</group>' +
'</form>' +
'</field>' +
'</form>',
archs: {
'turtle,false,list': '<tree><field name="display_name"/><field name="turtle_foo"/><field name="turtle_bar"/><field name="product_id"/></tree>',
'turtle,false,search': '<search><field name="turtle_foo"/><field name="turtle_bar"/><field name="product_id"/></search>',
},
session: {},
res_id: 1,
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/turtle/write') {
assert.strictEqual(args.args[0].length, 1, "should write on the turtle record");
assert.deepEqual(args.args[1], {"product_id":37}, "should write only the product_id on the turtle record");
}
if (route === '/web/dataset/call_kw/partner/write') {
assert.strictEqual(args.args[0][0], 1, "should write on the partner record 1");
assert.strictEqual(args.args[1].turtles[0][0], 6, "should send only a 'replace with' command");
}
return this._super.apply(this, arguments);
},
});
form.$('.o_data_row:first').click();
$('.modal .o_form_button_cancel').click();
form.$buttons.find('.o_form_button_edit').click();
// edit the first one2many record
form.$('.o_data_row:first').click();
var $many2one = $('.modal [name="product_id"] input').click();
var $dropdown = $many2one.autocomplete('widget');
$dropdown.find('li:first a').mouseenter();
$dropdown.find('li:first a').click();
$('.modal-footer button:first').click();
$('.o_form_button_save').click(); // don't save anything because the one2many does not change
// add a one2many record
form.$buttons.find('.o_form_button_edit').click();
form.$('.o_form_field_x2many_list_row_add a').click();
$('.modal .o_data_row:first .o_list_record_selector input').click();
$('.modal .o_select_button').click();
// edit the second one2many record
form.$('.o_data_row:eq(1)').click();
$many2one = $('.modal [name="product_id"] input').click();
$dropdown = $many2one.autocomplete('widget');
$dropdown.find('li:first a').mouseenter();
$dropdown.find('li:first a').click();
$('.modal-footer button:first').click();
$('.o_form_button_save').click();
form.destroy();
});
QUnit.test('new record, the context is properly evaluated and sent', function (assert) {
assert.expect(2);