diff options
Diffstat (limited to 'addons/web')
| -rw-r--r-- | addons/web/static/src/js/fields/field_utils.js | 41 | ||||
| -rw-r--r-- | addons/web/static/src/js/fields/relational_fields.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/abstract_view.js | 3 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/basic/basic_model.js | 29 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/form/form_renderer.js | 2 | ||||
| -rw-r--r-- | addons/web/static/src/less/fields.less | 10 | ||||
| -rw-r--r-- | addons/web/static/src/less/form_view.less | 17 | ||||
| -rw-r--r-- | addons/web/static/src/less/list_view.less | 3 | ||||
| -rw-r--r-- | addons/web/static/src/xml/base.xml | 4 | ||||
| -rw-r--r-- | addons/web/static/tests/fields/field_utils_tests.js | 62 | ||||
| -rw-r--r-- | addons/web/static/tests/fields/relational_fields_tests.js | 81 | ||||
| -rw-r--r-- | addons/web/static/tests/views/form_tests.js | 31 |
12 files changed, 241 insertions, 46 deletions
diff --git a/addons/web/static/src/js/fields/field_utils.js b/addons/web/static/src/js/fields/field_utils.js index 369dda9f037..7ba4122cccb 100644 --- a/addons/web/static/src/js/fields/field_utils.js +++ b/addons/web/static/src/js/fields/field_utils.js @@ -418,13 +418,36 @@ function parseDateTime(value, field, options) { throw new Error(_.str.sprintf(core._t("'%s' is not a correct datetime"), value)); } -function parseFloat(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'), ''); } - value = value.replace(core._t.database.parameters.decimal_point, '.'); - var parsed = Number(value); + 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)); } @@ -491,9 +514,17 @@ function parseFloatTime(value) { return factor * (hours + (minutes / 60)); } +/** + * 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) { - value = value.replace(new RegExp(core._t.database.parameters.thousands_sep, "g"), ''); - var parsed = Number(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)); diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index ed5f03b4f9b..c4422285ae7 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -423,7 +423,7 @@ var FieldMany2One = AbstractField.extend({ _updateExternalButton: function () { var has_external_button = !this.nodeOptions.no_open && !this.floating && this.isSet(); this.$external_button.toggle(has_external_button); - this.$el.toggleClass('o_with_button', has_external_button); + this.$el.toggleClass('o_with_button', has_external_button); // Should not be required anymore but kept for compatibility }, @@ -1943,7 +1943,7 @@ var FieldSelection = AbstractField.extend({ for (var i = 0 ; i < this.values.length ; i++) { this.$el.append($('<option/>', { value: JSON.stringify(this.values[i][0]), - html: this.values[i][1] + text: this.values[i][1] })); } var value = this.value; diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js index 594a4507963..a6cfea6e3be 100644 --- a/addons/web/static/src/js/views/abstract_view.js +++ b/addons/web/static/src/js/views/abstract_view.js @@ -226,7 +226,8 @@ var AbstractView = Class.extend({ return; } - attrs.limit = attrs.mode === "tree" ? 80 : 40; + attrs.limit = attrs.mode === "tree" ? 80 : + (attrs.widget === 'many2many_tags' ? 1000 : 40); if (attrs.Widget.prototype.useSubview && !attrs.__no_fetch && !attrs.views[attrs.mode]) { var context = {}; diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 5c6f5d2d851..6884442039e 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -2450,7 +2450,12 @@ var BasicModel = AbstractModel.extend({ _generateChanges: function (record, options) { options = options || {}; var viewType = options.viewType || record.viewType; - var changes = _.extend({}, record._changes); + var changes; + if ('changesOnly' in options && !options.changesOnly) { + changes = _.extend({}, record.data, record._changes); + } else { + changes = _.extend({}, record._changes); + }; var withReadonly = options.withReadonly || false; var commands = this._generateX2ManyCommands(record, { changesOnly: 'changesOnly' in options ? options.changesOnly : true, @@ -2496,10 +2501,14 @@ var BasicModel = AbstractModel.extend({ * current value of the parent record. * * @param {Object} record + * @param {Object} [options] This option object will be given to the private + * method _generateX2ManyCommands. In particular, it is useful to be able + * to send changesOnly:true to get all data, not only the current changes. * @returns {Object} the data */ - _generateOnChangeData: function (record) { - var commands = this._generateX2ManyCommands(record, {withReadonly: true}); + _generateOnChangeData: function (record, options) { + options = _.extend({}, options || {}, {withReadonly: true}); + var commands = this._generateX2ManyCommands(record, options); var data = _.extend(this.get(record.id, {raw: true}).data, commands); // 'display_name' is automatically added to the list of fields to fetch, // when fetching a record, even if it doesn't appear in the view. However, @@ -2618,6 +2627,7 @@ var BasicModel = AbstractModel.extend({ relRecord = _.findWhere(relRecordUpdated, {res_id: list.res_ids[i]}); changes = relRecord ? this._generateChanges(relRecord, options) : {}; if (!_.isEmpty(changes)) { + delete changes.id; command = x2ManyCommands.update(relRecord.res_id, changes); didChange = true; } else { @@ -3194,16 +3204,7 @@ var BasicModel = AbstractModel.extend({ if (value[0] === 6) { // REPLACE_WITH _.each(value[2], function (res_id) { - r = self._makeDataPoint({ - modelName: x2manyList.model, - context: x2manyList.context, - fieldsInfo: fieldsInfo, - fields: fields, - parentID: x2manyList.id, - res_id: res_id, - viewType: viewType, - }); - x2manyList._changes.push({operation: 'ADD', id: r.id}); + x2manyList._changes.push({operation: 'ADD', resID: res_id}); }); var def = self._readUngroupedList(x2manyList).then(function () { return $.when( @@ -3315,7 +3316,7 @@ var BasicModel = AbstractModel.extend({ options.fieldName = fields; } var context = this._getContext(record, options); - var currentData = this._generateOnChangeData(record); + var currentData = this._generateOnChangeData(record, {changesOnly: false}); return self._rpc({ model: record.model, diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index d25d1a423c9..ab5e4beb6b5 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -808,7 +808,7 @@ var FormRenderer = BasicRenderer.extend({ _.each(this.allFieldWidgets[this.state.id], function (widget) { var idForLabel = self.idsForLabels[widget.name]; var $label = idForLabel ? self.$('label[for=' + idForLabel + ']') : $(); - if (core.debug || widget.attrs.help || widget.field.help) { + if (config.debug || widget.attrs.help || widget.field.help) { self._addFieldTooltip(widget, $label); } if (widget.attrs.widget === 'upgrade_boolean') { diff --git a/addons/web/static/src/less/fields.less b/addons/web/static/src/less/fields.less index de4675d6701..9450147e7e8 100644 --- a/addons/web/static/src/less/fields.less +++ b/addons/web/static/src/less/fields.less @@ -49,7 +49,7 @@ // Dropdowns input (many2one, ...) .o_input_dropdown { - .o-flex(1, 0, auto); + .o-flex(1, 1, auto); position: relative; width: 100%; @@ -178,14 +178,6 @@ color: @brand-success; } - // Many2One - &.o_field_many2one { - .o-inline-flex-display(); - > span, > button { - .o-flex(0, 0, auto); - } - } - // Handle widget &.o_row_handle { cursor: ns-resize; diff --git a/addons/web/static/src/less/form_view.less b/addons/web/static/src/less/form_view.less index a73e89317f5..9bfbd6c535e 100644 --- a/addons/web/static/src/less/form_view.less +++ b/addons/web/static/src/less/form_view.less @@ -64,12 +64,6 @@ .o-align-self(center); } } - - .o_row .o_field_many2one, .o_field_many2one.oe_inline { - &.o_with_button:not(:last-child) { // If invisible element as last child, this line has no effect but that's not very important - margin-right: (35px + (@odoo-form-spacing-unit/2)); - } - } } .o_row > div > .o_field_widget { @@ -358,6 +352,17 @@ } } + :not(.o_row) > .o_field_widget, + .o_row > .o_field_widget:last-child { // Note: this does not take care + // of an invisible last-child but + // it does not really matter + // Makes extra buttons (e.g. m2o external button) overflow on the + // right padding of the parent element + .o_input_dropdown { + .o-flex(1, 0, auto); + } + } + &.o_label_nowrap .o_form_label { white-space: nowrap; } diff --git a/addons/web/static/src/less/list_view.less b/addons/web/static/src/less/list_view.less index d541bc8cfc3..82debf1a035 100644 --- a/addons/web/static/src/less/list_view.less +++ b/addons/web/static/src/less/list_view.less @@ -178,9 +178,6 @@ .o_field_widget { min-width: 0; } - .o_with_button .o_input_dropdown { - width: 1%; // Hack so that the sibling button is not overlapped - } .o_input_dropdown, .o_datepicker { > .o_input { padding-right: 15px; // To prevent caret overlapping diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 4ffffd37984..a4773488d30 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -35,11 +35,11 @@ </li> <li t-if="widget.attrs.context || widget.field.context" data-item="context"> <span class="oe_tooltip_technical_title">Context:</span> - <t t-esc="widget.attrs.context || widget.field.context"/> + <t t-esc="widget.attrs.context || JSON.stringify(widget.field.context)"/> </li> <li t-if="widget.attrs.domain || widget.field.domain" data-item="domain"> <span class="oe_tooltip_technical_title">Domain:</span> - <t t-esc="widget.attrs.domain || widget.field.domain"/> + <t t-esc="widget.attrs.domain || JSON.stringify(widget.field.domain)"/> </li> <li t-if="widget.attrs.modifiers and widget.attrs.modifiers != '{}'" data-item="modifiers"> <span class="oe_tooltip_technical_title">Modifiers:</span> diff --git a/addons/web/static/tests/fields/field_utils_tests.js b/addons/web/static/tests/fields/field_utils_tests.js index a0284798553..1f8c27e7192 100644 --- a/addons/web/static/tests/fields/field_utils_tests.js +++ b/addons/web/static/tests/fields/field_utils_tests.js @@ -128,7 +128,15 @@ QUnit.test('format one2many', function(assert) { }); QUnit.test('parse float', function(assert) { - assert.expect(7); + assert.expect(10); + + var originalParameters = _.clone(core._t.database.parameters); + + _.extend(core._t.database.parameters, { + grouping: [3, 0], + decimal_point: '.', + thousands_sep: ',' + }); assert.strictEqual(fieldUtils.parse.float(""), 0); assert.strictEqual(fieldUtils.parse.float("0"), 0); @@ -136,16 +144,64 @@ QUnit.test('parse float', function(assert) { assert.strictEqual(fieldUtils.parse.float("-100.00"), -100); assert.strictEqual(fieldUtils.parse.float("1,000.00"), 1000); assert.strictEqual(fieldUtils.parse.float("1,000,000.00"), 1000000); + assert.strictEqual(fieldUtils.parse.float('1,234.567'), 1234.567); + assert.throws(function () { + fieldUtils.parse.float("1.000.000"); + }, "Throw an exception if it's not a valid number"); - var originalParameters = $.extend(true, {}, core._t.database.parameters); _.extend(core._t.database.parameters, { grouping: [3, 0], decimal_point: ',', thousands_sep: '.' }); + assert.strictEqual(fieldUtils.parse.float('1.234,567'), 1234.567); + assert.throws(function () { + fieldUtils.parse.float("1,000,000"); + }, "Throw an exception if it's not a valid number"); - core._t.database.parameters = originalParameters; + _.extend(core._t.database.parameters, originalParameters); +}); + +QUnit.test('parse integer', function(assert) { + assert.expect(11); + + var originalParameters = _.clone(core._t.database.parameters); + + _.extend(core._t.database.parameters, { + grouping: [3, 0], + decimal_point: '.', + thousands_sep: ',' + }); + + assert.strictEqual(fieldUtils.parse.integer(""), 0); + assert.strictEqual(fieldUtils.parse.integer("0"), 0); + assert.strictEqual(fieldUtils.parse.integer("100"), 100); + assert.strictEqual(fieldUtils.parse.integer("-100"), -100); + assert.strictEqual(fieldUtils.parse.integer("1,000"), 1000); + assert.strictEqual(fieldUtils.parse.integer("1,000,000"), 1000000); + assert.throws(function () { + fieldUtils.parse.integer("1.000.000"); + }, "Throw an exception if it's not a valid number"); + assert.throws(function () { + fieldUtils.parse.integer("1,234.567"); + }, "Throw an exception if the number is a float"); + + _.extend(core._t.database.parameters, { + grouping: [3, 0], + decimal_point: ',', + thousands_sep: '.' + }); + + assert.strictEqual(fieldUtils.parse.integer("1.000.000"), 1000000); + assert.throws(function () { + fieldUtils.parse.integer("1,000,000"); + }, "Throw an exception if it's not a valid number"); + assert.throws(function () { + fieldUtils.parse.integer("1.234,567"); + }, "Throw an exception if the number is a float"); + + _.extend(core._t.database.parameters, originalParameters); }); QUnit.test('parse monetary', function(assert) { diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 7a0937084c3..bc24146f253 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -1063,6 +1063,44 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('list in form: default_get with x2many create and onchange', function (assert) { + assert.expect(1); + + this.data.partner.onchanges.turtles = function (obj) { + assert.deepEqual( + obj.turtles, + [ + [0, false, {turtle_foo: 'blip', id: 2}], + [0, false, {turtle_foo: 'kawa', id: 3}] + ], + "should have properly created the x2many command list"); + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form>' + + '<sheet>' + + '<field name="turtles">' + + '<tree editable="bottom">' + + '<field name="turtle_foo"/>' + + '</tree>' + + '</field>' + + '<field name="int_field"/>' + + '</sheet>' + + '</form>', + mockRPC: function (route, args) { + if (args.method === 'default_get') { + return $.when({turtles: [[6, 0, [2,3]]]}); + } + return this._super.apply(this, arguments); + }, + }); + + form.destroy(); + }); + QUnit.test('list in form: call button in sub view', function (assert) { assert.expect(6); @@ -7498,6 +7536,26 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('field selection with many2ones and special characters', function (assert) { + assert.expect(1); + + // edit the partner with id=4 + this.data.partner.records[2].display_name = '<span>hey</span>'; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<field name="trululu" widget="selection"/>' + + '</form>', + res_id: 1, + viewOptions: {mode: 'edit'}, + }); + assert.strictEqual(form.$('select option[value="4"]').text(), '<span>hey</span>'); + + form.destroy(); + }); + QUnit.test('widget selection on a many2one: domain updated by an onchange', function (assert) { assert.expect(4); @@ -7891,6 +7949,29 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('many2many_tags can load more than 40 records', function (assert) { + assert.expect(1); + + this.data.partner.fields.partner_ids = {string: "Partner", type: "many2many", relation: 'partner'}; + this.data.partner.records[0].partner_ids = []; + for (var i = 15; i < 115; i++) { + this.data.partner.records.push({id: i, display_name: 'walter' + i}); + this.data.partner.records[0].partner_ids.push(i); + } + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<field name="partner_ids" widget="many2many_tags"/>' + + '</form>', + res_id: 1, + }); + assert.strictEqual(form.$('.o_field_widget[name="partner_ids"] > span').length, 100, + 'should have rendered 100 tags'); + form.destroy(); + }); + QUnit.test('field many2many_tags keeps focus when being edited', function (assert) { assert.expect(7); diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index 6a3fdb0fbea..5d2010907d5 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -5977,5 +5977,36 @@ QUnit.module('Views', { form.destroy(); }); + + QUnit.test('proper context stringification in debug mode tooltip', function (assert) { + assert.expect(2); + + var initialDebugMode = config.debug; + config.debug = true; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<sheet>' + + '<field name="product_id" context="{\'lang\': \'en_US\'}"/>' + + '</sheet>' + + '</form>', + }); + + var $field = form.$('[name="product_id"]'); + $field.tooltip('show', true); + $field.trigger($.Event('mouseenter')); + assert.strictEqual($('.oe_tooltip_technical>li[data-item="context"]').length, + 1, 'context should be present for this field'); + assert.strictEqual($('.oe_tooltip_technical>li[data-item="context"]')[0].lastChild.wholeText.trim(), + "{'lang': 'en_US'}", "context should be properly stringified"); + + config.debug = initialDebugMode; + form.destroy(); + }); + + }); }); |
