[FIX] web: selection field with value 0

The server doesn't make a distinction between an unset selection
field, and a value 0 (integer). So when 0 is a valid key for the
field, the server returns false as value if the field is unset, or
if its value is 0, which is not really convenient.

As a consequence, when the value is 0, it appears as unset in the
interface. To circumvent this, we automatically converts false
value of selection fields to 0, if 0 is a key of the selection.
This commit is contained in:
Aaron Bohy
2017-05-18 13:55:46 +02:00
parent 3dd97db07f
commit 94c91374e3
4 changed files with 97 additions and 24 deletions
@@ -296,12 +296,12 @@ function formatMonetary(value, field, options) {
* @param {boolean} [options.escape=false] if true, escapes the formatted value
*/
function formatSelection(value, field, options) {
if (!value) {
return '';
}
var val = _.find(field.selection, function (option) {
return option[0] === value;
});
if (!val) {
return '';
}
value = val[1];
if (options && options.escape) {
value = _.escape(value);
@@ -1671,7 +1671,7 @@ var FieldSelection = AbstractField.extend({
},
//--------------------------------------------------------------------------
// Private
// Public
//--------------------------------------------------------------------------
/**
@@ -1681,6 +1681,12 @@ var FieldSelection = AbstractField.extend({
getFocusableElement: function () {
return this.$el.is('select') ? this.$el : $();
},
/**
* @override
*/
isSet: function () {
return this.value !== false;
},
//--------------------------------------------------------------------------
// Private
@@ -1093,14 +1093,8 @@ var BasicModel = AbstractModel.extend({
});
defs.push(def);
}
} else if (field.type === 'date') {
// process data: convert into a moment instance
record._changes[name] = fieldUtils.parse.date(val);
} else if (field.type === 'datetime') {
// process datetime: convert into a moment instance
record._changes[name] = fieldUtils.parse.datetime(val);
} else {
record._changes[name] = val;
record._changes[name] = self._parseServerValue(field, val);
}
});
return $.when.apply($, defs);
@@ -2349,14 +2343,8 @@ var BasicModel = AbstractModel.extend({
_.each(_.keys(many2ones), function (name) {
defs.push(self._fetchNameGets(x2manyList, name));
});
} else if (field.type === 'date') {
// process date: convert into a moment instance
record._changes[name] = fieldUtils.parse.date(result[name], field, {isUTC: true});
} else if (field.type === 'datetime') {
// process datetime: convert into a moment instance
record._changes[name] = fieldUtils.parse.datetime(result[name], field, {isUTC: true});
} else {
record._changes[name] = result[name];
record._changes[name] = self._parseServerValue(field, result[name]);
}
});
return $.when.apply($, defs)
@@ -2426,15 +2414,35 @@ var BasicModel = AbstractModel.extend({
// no value for the many2one
record[fieldName] = false;
}
} else if (field.type === 'date') {
// process data: convert into a moment instance
record[fieldName] = fieldUtils.parse.date(val, field, {isUTC: true});
} else if (field.type === 'datetime') {
// process datetime: convert into a moment instance
record[fieldName] = fieldUtils.parse.datetime(val, field, {isUTC: true});
} else {
record[fieldName] = self._parseServerValue(field, val);
}
});
},
/**
* Processes date(time) and selection field values sent by the server.
* Converts data(time) values to moment instances.
* Converts false values of selection fields to 0 if 0 is a valid key,
* because the server doesn't make a distinction between false and 0, and
* always sends false when value is 0.
*
* @param {Object} field the field description
* @param {*} value
* @returns {*} the processed value
*/
_parseServerValue: function (field, value) {
if (field.type === 'date' || field.type === 'datetime') {
// process date(time): convert into a moment instance
value = fieldUtils.parse[field.type](value, field, {isUTC: true});
} else if (field.type === 'selection' && value === false) {
// process selection: convert false to 0, if 0 is a valid key
var hasKey0 = _.find(field.selection, function (option) {
return option[0] === 0;
});
value = hasKey0 ? 0 : value;
}
return value;
},
/**
* This method is quite important: it is supposed to perform the /onchange
* rpc and apply the result.
@@ -4004,6 +4004,65 @@ QUnit.module('relational_fields', {
form.destroy();
});
QUnit.test('unset selection field with 0 as key', function (assert) {
// The server doesn't make a distinction between false value (the field
// is unset), and selection 0, as in that case the value it returns is
// false. So the client must convert false to value 0 if it exists.
assert.expect(2);
this.data.partner.fields.selection = {
type: "selection",
selection: [[0, "Value O"], [1, "Value 1"]],
};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="selection"/>' +
'</form>',
res_id: 1,
});
assert.strictEqual(form.$('.o_field_widget').text(), 'Value O',
"the displayed value should be 'Value O'");
assert.notOk(form.$('.o_field_widget').hasClass('o_field_empty'),
"should not have class o_field_empty");
form.destroy();
});
QUnit.test('unset selection field with string keys', function (assert) {
// The server doesn't make a distinction between false value (the field
// is unset), and selection 0, as in that case the value it returns is
// false. So the client must convert false to value 0 if it exists. In
// this test, it doesn't exist as keys are strings.
assert.expect(2);
this.data.partner.fields.selection = {
type: "selection",
selection: [['0', "Value O"], ['1', "Value 1"]],
};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="selection"/>' +
'</form>',
res_id: 1,
});
assert.strictEqual(form.$('.o_field_widget').text(), '',
"there should be no displayed value");
assert.ok(form.$('.o_field_widget').hasClass('o_field_empty'),
"should have class o_field_empty");
form.destroy();
});
QUnit.module('FieldMany2ManyTags');
QUnit.test('fieldmany2many tags: rendering and edition', function (assert) {