summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/fields/field_utils.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/src/js/fields/field_utils.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/src/js/fields/field_utils.js')
-rw-r--r--addons/web/static/src/js/fields/field_utils.js762
1 files changed, 762 insertions, 0 deletions
diff --git a/addons/web/static/src/js/fields/field_utils.js b/addons/web/static/src/js/fields/field_utils.js
new file mode 100644
index 00000000..beba1b07
--- /dev/null
+++ b/addons/web/static/src/js/fields/field_utils.js
@@ -0,0 +1,762 @@
+odoo.define('web.field_utils', function (require) {
+"use strict";
+
+/**
+ * Field Utils
+ *
+ * This file contains two types of functions: formatting functions and parsing
+ * functions.
+ *
+ * Each field type has to display in string form at some point, but it should be
+ * stored in memory with the actual value. For example, a float value of 0.5 is
+ * represented as the string "0.5" but is kept in memory as a float. A date
+ * (or datetime) value is always stored as a Moment.js object, but displayed as
+ * a string. This file contains all sort of functions necessary to perform the
+ * conversions.
+ */
+
+var core = require('web.core');
+var dom = require('web.dom');
+var session = require('web.session');
+var time = require('web.time');
+var utils = require('web.utils');
+
+var _t = core._t;
+
+//------------------------------------------------------------------------------
+// Formatting
+//------------------------------------------------------------------------------
+
+/**
+ * Convert binary to bin_size
+ *
+ * @param {string} [value] base64 representation of the binary (might be already a bin_size!)
+ * @param {Object} [field]
+ * a description of the field (note: this parameter is ignored)
+ * @param {Object} [options] additional options (note: this parameter is ignored)
+ *
+ * @returns {string} bin_size (which is human-readable)
+ */
+function formatBinary(value, field, options) {
+ if (!value) {
+ return '';
+ }
+ return utils.binaryToBinsize(value);
+}
+
+/**
+ * @todo Really? it returns a jQuery element... We should try to avoid this and
+ * let DOM utility functions handle this directly. And replace this with a
+ * function that returns a string so we can get rid of the forceString.
+ *
+ * @param {boolean} value
+ * @param {Object} [field]
+ * a description of the field (note: this parameter is ignored)
+ * @param {Object} [options] additional options
+ * @param {boolean} [options.forceString=false] if true, returns a string
+* representation of the boolean rather than a jQueryElement
+ * @returns {jQuery|string}
+ */
+function formatBoolean(value, field, options) {
+ if (options && options.forceString) {
+ return value ? _t('True') : _t('False');
+ }
+ return dom.renderCheckbox({
+ prop: {
+ checked: value,
+ disabled: true,
+ },
+ });
+}
+
+/**
+ * Returns a string representing a char. If the value is false, then we return
+ * an empty string.
+ *
+ * @param {string|false} value
+ * @param {Object} [field]
+ * a description of the field (note: this parameter is ignored)
+ * @param {Object} [options] additional options
+ * @param {boolean} [options.escape=false] if true, escapes the formatted value
+ * @param {boolean} [options.isPassword=false] if true, returns '********'
+ * instead of the formatted value
+ * @returns {string}
+ */
+function formatChar(value, field, options) {
+ value = typeof value === 'string' ? value : '';
+ if (options && options.isPassword) {
+ return _.str.repeat('*', value ? value.length : 0);
+ }
+ if (options && options.escape) {
+ value = _.escape(value);
+ }
+ return value;
+}
+
+/**
+ * Returns a string representing a date. If the value is false, then we return
+ * an empty string. Note that this is dependant on the localization settings
+ *
+ * @param {Moment|false} value
+ * @param {Object} [field]
+ * a description of the field (note: this parameter is ignored)
+ * @param {Object} [options] additional options
+ * @param {boolean} [options.timezone=true] use the user timezone when formating the
+ * date
+ * @returns {string}
+ */
+function formatDate(value, field, options) {
+ if (value === false || isNaN(value)) {
+ return "";
+ }
+ if (field && field.type === 'datetime') {
+ if (!options || !('timezone' in options) || options.timezone) {
+ value = value.clone().add(session.getTZOffset(value), 'minutes');
+ }
+ }
+ var date_format = time.getLangDateFormat();
+ return value.format(date_format);
+}
+
+/**
+ * Returns a string representing a datetime. If the value is false, then we
+ * return an empty string. Note that this is dependant on the localization
+ * settings
+ *
+ * @params {Moment|false}
+ * @param {Object} [field]
+ * a description of the field (note: this parameter is ignored)
+ * @param {Object} [options] additional options
+ * @param {boolean} [options.timezone=true] use the user timezone when formating the
+ * date
+ * @returns {string}
+ */
+function formatDateTime(value, field, options) {
+ if (value === false) {
+ return "";
+ }
+ if (!options || !('timezone' in options) || options.timezone) {
+ value = value.clone().add(session.getTZOffset(value), 'minutes');
+ }
+ return value.format(time.getLangDatetimeFormat());
+}
+
+/**
+ * Returns a string representing a float. The result takes into account the
+ * user settings (to display the correct decimal separator).
+ *
+ * @param {float|false} value the value that should be formatted
+ * @param {Object} [field] a description of the field (returned by fields_get
+ * for example). It may contain a description of the number of digits that
+ * should be used.
+ * @param {Object} [options] additional options to override the values in the
+ * python description of the field.
+ * @param {integer[]} [options.digits] the number of digits that should be used,
+ * instead of the default digits precision in the field.
+ * @param {function} [options.humanReadable] if returns true,
+ * formatFloat acts like utils.human_number
+ * @returns {string}
+ */
+function formatFloat(value, field, options) {
+ options = options || {};
+ if (value === false) {
+ return "";
+ }
+ if (options.humanReadable && options.humanReadable(value)) {
+ return utils.human_number(value, options.decimals, options.minDigits, options.formatterCallback);
+ }
+ var l10n = core._t.database.parameters;
+ var precision;
+ if (options.digits) {
+ precision = options.digits[1];
+ } else if (field && field.digits) {
+ precision = field.digits[1];
+ } else {
+ precision = 2;
+ }
+ var formatted = _.str.sprintf('%.' + precision + 'f', value || 0).split('.');
+ formatted[0] = utils.insert_thousand_seps(formatted[0]);
+ return formatted.join(l10n.decimal_point);
+}
+
+
+/**
+ * Returns a string representing a float value, from a float converted with a
+ * factor.
+ *
+ * @param {number} value
+ * @param {number} [options.factor]
+ * Conversion factor, default value is 1.0
+ * @returns {string}
+ */
+function formatFloatFactor(value, field, options) {
+ var factor = options.factor || 1;
+ return formatFloat(value * factor, field, options);
+}
+
+/**
+ * Returns a string representing a time value, from a float. The idea is that
+ * we sometimes want to display something like 1:45 instead of 1.75, or 0:15
+ * instead of 0.25.
+ *
+ * @param {float} value
+ * @param {Object} [field]
+ * a description of the field (note: this parameter is ignored)
+ * @param {Object} [options]
+ * @param {boolean} [options.noLeadingZeroHour] if true, format like 1:30
+ * otherwise, format like 01:30
+ * @returns {string}
+ */
+function formatFloatTime(value, field, options) {
+ options = options || {};
+ var pattern = options.noLeadingZeroHour ? '%1d:%02d' : '%02d:%02d';
+ if (value < 0) {
+ value = Math.abs(value);
+ pattern = '-' + pattern;
+ }
+ var hour = Math.floor(value);
+ var min = Math.round((value % 1) * 60);
+ if (min === 60){
+ min = 0;
+ hour = hour + 1;
+ }
+ return _.str.sprintf(pattern, hour, min);
+}
+
+/**
+ * Returns a string representing an integer. If the value is false, then we
+ * return an empty string.
+ *
+ * @param {integer|false} value
+ * @param {Object} [field]
+ * a description of the field (note: this parameter is ignored)
+ * @param {Object} [options] additional options
+ * @param {boolean} [options.isPassword=false] if true, returns '********'
+ * @param {function} [options.humanReadable] if returns true,
+ * formatFloat acts like utils.human_number
+ * @returns {string}
+ */
+function formatInteger(value, field, options) {
+ options = options || {};
+ if (options.isPassword) {
+ return _.str.repeat('*', String(value).length);
+ }
+ if (!value && value !== 0) {
+ // previously, it returned 'false'. I don't know why. But for the Pivot
+ // view, I want to display the concept of 'no value' with an empty
+ // string.
+ return "";
+ }
+ if (options.humanReadable && options.humanReadable(value)) {
+ return utils.human_number(value, options.decimals, options.minDigits, options.formatterCallback);
+ }
+ return utils.insert_thousand_seps(_.str.sprintf('%d', value));
+}
+
+/**
+ * Returns a string representing an many2one. If the value is false, then we
+ * return an empty string. Note that it accepts two types of input parameters:
+ * an array, in that case we assume that the many2one value is of the form
+ * [id, nameget], and we return the nameget, or it can be an object, and in that
+ * case, we assume that it is a record datapoint from a BasicModel.
+ *
+ * @param {Array|Object|false} value
+ * @param {Object} [field]
+ * a description of the field (note: this parameter is ignored)
+ * @param {Object} [options] additional options
+ * @param {boolean} [options.escape=false] if true, escapes the formatted value
+ * @returns {string}
+ */
+function formatMany2one(value, field, options) {
+ if (!value) {
+ value = '';
+ } else if (_.isArray(value)) {
+ // value is a pair [id, nameget]
+ value = value[1];
+ } else {
+ // value is a datapoint, so we read its display_name field, which
+ // may in turn be a datapoint (if the name field is a many2one)
+ while (value.data) {
+ value = value.data.display_name || '';
+ }
+ }
+ if (options && options.escape) {
+ value = _.escape(value);
+ }
+ return value;
+}
+
+/**
+ * Returns a string indicating the number of records in the relation.
+ *
+ * @param {Object} value a valid element from a BasicModel, that represents a
+ * list of values
+ * @returns {string}
+ */
+function formatX2Many(value) {
+ if (value.data.length === 0) {
+ return _t('No records');
+ } else if (value.data.length === 1) {
+ return _t('1 record');
+ } else {
+ return value.data.length + _t(' records');
+ }
+}
+
+/**
+ * Returns a string representing a monetary value. The result takes into account
+ * the user settings (to display the correct decimal separator, currency, ...).
+ *
+ * @param {float|false} value the value that should be formatted
+ * @param {Object} [field]
+ * a description of the field (returned by fields_get for example). It
+ * may contain a description of the number of digits that should be used.
+ * @param {Object} [options]
+ * additional options to override the values in the python description of
+ * the field.
+ * @param {Object} [options.currency] the description of the currency to use
+ * @param {integer} [options.currency_id]
+ * the id of the 'res.currency' to use (ignored if options.currency)
+ * @param {string} [options.currency_field]
+ * the name of the field whose value is the currency id
+ * (ignore if options.currency or options.currency_id)
+ * Note: if not given it will default to the field currency_field value
+ * or to 'currency_id'.
+ * @param {Object} [options.data]
+ * a mapping of field name to field value, required with
+ * options.currency_field
+ * @param {integer[]} [options.digits]
+ * the number of digits that should be used, instead of the default
+ * digits precision in the field. Note: if the currency defines a
+ * precision, the currency's one is used.
+ * @param {boolean} [options.forceString=false]
+ * if false, returns a string encoding the html formatted value (with
+ * whitespace encoded as '&nbsp;')
+ * @returns {string}
+ */
+function formatMonetary(value, field, options) {
+ if (value === false) {
+ return "";
+ }
+ options = Object.assign({ forceString: false }, options);
+
+ var currency = options.currency;
+ if (!currency) {
+ var currency_id = options.currency_id;
+ if (!currency_id && options.data) {
+ var currency_field = options.currency_field || field.currency_field || 'currency_id';
+ currency_id = options.data[currency_field] && options.data[currency_field].res_id;
+ }
+ currency = session.get_currency(currency_id);
+ }
+
+ var digits = (currency && currency.digits) || options.digits;
+ if (options.field_digits === true) {
+ digits = field.digits || digits;
+ }
+ var formatted_value = formatFloat(value, field,
+ _.extend({}, options , {digits: digits})
+ );
+
+ if (!currency || options.noSymbol) {
+ return formatted_value;
+ }
+ const ws = options.forceString ? ' ' : '&nbsp;';
+ if (currency.position === "after") {
+ return formatted_value + ws + currency.symbol;
+ } else {
+ return currency.symbol + ws + formatted_value;
+ }
+}
+/**
+ * Returns a string representing the given value (multiplied by 100)
+ * concatenated with '%'.
+ *
+ * @param {number | false} value
+ * @param {Object} [field]
+ * @param {Object} [options]
+ * @param {function} [options.humanReadable] if returns true, parsing is avoided
+ * @returns {string}
+ */
+function formatPercentage(value, field, options) {
+ options = options || {};
+ let result = formatFloat(value * 100, field, options) || '0';
+ if (!options.humanReadable || !options.humanReadable(value * 100)) {
+ result = parseFloat(result).toString().replace('.', _t.database.parameters.decimal_point);
+ }
+ return result + (options.noSymbol ? '' : '%');
+}
+/**
+ * Returns a string representing the value of the selection.
+ *
+ * @param {string|false} value
+ * @param {Object} [field]
+ * a description of the field (note: this parameter is ignored)
+ * @param {Object} [options] additional options
+ * @param {boolean} [options.escape=false] if true, escapes the formatted value
+ */
+function formatSelection(value, field, options) {
+ var val = _.find(field.selection, function (option) {
+ return option[0] === value;
+ });
+ if (!val) {
+ return '';
+ }
+ value = val[1];
+ if (options && options.escape) {
+ value = _.escape(value);
+ }
+ return value;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Parse
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Smart date inputs are shortcuts to write dates quicker.
+ * These shortcuts should respect the format ^[+-]\d+[dmwy]?$
+ *
+ * e.g.
+ * "+1d" or "+1" will return now + 1 day
+ * "-2w" will return now - 2 weeks
+ * "+3m" will return now + 3 months
+ * "-4y" will return now + 4 years
+ *
+ * @param {string} value
+ * @returns {Moment|false} Moment date object
+ */
+function parseSmartDateInput(value) {
+ const units = {
+ d: 'days',
+ m: 'months',
+ w: 'weeks',
+ y: 'years',
+ };
+ const re = new RegExp(`^([+-])(\\d+)([${Object.keys(units).join('')}]?)$`);
+ const match = re.exec(value);
+ if (match) {
+ let date = moment();
+ const offset = parseInt(match[2], 10);
+ const unit = units[match[3] || 'd'];
+ if (match[1] === '+') {
+ date.add(offset, unit);
+ } else {
+ date.subtract(offset, unit);
+ }
+ return date;
+ }
+ return false;
+}
+
+/**
+ * Create an Date object
+ * The method toJSON return the formated value to send value server side
+ *
+ * @param {string} value
+ * @param {Object} [field]
+ * a description of the field (note: this parameter is ignored)
+ * @param {Object} [options] additional options
+ * @param {boolean} [options.isUTC] the formatted date is utc
+ * @param {boolean} [options.timezone=false] format the date after apply the timezone
+ * offset
+ * @returns {Moment|false} Moment date object
+ */
+function parseDate(value, field, options) {
+ if (!value) {
+ return false;
+ }
+ var datePattern = time.getLangDateFormat();
+ var datePatternWoZero = time.getLangDateFormatWoZero();
+ var date;
+ const smartDate = parseSmartDateInput(value);
+ if (smartDate) {
+ date = smartDate;
+ } else {
+ if (options && options.isUTC) {
+ value = value.padStart(10, "0"); // server may send "932-10-10" for "0932-10-10" on some OS
+ date = moment.utc(value);
+ } else {
+ date = moment.utc(value, [datePattern, datePatternWoZero, moment.ISO_8601]);
+ }
+ }
+ if (date.isValid()) {
+ if (date.year() === 0) {
+ date.year(moment.utc().year());
+ }
+ if (date.year() >= 1000){
+ date.toJSON = function () {
+ return this.clone().locale('en').format('YYYY-MM-DD');
+ };
+ return date;
+ }
+ }
+ throw new Error(_.str.sprintf(core._t("'%s' is not a correct date"), value));
+}
+
+/**
+ * Create an Date object
+ * The method toJSON return the formated value to send value server side
+ *
+ * @param {string} value
+ * @param {Object} [field]
+ * a description of the field (note: this parameter is ignored)
+ * @param {Object} [options] additional options
+ * @param {boolean} [options.isUTC] the formatted date is utc
+ * @param {boolean} [options.timezone=false] format the date after apply the timezone
+ * offset
+ * @returns {Moment|false} Moment date object
+ */
+function parseDateTime(value, field, options) {
+ if (!value) {
+ return false;
+ }
+ const datePattern = time.getLangDateFormat();
+ const timePattern = time.getLangTimeFormat();
+ const datePatternWoZero = time.getLangDateFormatWoZero();
+ const timePatternWoZero = time.getLangTimeFormatWoZero();
+ var pattern1 = datePattern + ' ' + timePattern;
+ var pattern2 = datePatternWoZero + ' ' + timePatternWoZero;
+ var datetime;
+ const smartDate = parseSmartDateInput(value);
+ if (smartDate) {
+ datetime = smartDate;
+ } else {
+ if (options && options.isUTC) {
+ value = value.padStart(19, "0"); // server may send "932-10-10" for "0932-10-10" on some OS
+ // phatomjs crash if we don't use this format
+ datetime = moment.utc(value.replace(' ', 'T') + 'Z');
+ } else {
+ datetime = moment.utc(value, [pattern1, pattern2, moment.ISO_8601]);
+ if (options && options.timezone) {
+ datetime.add(-session.getTZOffset(datetime), 'minutes');
+ }
+ }
+ }
+ if (datetime.isValid()) {
+ if (datetime.year() === 0) {
+ datetime.year(moment.utc().year());
+ }
+ if (datetime.year() >= 1000) {
+ datetime.toJSON = function () {
+ return this.clone().locale('en').format('YYYY-MM-DD HH:mm:ss');
+ };
+ return datetime;
+ }
+ }
+ throw new Error(_.str.sprintf(core._t("'%s' is not a correct datetime"), value));
+}
+
+/**
+ * Parse a String containing number in language formating
+ *
+ * @param {string} value
+ * The string to be parsed with the setting of thousands and
+ * decimal separator
+ * @returns {float|NaN} the number value contained in the string representation
+ */
+function parseNumber(value) {
+ if (core._t.database.parameters.thousands_sep) {
+ var escapedSep = _.str.escapeRegExp(core._t.database.parameters.thousands_sep);
+ value = value.replace(new RegExp(escapedSep, 'g'), '');
+ }
+ if (core._t.database.parameters.decimal_point) {
+ value = value.replace(core._t.database.parameters.decimal_point, '.');
+ }
+ return Number(value);
+}
+
+/**
+ * Parse a String containing float in language formating
+ *
+ * @param {string} value
+ * The string to be parsed with the setting of thousands and
+ * decimal separator
+ * @returns {float}
+ * @throws {Error} if no float is found respecting the language configuration
+ */
+function parseFloat(value) {
+ var parsed = parseNumber(value);
+ if (isNaN(parsed)) {
+ throw new Error(_.str.sprintf(core._t("'%s' is not a correct float"), value));
+ }
+ return parsed;
+}
+
+/**
+ * Parse a String containing currency symbol and returns amount
+ *
+ * @param {string} value
+ * The string to be parsed
+ * We assume that a monetary is always a pair (symbol, amount) separated
+ * by a non breaking space. A simple float can also be accepted as value
+ * @param {Object} [field]
+ * a description of the field (returned by fields_get for example).
+ * @param {Object} [options] additional options.
+ * @param {Object} [options.currency] - the description of the currency to use
+ * @param {integer} [options.currency_id]
+ * the id of the 'res.currency' to use (ignored if options.currency)
+ * @param {string} [options.currency_field]
+ * the name of the field whose value is the currency id
+ * (ignore if options.currency or options.currency_id)
+ * Note: if not given it will default to the field currency_field value
+ * or to 'currency_id'.
+ * @param {Object} [options.data]
+ * a mapping of field name to field value, required with
+ * options.currency_field
+ *
+ * @returns {float} the float value contained in the string representation
+ * @throws {Error} if no float is found or if parameter does not respect monetary condition
+ */
+function parseMonetary(value, field, options) {
+ var values = value.split('&nbsp;');
+ if (values.length === 1) {
+ return parseFloat(value);
+ }
+ else if (values.length !== 2) {
+ throw new Error(_.str.sprintf(core._t("'%s' is not a correct monetary field"), value));
+ }
+ options = options || {};
+ var currency = options.currency;
+ if (!currency) {
+ var currency_id = options.currency_id;
+ if (!currency_id && options.data) {
+ var currency_field = options.currency_field || field.currency_field || 'currency_id';
+ currency_id = options.data[currency_field] && options.data[currency_field].res_id;
+ }
+ currency = session.get_currency(currency_id);
+ }
+ return parseFloat(values[0] === currency.symbol ? values[1] : values[0]);
+}
+
+/**
+ * Parse a String containing float and unconvert it with a conversion factor
+ *
+ * @param {number} [options.factor]
+ * Conversion factor, default value is 1.0
+ */
+function parseFloatFactor(value, field, options) {
+ var parsed = parseFloat(value);
+ var factor = options.factor || 1.0;
+ return parsed / factor;
+}
+
+function parseFloatTime(value) {
+ var factor = 1;
+ if (value[0] === '-') {
+ value = value.slice(1);
+ factor = -1;
+ }
+ var float_time_pair = value.split(":");
+ if (float_time_pair.length !== 2)
+ return factor * parseFloat(value);
+ var hours = parseInteger(float_time_pair[0]);
+ var minutes = parseInteger(float_time_pair[1]);
+ return factor * (hours + (minutes / 60));
+}
+
+/**
+ * Parse a String containing float and unconvert it with a conversion factor
+ * of 100. The percentage can be a regular xx.xx float or a xx%.
+ *
+ * @param {string} value
+ * The string to be parsed
+ * @returns {float}
+ * @throws {Error} if the value couldn't be converted to float
+ */
+function parsePercentage(value) {
+ return parseFloat(value) / 100;
+}
+
+/**
+ * Parse a String containing integer with language formating
+ *
+ * @param {string} value
+ * The string to be parsed with the setting of thousands and
+ * decimal separator
+ * @returns {integer}
+ * @throws {Error} if no integer is found respecting the language configuration
+ */
+function parseInteger(value) {
+ var parsed = parseNumber(value);
+ // do not accept not numbers or float values
+ if (isNaN(parsed) || parsed % 1 || parsed < -2147483648 || parsed > 2147483647) {
+ throw new Error(_.str.sprintf(core._t("'%s' is not a correct integer"), value));
+ }
+ return parsed;
+}
+
+/**
+ * Creates an object with id and display_name.
+ *
+ * @param {Array|number|string|Object} value
+ * The given value can be :
+ * - an array with id as first element and display_name as second element
+ * - a number or a string representing the id (the display_name will be
+ * returned as undefined)
+ * - an object, simply returned untouched
+ * @returns {Object} (contains the id and display_name)
+ * Note: if the given value is not an array, a string or a
+ * number, the value is returned untouched.
+ */
+function parseMany2one(value) {
+ if (_.isArray(value)) {
+ return {
+ id: value[0],
+ display_name: value[1],
+ };
+ }
+ if (_.isNumber(value) || _.isString(value)) {
+ return {
+ id: parseInt(value, 10),
+ };
+ }
+ return value;
+}
+
+return {
+ format: {
+ binary: formatBinary,
+ boolean: formatBoolean,
+ char: formatChar,
+ date: formatDate,
+ datetime: formatDateTime,
+ float: formatFloat,
+ float_factor: formatFloatFactor,
+ float_time: formatFloatTime,
+ html: _.identity, // todo
+ integer: formatInteger,
+ many2many: formatX2Many,
+ many2one: formatMany2one,
+ many2one_reference: formatInteger,
+ monetary: formatMonetary,
+ one2many: formatX2Many,
+ percentage: formatPercentage,
+ reference: formatMany2one,
+ selection: formatSelection,
+ text: formatChar,
+ },
+ parse: {
+ binary: _.identity,
+ boolean: _.identity, // todo
+ char: _.identity, // todo
+ date: parseDate, // todo
+ datetime: parseDateTime, // todo
+ float: parseFloat,
+ float_factor: parseFloatFactor,
+ float_time: parseFloatTime,
+ html: _.identity, // todo
+ integer: parseInteger,
+ many2many: _.identity, // todo
+ many2one: parseMany2one,
+ many2one_reference: parseInteger,
+ monetary: parseMonetary,
+ one2many: _.identity,
+ percentage: parsePercentage,
+ reference: parseMany2one,
+ selection: _.identity, // todo
+ text: _.identity, // todo
+ },
+};
+
+});