diff options
| author | Christophe Simonis <chs@odoo.com> | 2017-06-27 14:19:31 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2017-06-27 14:19:31 +0200 |
| commit | dd43071638773dcb58376b79ec7b1456c8b0454d (patch) | |
| tree | fd7935e2aa93de03fa3fe12507f8ef74405a255e /addons/web/static/src | |
| parent | 6501184e6294ffb06e24a0ec8e9666b79a80b59a (diff) | |
| parent | 278e478d55209bd958836fde7fe1c82c555180dc (diff) | |
[MERGE] forward port branch saas-16 up to 278e478d552
Diffstat (limited to 'addons/web/static/src')
| -rw-r--r-- | addons/web/static/src/js/chrome/search_filters.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/chrome/view_manager.js | 2 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/pyeval.js | 16 | ||||
| -rw-r--r-- | addons/web/static/src/js/fields/abstract_field.js | 30 | ||||
| -rw-r--r-- | addons/web/static/src/js/fields/basic_fields.js | 149 | ||||
| -rw-r--r-- | addons/web/static/src/js/fields/relational_fields.js | 22 | ||||
| -rw-r--r-- | addons/web/static/src/js/fields/upgrade_fields.js | 6 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/basic/basic_controller.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/basic/basic_model.js | 18 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/form/form_renderer.js | 1 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/kanban/kanban_column.js | 10 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/kanban/kanban_model.js | 13 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/list/list_renderer.js | 8 | ||||
| -rw-r--r-- | addons/web/static/src/less/datepicker.less | 9 | ||||
| -rw-r--r-- | addons/web/static/src/xml/base.xml | 18 |
15 files changed, 196 insertions, 114 deletions
diff --git a/addons/web/static/src/js/chrome/search_filters.js b/addons/web/static/src/js/chrome/search_filters.js index 69c38056e4a..a0e3c1c077f 100644 --- a/addons/web/static/src/js/chrome/search_filters.js +++ b/addons/web/static/src/js/chrome/search_filters.js @@ -155,6 +155,7 @@ ExtendedSearchProposition.Field = Widget.extend({ ExtendedSearchProposition.Char = ExtendedSearchProposition.Field.extend({ tagName: 'input', + className: 'o_input', attributes: { type: 'text' }, @@ -186,7 +187,7 @@ ExtendedSearchProposition.DateTime = ExtendedSearchProposition.Field.extend({ {value: "∄", text: _lt("is not set")} ], get_value: function (index) { - return this["datewidget_" + (index || 0)].get_value(); + return this["datewidget_" + (index || 0)].getValue(); }, get_domain: function (field, operator) { switch (operator.value) { @@ -251,6 +252,7 @@ ExtendedSearchProposition.Date = ExtendedSearchProposition.DateTime.extend({ ExtendedSearchProposition.Integer = ExtendedSearchProposition.Field.extend({ tagName: 'input', + className: 'o_input', attributes: { type: 'number', value: '0', diff --git a/addons/web/static/src/js/chrome/view_manager.js b/addons/web/static/src/js/chrome/view_manager.js index 8d760c568b0..d0beb5bf3dd 100644 --- a/addons/web/static/src/js/chrome/view_manager.js +++ b/addons/web/static/src/js/chrome/view_manager.js @@ -307,7 +307,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, { self.trigger('switch_mode', view_type, view_options); }).fail(function(e) { if (!(e && e.code === 200 && e.data.exception_type)) { - self.do_warn(_t("Error"), view.controller.display_name + _t(" view couldn't be loaded")); + self.do_warn(_t("Error"), view.label + _t(" view couldn't be loaded")); } // Restore internal state self.active_view = old_view; diff --git a/addons/web/static/src/js/core/pyeval.js b/addons/web/static/src/js/core/pyeval.js index 75efc9e813f..502c6a4ce90 100644 --- a/addons/web/static/src/js/core/pyeval.js +++ b/addons/web/static/src/js/core/pyeval.js @@ -435,6 +435,22 @@ datetime.datetime = py.type('datetime', null, { this.second, this.microsecond / 1000); }, + __add__: function (other) { + if (!py.PY_isInstance(other, datetime.timedelta)) { + return py.NotImplemented; + } + var s = tmxxx(this.year, this.month, this.day + other.days, this.hour, this.minute, this.second + other.seconds); + return datetime.datetime.fromJSON(s.year, s.month, s.day, s.hour, s.minute, s.second); + }, + __sub__: function (other) { + if (py.PY_isInstance(other, datetime.timedelta)) { + return py.PY_add(this, py.PY_negative(other)); + } + return py.NotImplemented; + }, + fromJSON: function (year, month, day, hour, minute, second) { + return py.PY_call(datetime.datetime, [year, month, day, hour, minute, second]); + }, }); datetime.date = py.type('date', null, { diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js index 492d9f26552..52e0aef77e4 100644 --- a/addons/web/static/src/js/fields/abstract_field.js +++ b/addons/web/static/src/js/fields/abstract_field.js @@ -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 @@ -426,22 +440,18 @@ var AbstractField = Widget.extend({ this.trigger_up('navigation_move', {direction: 'cancel'}); break; case $.ui.keyCode.UP: - ev.preventDefault(); ev.stopPropagation(); this.trigger_up('navigation_move', {direction: 'up'}); break; case $.ui.keyCode.RIGHT: - ev.preventDefault(); ev.stopPropagation(); this.trigger_up('navigation_move', {direction: 'right'}); break; case $.ui.keyCode.DOWN: - ev.preventDefault(); ev.stopPropagation(); this.trigger_up('navigation_move', {direction: 'down'}); break; case $.ui.keyCode.LEFT: - ev.preventDefault(); ev.stopPropagation(); this.trigger_up('navigation_move', {direction: 'left'}); break; diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index d4013a8bcdb..82970255b3e 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -285,11 +285,15 @@ var InputField = DebouncedField.extend({ */ _onNavigationMove: function (ev) { this._super.apply(this, arguments); - var input = this.$input[0]; - var selecting = (input.selectionEnd !== input.selectionStart); - if ((ev.data.direction === "left" && (selecting || input.selectionStart !== 0)) - || (ev.data.direction === "right" && (selecting || input.selectionStart !== input.value.length))) { - ev.stopPropagation(); + + // the following code only makes sense in edit mode, with an input + if (this.mode === 'edit') { + var input = this.$input[0]; + var selecting = (input.selectionEnd !== input.selectionStart); + if ((ev.data.direction === "left" && (selecting || input.selectionStart !== 0)) + || (ev.data.direction === "right" && (selecting || input.selectionStart !== input.value.length))) { + ev.stopPropagation(); + } } }, }); @@ -322,6 +326,14 @@ var FieldDate = InputField.extend({ 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. * * @override @@ -396,17 +408,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 +449,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 +480,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 +559,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({ @@ -686,6 +667,11 @@ var FieldBoolean = AbstractField.extend({ this.$input.prop('checked', !this.value); this._setValue(!this.value); return; + case $.ui.keyCode.UP: + case $.ui.keyCode.RIGHT: + case $.ui.keyCode.DOWN: + case $.ui.keyCode.LEFT: + ev.preventDefault(); } this._super.apply(this, arguments); }, @@ -734,7 +720,7 @@ var FieldInteger = InputField.extend({ } return value; } - return field_utils.format.integer(value, this.field); + return this._super.apply(this, arguments); }, }); @@ -771,31 +757,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, { @@ -853,6 +819,19 @@ var FieldEmail = InputField.extend({ }, //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Returns the associated link. + * + * @override + */ + getFocusableElement: function () { + return this.mode === 'readonly' ? this.$el : this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- @@ -887,6 +866,17 @@ var FieldPhone = FieldEmail.extend({ this.tagName = 'span'; } }, + /** + * Returns the associated link only if there is one. + * + * @override + */ + getFocusableElement: function () { + if (this._canCall()) { + return this._super.apply(this, arguments); + } + return $(); + }, //-------------------------------------------------------------------------- // Private @@ -941,6 +931,19 @@ var UrlWidget = InputField.extend({ }, //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Returns the associated link. + * + * @override + */ + getFocusableElement: function () { + return this.mode === 'readonly' ? this.$el : this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- @@ -1041,7 +1044,13 @@ var AbstractFieldBinary = AbstractField.extend({ set_filename: function (value) { var filename = this.attrs.filename; if (filename && filename in this.fields) { - this.trigger_up('update_field', { name: filename, value: value }); + var changes = {}; + changes[filename] = value; + this.trigger_up('field_changed', { + dataPointID: this.dataPointID, + changes: changes, + viewType: this.viewType, + }); } }, on_clear: function () { diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 3b47b8c64b0..419d1f5f646 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -124,7 +124,7 @@ var FieldMany2One = AbstractField.extend({ * @returns {jQuery} */ getFocusableElement: function () { - return this.$input || $(); + return this.mode === 'edit' && this.$input || this.$el; }, /** * TODO @@ -206,6 +206,17 @@ var FieldMany2One = AbstractField.extend({ return []; }, /** + * Returns the display_name from a string which contains it but was altered + * as a result of the show_address option using a horrible hack. + * + * @private + * @param {string} value + * @returns {string} display_name without show_address mess + */ + _getDisplayName: function (value) { + return value.split('\n')[0]; + }, + /** * @private * @param {string} name */ @@ -244,7 +255,7 @@ var FieldMany2One = AbstractField.extend({ // and hope for the best that noone tries to uses this mechanism to do // something else. if (this.nodeOptions.always_reload) { - value = value.split('\n')[0]; + value = this._getDisplayName(value); } this.$input.val(value); if (!this.autocomplete_bound) { @@ -302,7 +313,7 @@ var FieldMany2One = AbstractField.extend({ .then(function (result) { // possible selections for the m2o var values = _.map(result, function (x) { - x[1] = x[1].split("\n")[0]; + x[1] = self._getDisplayName(x[1]); return { label: _.str.escapeHTML(x[1].trim()) || data.noDisplayContent, value: x[1], @@ -478,7 +489,7 @@ var FieldMany2One = AbstractField.extend({ _onInputKeyup: function () { if (this.$input.val() === "") { this.reinitialize(false); - } else if (this.m2o_value !== this.$input.val()) { + } else if (this._getDisplayName(this.m2o_value) !== this.$input.val()) { this.floating = true; this._updateExternalButton(); } @@ -1585,7 +1596,7 @@ var FormFieldMany2ManyTags = FieldMany2ManyTags.extend({ var KanbanFieldMany2ManyTags = FieldMany2ManyTags.extend({ _render: function () { var self = this; - this.$el.addClass('o_field_many2manytags o_kanban_tags'); + this.$el.empty().addClass('o_field_many2manytags o_kanban_tags'); _.each(this.value.data, function (m2m) { // 10th color is invisible if ('color' in m2m.data && m2m.data.color !== 10) { @@ -1763,6 +1774,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] === ''; diff --git a/addons/web/static/src/js/fields/upgrade_fields.js b/addons/web/static/src/js/fields/upgrade_fields.js index ecf267e03c1..ae8cae56ef3 100644 --- a/addons/web/static/src/js/fields/upgrade_fields.js +++ b/addons/web/static/src/js/fields/upgrade_fields.js @@ -6,6 +6,7 @@ odoo.define('web.upgrade_widgets', function (require) { * When checked, an upgrade popup is showed to the user. */ +var AbstractField = require('web.AbstractField'); var basic_fields = require('web.basic_fields'); var core = require('web.core'); var Dialog = require('web.Dialog'); @@ -58,6 +59,7 @@ var AbstractFieldUpgrade = { * Opens the Upgrade dialog. * * @private + * @returns {Dialog} the instance of the opened Dialog */ _openDialog: function () { var message = $(QWeb.render('EnterpriseUpgrade')); @@ -75,7 +77,7 @@ var AbstractFieldUpgrade = { }, ]; - new Dialog(this, { + return new Dialog(this, { size: 'medium', buttons: buttons, $content: $('<div>', { @@ -122,7 +124,7 @@ var AbstractFieldUpgrade = { var UpgradeBoolean = FieldBoolean.extend(AbstractFieldUpgrade, { supportedFieldTypes: [], - events: _.extend({}, FieldBoolean.prototype.events, { + events: _.extend({}, AbstractField.prototype.events, { 'click input': '_onInputClicked', }), diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js index af4ca7d7a08..67a4fa504d8 100644 --- a/addons/web/static/src/js/views/basic/basic_controller.js +++ b/addons/web/static/src/js/views/basic/basic_controller.js @@ -423,7 +423,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { }); if (!options.stayInEdit) { saveDef = saveDef.then(function (fieldNames) { - var def = fieldNames.length ? self._confirmSave(recordID) : self._setMode('readonly'); + var def = fieldNames.length ? self._confirmSave(recordID) : self._setMode('readonly', recordID); return def.then(function () { return fieldNames; }); @@ -534,6 +534,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { * @param {function} ev.data.action the function to execute in the mutex */ _onMutexify: function (ev) { + ev.stopPropagation(); // prevent other controllers from handling this request this.mutex.exec(ev.data.action); }, /** @@ -578,6 +579,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { * @param {OdooEvent} event */ _onTranslate: function (event) { + event.stopPropagation(); var record = this.model.get(event.data.id, {raw: true}); this._rpc({ route: '/web/dataset/call_button', 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 d897a74c696..716624f368b 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -1399,7 +1399,8 @@ var BasicModel = AbstractModel.extend({ * @param {string} [optinos.viewType] the type of view for which the record * is fetched (usefull to load the adequate fields), by defaults, uses * record.viewType - * @returns {Deferred -> Object} resolves to the record + * @returns {Deferred -> Object} resolves to the record or is rejected in + * case no id given were valid ids */ _fetchRecord: function (record, options) { var self = this; @@ -1412,6 +1413,9 @@ var BasicModel = AbstractModel.extend({ context: _.extend({}, record.context, {bin_size: true}), }) .then(function (result) { + if (result.length === 0) { + return $.Deferred().reject(); + } result = result[0]; record.data = _.extend({}, record.data, result); }) @@ -2194,12 +2198,6 @@ var BasicModel = AbstractModel.extend({ if (!field || field.name === 'id') { continue; } - if (field.type === 'float' || - field.type === 'integer' || - field.type === 'monetary') { - context[fieldName] = context[fieldName] || 0; - continue; - } if (field.type === 'date' || field.type === 'datetime') { if (context[fieldName]) { context[fieldName] = JSON.parse(JSON.stringify(context[fieldName])); @@ -2445,7 +2443,11 @@ var BasicModel = AbstractModel.extend({ var fieldName = fieldNames[i]; if (!(fieldName in result)) { var field = params.fields[fieldName]; - if (field.type === 'one2many' || field.type === 'many2many') { + if (field.type === 'float' || + field.type === 'integer' || + field.type === 'monetary') { + result[fieldName] = 0; + } else if (field.type === 'one2many' || field.type === 'many2many') { result[fieldName] = []; } else { result[fieldName] = null; 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 08f28438368..25b72e4fb5a 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -828,6 +828,7 @@ var FormRenderer = BasicRenderer.extend({ */ _onNavigationMove: function (ev) { ev.stopPropagation(); + var index; if (ev.data.direction === "next") { index = this.allFieldWidgets[this.state.id].indexOf(ev.data.target); diff --git a/addons/web/static/src/js/views/kanban/kanban_column.js b/addons/web/static/src/js/views/kanban/kanban_column.js index 99c9500d210..93165066614 100644 --- a/addons/web/static/src/js/views/kanban/kanban_column.js +++ b/addons/web/static/src/js/views/kanban/kanban_column.js @@ -40,8 +40,6 @@ var KanbanColumn = Widget.extend({ var value = data.value; this.id = data.res_id || value; - // todo: handle group_by_m2o (nameget) - this.title = value === undefined ? _t('Undefined') : value; this.folded = !data.isOpen; this.has_active_field = 'active' in data.fields; this.size = data.count; @@ -62,6 +60,14 @@ var KanbanColumn = Widget.extend({ this.record_options = _.clone(recordOptions); + if (options.grouped_by_m2o) { + // For many2one, a false value means that the field is not set. + this.title = value ? value : _t('Undefined'); + } else { + // False and 0 might be valid values for these fields. + this.title = value === undefined ? _t('Undefined') : value; + } + if (options.group_by_tooltip) { this.tooltipInfo = _.map(options.group_by_tooltip, function (help, field) { return (data.tooltipData && data.tooltipData[field] && "<div>" + help + "<br>" + data.tooltipData[field] + "</div>") || ''; diff --git a/addons/web/static/src/js/views/kanban/kanban_model.js b/addons/web/static/src/js/views/kanban/kanban_model.js index cd69889bda9..3977ac0df87 100644 --- a/addons/web/static/src/js/views/kanban/kanban_model.js +++ b/addons/web/static/src/js/views/kanban/kanban_model.js @@ -153,7 +153,7 @@ var KanbanModel = BasicModel.extend({ }).then(function () { // Remove record from its current group var old_group; - for (var i = 0; i < parent.count; i++) { + for (var i = 0; i < parent.data.length; i++) { old_group = self.localData[parent.data[i]]; var index = _.indexOf(old_group.data, recordID); if (index >= 0) { @@ -244,9 +244,14 @@ var KanbanModel = BasicModel.extend({ if (groupedByField.type !== 'many2one') { return $.when(); } - var groupIds = _.map(list.data, function (id) { - return self.get(id, {raw: true}).res_id; - }); + var groupIds = _.reduce(list.data, function (groupIds, id) { + var res_id = self.get(id, {raw: true}).res_id; + // The field on which we are grouping might not be set on all records + if (res_id) { + groupIds.push(res_id); + } + return groupIds; + }, []); var tooltipFields = []; var groupedByFieldInfo = list.fieldsInfo.kanban[list.groupedBy[0]]; if (groupedByFieldInfo && groupedByFieldInfo.options) { diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js index f713ec6e66e..a917781b580 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -271,10 +271,14 @@ var ListRenderer = BasicRenderer.extend({ _renderButton: function (record, node) { var $button = $('<button>', { type: 'button', - class: 'o_icon_button', title: node.attrs.string, }); - $button.append($('<i>', {class: 'fa ' + node.attrs.icon})); + if (node.attrs.icon) { + $button.addClass('o_icon_button'); + $button.append($('<i>', {class: 'fa ' + node.attrs.icon})); + } else { + $button.text(node.attrs.string); + } this._registerModifiers(node, record, $button); diff --git a/addons/web/static/src/less/datepicker.less b/addons/web/static/src/less/datepicker.less index bc36f8c5a43..99c909efacc 100644 --- a/addons/web/static/src/less/datepicker.less +++ b/addons/web/static/src/less/datepicker.less @@ -28,6 +28,13 @@ } } +// Need to put datetimepicker widget above everything else +// The '.dropdown-menu' part is needed to override default bootstrap rule as +// this file is unfortunately placed in common assets. +.bootstrap-datetimepicker-widget.dropdown-menu { + z-index: @zindex-modal + 1; +} + .datepicker { .table-condensed { > thead { @@ -66,5 +73,5 @@ } } } - } + } } diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index b673690d379..78cc5fd9ad5 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -961,7 +961,8 @@ <span t-if="attrs.help">?</span> </label> <div t-att-style="style"> - <input type="text" size="15" t-att-name="attrs.name" + <input type="text" size="15" class="o_input" + t-att-name="attrs.name" t-att-autofocus="attrs.default_focus === '1' ? 'autofocus' : undefined" t-att-id="element_id" t-att-value="defaults[attrs.name] || ''"/> @@ -990,7 +991,9 @@ <span t-if="attrs.help">?</span> </label> <div t-att-style="style"> - <select t-att-name="attrs.name" t-att-id="element_id" + <select class="o_input" + t-att-name="attrs.name" + t-att-id="element_id" t-att-autofocus="attrs.default_focus === '1' || undefined"> <option t-if="prepend_empty"/> <t t-foreach="attrs.selection" t-as="option"> @@ -1012,7 +1015,7 @@ <li class="o_filter_condition"> <span class="o_or_filter">or</span> <span> - <select class="o_searchview_extended_prop_field"> + <select class="o_input o_searchview_extended_prop_field"> <t t-foreach="widget.attrs.fields" t-as="field"> <option t-att="{'selected': field === widget.attrs.selected ? 'selected' : null}" t-att-value="field.name"> @@ -1022,18 +1025,19 @@ </select> <span class="o_searchview_extended_delete_prop fa fa-trash-o"/> </span> - <select class="o_searchview_extended_prop_op"/> + <select class="o_input o_searchview_extended_prop_op"/> <span class="o_searchview_extended_prop_value"/> </li> </t> <t t-name="SearchView.extended_search.proposition.float"> - <input t-att-type="widget.decimal_point === '.' ? 'number' : 'text'" + <input class="o_input" + t-att-type="widget.decimal_point === '.' ? 'number' : 'text'" t-attf-title="Number using #{widget.decimal_point || '.' } as decimal separator." t-attf-pattern="[0-9]+([\\#{widget.decimal_point || '.' }][0-9]+)?" t-attf-value="0#{widget.decimal_point || '.' }0" step="0.01"/> </t> <t t-name="SearchView.extended_search.proposition.selection"> - <select> + <select class="o_input"> <option t-foreach="widget.field.selection" t-as="element" t-att-value="element[0]"> <t t-esc="element[1]"/> </option> @@ -1051,7 +1055,7 @@ </ul> </div> <t t-name="GroupByMenuSelector"> - <li><select class="o_add_group o_group_selector"> + <li><select class="o_input o_add_group o_group_selector"> <t t-foreach="groupableFields" t-as="field"> <option t-att-data-name="field.name"><t t-esc="field.string"/></option> </t> |
