[FIX] web: FieldWidgets: use correct format/parse functions
Field widgets must format and parse values according to the widget type, not to the field type. For example, if a monetary field is inserted in a form view with widget="float", the field's value must be formatted with the float formatter, not the montetary one. This wasn't the case before this rev.
This commit is contained in:
@@ -142,6 +142,21 @@ var AbstractField = Widget.extend({
|
||||
// this is the last value that was set by the user, unparsed. This is
|
||||
// used to avoid setting the value twice in a row with the exact value.
|
||||
this.lastSetValue = undefined;
|
||||
|
||||
// formatType is used to determine which format (and parse) functions
|
||||
// to call to format the field's value to insert into the DOM (typically
|
||||
// put into a span or an input), and to parse the value from the input
|
||||
// to send it to the server. These functions are chosen according to
|
||||
// the 'widget' attrs if is is given, and if it is a valid key, with a
|
||||
// fallback on the field type, ensuring that the value is formatted and
|
||||
// displayed according to the choosen widget, if any.
|
||||
this.formatType = this.attrs.widget in field_utils.format ?
|
||||
this.attrs.widget :
|
||||
this.field.type;
|
||||
// formatOptions (resp. parseOptions) is a dict of options passed to
|
||||
// calls to the format (resp. parse) function.
|
||||
this.formatOptions = {};
|
||||
this.parseOptions = {};
|
||||
},
|
||||
/**
|
||||
* When a field widget is appended to the DOM, its start method is called,
|
||||
@@ -266,15 +281,15 @@ var AbstractField = Widget.extend({
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* convert the value from the field to a string representation
|
||||
* Converts the value from the field to a string representation.
|
||||
*
|
||||
* @private
|
||||
* @param {any} value (from the field type)
|
||||
* @returns {string}
|
||||
*/
|
||||
_formatValue: function (value) {
|
||||
var options = _.extend({}, this.nodeOptions, { data: this.recordData });
|
||||
return field_utils.format[this.field.type](value, this.field, options);
|
||||
var options = _.extend({}, this.nodeOptions, { data: this.recordData }, this.formatOptions);
|
||||
return field_utils.format[this.formatType](value, this.field, options);
|
||||
},
|
||||
/**
|
||||
* This method check if a value is the same as the current value of the
|
||||
@@ -291,15 +306,14 @@ var AbstractField = Widget.extend({
|
||||
return this.value === value;
|
||||
},
|
||||
/**
|
||||
* convert a string representation to a valid value, depending on the field
|
||||
* type.
|
||||
* Converts a string representation to a valid value.
|
||||
*
|
||||
* @private
|
||||
* @param {string} value
|
||||
* @returns {any}
|
||||
*/
|
||||
_parseValue: function (value) {
|
||||
return field_utils.parse[this.field.type](value, this.field);
|
||||
return field_utils.parse[this.formatType](value, this.field, this.parseOptions);
|
||||
},
|
||||
/**
|
||||
* main rendering function. Override this if your widget has the same render
|
||||
|
||||
@@ -321,6 +321,14 @@ var FieldDate = InputField.extend({
|
||||
tagName: "span",
|
||||
supportedFieldTypes: ['date'],
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
// use the session timezone when formatting dates
|
||||
this.formatOptions.timezone = true;
|
||||
},
|
||||
/**
|
||||
* In edit mode, instantiates a DateWidget datepicker and listen to changes.
|
||||
*
|
||||
@@ -396,17 +404,6 @@ var FieldDate = InputField.extend({
|
||||
this.datewidget.setValue(this.value);
|
||||
this.$input = this.datewidget.$input;
|
||||
},
|
||||
|
||||
/**
|
||||
* FieldDateTime overrides _formatValue to use the session timezone
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
* @param {Moment} value
|
||||
*/
|
||||
_formatValue: function (value) {
|
||||
return field_utils.format.date(value, {timezone: true});
|
||||
},
|
||||
});
|
||||
|
||||
var FieldDateTime = FieldDate.extend({
|
||||
@@ -448,17 +445,6 @@ var FieldDateTime = FieldDate.extend({
|
||||
this.datewidget.setValue(value);
|
||||
this.$input = this.datewidget.$input;
|
||||
},
|
||||
|
||||
/**
|
||||
* FieldDateTime overrides _formatValue to use the session timezone
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
* @param {Moment} value
|
||||
*/
|
||||
_formatValue: function (value) {
|
||||
return field_utils.format.datetime(value, {timezone: true});
|
||||
},
|
||||
});
|
||||
|
||||
var FieldMonetary = InputField.extend({
|
||||
@@ -490,6 +476,13 @@ var FieldMonetary = InputField.extend({
|
||||
this.tagName = 'div';
|
||||
this.className += ' o_input';
|
||||
}
|
||||
|
||||
// use the formatFloat function in edit
|
||||
if (this.mode === 'edit') {
|
||||
this.formatType = 'float';
|
||||
}
|
||||
this.formatOptions.currency = this.currency;
|
||||
this.formatOptions.digits = [16, 2];
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -562,22 +555,6 @@ var FieldMonetary = InputField.extend({
|
||||
var currencyID = this.record.data[currencyField] && this.record.data[currencyField].res_id;
|
||||
this.currency = session.get_currency(currencyID);
|
||||
},
|
||||
/**
|
||||
* FieldMonetary overrides _formatValue to use the format monetary method
|
||||
* in readonly.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
* @param {float} value
|
||||
* @returns {string}
|
||||
*/
|
||||
_formatValue: function (value) {
|
||||
var format = field_utils.format[this.mode === 'edit' ? "float" : "monetary"];
|
||||
return format(value, this.field, {
|
||||
digits: [16, 2],
|
||||
currency: this.currency,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
var FieldBoolean = AbstractField.extend({
|
||||
@@ -739,7 +716,7 @@ var FieldInteger = InputField.extend({
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return field_utils.format.integer(value, this.field);
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -776,31 +753,11 @@ var FieldFloat = InputField.extend({
|
||||
});
|
||||
|
||||
var FieldFloatTime = FieldFloat.extend({
|
||||
supportedFieldTypes: ['float'],
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Format the float time value into a human-readable time format.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_formatValue: function (value) {
|
||||
return field_utils.format.float_time(value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse the human-readable formatted time value into a float.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_parseValue: function (value) {
|
||||
return field_utils.parse.float_time(value);
|
||||
},
|
||||
// this is not strictly necessary, as for this widget to be used, the 'widget'
|
||||
// attrs must be set to 'float_time', so the formatType is automatically
|
||||
// 'float_time', but for the sake of clarity, we explicitely define a
|
||||
// FieldFloatTime widget with formatType = 'float_time'.
|
||||
formatType: 'float_time',
|
||||
});
|
||||
|
||||
var FieldText = InputField.extend(TranslatableFieldMixin, {
|
||||
|
||||
@@ -1763,6 +1763,7 @@ var FieldSelection = AbstractField.extend({
|
||||
this._super.apply(this, arguments);
|
||||
if (this.field.type === 'many2one') {
|
||||
this.values = this.record.specialData[this.name];
|
||||
this.formatType = 'many2one';
|
||||
} else {
|
||||
this.values = _.reject(this.field.selection, function (v) {
|
||||
return v[0] === false && v[1] === '';
|
||||
|
||||
@@ -536,6 +536,35 @@ QUnit.module('basic_fields', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('float widget on monetary field', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data.partner.fields.monetary = {string: "Monetary", type: 'monetary'};
|
||||
this.data.partner.records[0].monetary = 9.99;
|
||||
this.data.partner.records[0].currency_id = 1;
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch:'<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<field name="monetary" widget="float"/>' +
|
||||
'<field name="currency_id" invisible="1"/>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
session: {
|
||||
currencies: _.indexBy(this.data.currency.records, 'id'),
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(form.$('.o_field_widget[name=monetary]').text(), '9.99',
|
||||
'value should be correctly formatted (with the float formatter)');
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.module('FieldEmail');
|
||||
|
||||
QUnit.test('email field in form view', function (assert) {
|
||||
|
||||
@@ -5153,7 +5153,7 @@ QUnit.module('relational_fields', {
|
||||
});
|
||||
|
||||
QUnit.test('widget selection, edition and on many2one field', function (assert) {
|
||||
assert.expect(15);
|
||||
assert.expect(18);
|
||||
|
||||
this.data.partner.onchanges = {product_id: function () {}};
|
||||
this.data.partner.records[0].product_id = 37;
|
||||
@@ -5178,6 +5178,13 @@ QUnit.module('relational_fields', {
|
||||
});
|
||||
|
||||
assert.ok(!form.$('select').length, "should not have a select tag in dom");
|
||||
assert.strictEqual(form.$('.o_field_widget[name=product_id]').text(), 'xphone',
|
||||
"should have rendered the many2one field correctly");
|
||||
assert.strictEqual(form.$('.o_field_widget[name=trululu]').text(), '',
|
||||
"should have rendered the unset many2one field correctly");
|
||||
assert.strictEqual(form.$('.o_field_widget[name=color]').text(), 'Red',
|
||||
"should have rendered the selection field correctly");
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
|
||||
assert.strictEqual(form.$('select').length, 3,
|
||||
|
||||
Reference in New Issue
Block a user