summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2017-10-13 13:13:11 +0200
committerChristophe Simonis <chs@odoo.com>2017-10-13 13:13:11 +0200
commit1da064a1d34b48ff23d8cdf5a1c189c8bbb4eb64 (patch)
treed832553531790ca342cda3b03e6c44097717ff43 /addons/web/static
parent119ae9b5dcaea577bfd91b36565b764990b9de02 (diff)
parent8ee9024a4c888647df9e18861fe5e58b68947469 (diff)
[MERGE] forward port branch saas-16 up to 8ee9024a4c8
Diffstat (limited to 'addons/web/static')
-rw-r--r--addons/web/static/src/js/fields/field_utils.js41
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js4
-rw-r--r--addons/web/static/src/js/views/abstract_view.js3
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js29
-rw-r--r--addons/web/static/src/js/views/form/form_renderer.js2
-rw-r--r--addons/web/static/src/less/fields.less10
-rw-r--r--addons/web/static/src/less/form_view.less17
-rw-r--r--addons/web/static/src/less/list_view.less3
-rw-r--r--addons/web/static/src/xml/base.xml4
-rw-r--r--addons/web/static/tests/fields/field_utils_tests.js62
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js81
-rw-r--r--addons/web/static/tests/views/form_tests.js31
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();
+ });
+
+
});
});