From f2501bfd3c01bd8d98897da70af537a3526cdaaf Mon Sep 17 00:00:00 2001 From: Dennis Sluijk Date: Thu, 22 Feb 2018 12:01:57 +0100 Subject: [FIX] web: return dialog in web.CrashManager functions This is more a architecture fix than a bug fix. With this commit, it is easier to modify the behavior of the dialog using the super function. like this: this._super.apply(this, arguments).opened(...) PR #23255 --- addons/web/static/src/js/services/crash_manager.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/services/crash_manager.js b/addons/web/static/src/js/services/crash_manager.js index 9c7914eeebf..ccc7e089e65 100644 --- a/addons/web/static/src/js/services/crash_manager.js +++ b/addons/web/static/src/js/services/crash_manager.js @@ -103,7 +103,7 @@ var CrashManager = core.Class.extend({ if (!this.active) { return; } - new Dialog(this, { + return new Dialog(this, { size: 'medium', title: _.str.capitalize(error.type || error.message) || _t("Odoo Warning"), subtitle: error.data.title, @@ -149,10 +149,10 @@ var CrashManager = core.Class.extend({ clipboard.destroy(); }); - dialog.open(); + return dialog.open(); }, show_message: function(exception) { - this.show_error({ + return this.show_error({ type: _t("Odoo Client Error"), message: exception, data: {debug: ""} -- cgit v1.2.3 From ad1cceb4fbd2f054af647833bd34e4e06012bb6c Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Tue, 20 Feb 2018 11:01:22 +0100 Subject: [FIX] web: one2many and onchange corner case issue Let's assume a one2many list view inside a form view, displaying a single char field A. There is an onchange on the form view returning a command 0 (CREATE) for the one2many. The command 0 only specifies a value for field A. The one2many list isn't editable, so when a sub-record is clicked, it is open in a form view (in a dialog). In this form view, an x2many field B is displayed in a sub list or kanban view. Before this rev., it crashed when the user tried to open the created one2many sub-record (returned by the onchange) in the form view, because of the presence of the x2many field B, which wasn't correctly processed by the BasicModel (its value was undefined, whereas it must be a valid, empty, dataPoint). Moreover, the limit of the x2many field B wasn't set, so a pager was displayed as soon as it contained at least one record. Fixes #22050 --- .../web/static/src/js/views/basic/basic_model.js | 143 +++++++++++++-------- addons/web/static/src/js/views/basic/basic_view.js | 7 +- 2 files changed, 97 insertions(+), 53 deletions(-) (limited to 'addons/web/static/src') 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 6eef41f149d..e2cc60f7fc4 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -189,6 +189,89 @@ var BasicModel = AbstractModel.extend({ return id; }); }, + /** + * Add and process default values for a given record. Those values are + * parsed and stored in the '_changes' key of the record. For relational + * fields, sub-dataPoints are created, and missing relational data is + * fetched. Also generate default values for fields with no given value. + * Typically, this function is called with the result of a 'default_get' + * RPC, to populate a newly created dataPoint. It may also be called when a + * one2many subrecord is open in a form view (dialog), to generate the + * default values for the fields displayed in the o2m form view, but not in + * the list or kanban (mainly to correctly create sub-dataPoints for + * relational fields). + * + * @param {string} recordID local id for a record + * @param {Object} values dict of default values for the given record + * @param {Object} [options] + * @param {string} [options.viewType] current viewType. If not set, we will + * assume main viewType from the record + * @param {Array} [options.fieldNames] list of field names for which a + * default value must be generated (used to complete the values dict) + * @returns {Deferred} + */ + applyDefaultValues: function (recordID, values, options) { + options = options || {}; + var record = this.localData[recordID]; + var viewType = options.viewType || record.viewType; + var fieldNames = options.fieldNames || Object.keys(record.fieldsInfo[viewType]); + var field; + var fieldName; + record._changes = record._changes || {}; + + // fill default values for missing fields + for (var i = 0; i < fieldNames.length; i++) { + fieldName = fieldNames[i]; + if (!(fieldName in values) && !(fieldName in record._changes)) { + field = record.fields[fieldName]; + if (field.type === 'float' || + field.type === 'integer' || + field.type === 'monetary') { + values[fieldName] = 0; + } else if (field.type === 'one2many' || field.type === 'many2many') { + values[fieldName] = []; + } else { + values[fieldName] = null; + } + } + } + + // parse each value and create dataPoints for relational fields + var defs = []; + for (fieldName in values) { + field = record.fields[fieldName]; + if (!field) { + continue; // ignore values for unknown fields + } + record.data[fieldName] = null; + var dp; + if (field.type === 'many2one' && values[fieldName]) { + dp = this._makeDataPoint({ + context: record.context, + data: {id: values[fieldName]}, + modelName: field.relation, + parentID: record.id, + }); + record._changes[fieldName] = dp.id; + } else if (field.type === 'reference' && values[fieldName]) { + var ref = values[fieldName].split(','); + dp = this._makeDataPoint({ + context: record.context, + data: {id: parseInt(ref[1])}, + modelName: ref[0], + parentID: record.id, + }); + defs.push(this._fetchNameGet(dp)); + record._changes[fieldName] = dp.id; + } else if (field.type === 'one2many' || field.type === 'many2many') { + defs.push(this._processX2ManyCommands(record, fieldName, values[fieldName], options)); + } else { + record._changes[fieldName] = this._parseServerValue(field, values[fieldName]); + } + } + + return $.when.apply($, defs); + }, /** * Onchange RPCs may return values for fields that are not in the current * view. Those fields might even be unknown when the onchange returns (e.g. @@ -1397,6 +1480,7 @@ var BasicModel = AbstractModel.extend({ list = self._makeDataPoint({ fields: view ? view.fields : fieldInfo.relatedFields, fieldsInfo: view ? view.fieldsInfo : fieldInfo.fieldsInfo, + limit: fieldInfo.limit, modelName: field.relation, parentID: record.id, static: true, @@ -3227,27 +3311,8 @@ var BasicModel = AbstractModel.extend({ context: params.context, }) .then(function (result) { - // fill default values for missing fields - for (var i = 0; i < fieldNames.length; i++) { - var fieldName = fieldNames[i]; - if (!(fieldName in result)) { - var field = params.fields[fieldName]; - 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; - } - } - } - - var data = {}; var record = self._makeDataPoint({ modelName: modelName, - data: data, fields: params.fields, fieldsInfo: params.fieldsInfo, context: params.context, @@ -3256,37 +3321,7 @@ var BasicModel = AbstractModel.extend({ viewType: params.viewType, }); - var defs = []; - _.each(fieldNames, function (name) { - var field = params.fields[name]; - data[name] = null; - record._changes = record._changes || {}; - var dp; - if (field.type === 'many2one' && result[name]) { - dp = self._makeDataPoint({ - context: record.context, - data: {id: result[name]}, - modelName: field.relation, - parentID: record.id, - }); - record._changes[name] = dp.id; - } else if (field.type === 'reference' && result[name]) { - var ref = result[name].split(','); - dp = self._makeDataPoint({ - context: record.context, - data: {id: parseInt(ref[1])}, - modelName: ref[0], - parentID: record.id, - }); - defs.push(self._fetchNameGet(dp)); - record._changes[name] = dp.id; - } else if (field.type === 'one2many' || field.type === 'many2many') { - defs.push(self._processX2ManyCommands(record, name, result[name])); - } else { - record._changes[name] = self._parseServerValue(field, result[name]); - } - }); - return $.when.apply($, defs) + return self.applyDefaultValues(record.id, result) .then(function () { var def = $.Deferred(); self._performOnChange(record, fields_key).always(function () { @@ -3469,13 +3504,17 @@ var BasicModel = AbstractModel.extend({ * @param {Object} record * @param {string} fieldName * @param {Array[Array]} commands + * @param {Object} [options] + * @param {string} [options.viewType] current viewType. If not set, we will + * assume main viewType from the record * @returns {Deferred} */ - _processX2ManyCommands: function (record, fieldName, commands) { + _processX2ManyCommands: function (record, fieldName, commands, options) { var self = this; + options = options || {}; var defs = []; var field = record.fields[fieldName]; - var fieldInfo = record.fieldsInfo[record.viewType][fieldName]; + var fieldInfo = record.fieldsInfo[options.viewType || record.viewType][fieldName]; var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode]; var fieldsInfo = view ? view.fieldsInfo : fieldInfo.fieldsInfo; var fields = view ? view.fields : fieldInfo.relatedFields; diff --git a/addons/web/static/src/js/views/basic/basic_view.js b/addons/web/static/src/js/views/basic/basic_view.js index 8a91e2b90c7..c76020cacac 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -120,7 +120,12 @@ var BasicView = AbstractView.extend({ // (because those fields were unknow at that time). So we ask // the model to process them. def = this.model.applyRawChanges(record.id, viewType).then(function () { - if (!self.model.isNew(record.id)) { + if (self.model.isNew(record.id)) { + return self.model.applyDefaultValues(record.id, {}, { + fieldNames: fieldNames, + viewType: viewType, + }); + } else { return self.model.reload(record.id, { fieldNames: fieldNames, keepChanges: true, -- cgit v1.2.3 From a16013be6636db58a4252fafd67033c6643a1e48 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Fri, 23 Feb 2018 13:48:20 +0100 Subject: [FIX] web: multi pages ordered o2m with onchange Let's assume a one2many displayed over several pages with a default order such that a record, say R, that would normally be on page 1 is actually on another page. Moreover, there is an onchange on that one2many which converts all commands 4 (LINK_TO) into commands 1 (UPDATE), which is standard in the ORM. When the first line of the one2many was edited, the record R was fully loaded, whereas it shouldn't have been (as it is not on the current page). So basically, a useless read was performed. Moreover, if there was an x2many field in the one2many list, a crash occurred because relational data of R that shouldn't have been read wasn't postprocessed by the BasicModel. The useless read occurred because the one2many subrecords weren't correctly sorted before checking if some of them/some of their fields should be read. This is not the case anymore. OPW-1816800 --- addons/web/static/src/js/views/basic/basic_model.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'addons/web/static/src') 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 e2cc60f7fc4..18193a5c364 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -3804,14 +3804,14 @@ var BasicModel = AbstractModel.extend({ // for multi-pages list datapoints, we might need to read the // order field first to apply the order on all pages - if (list.res_ids.length > list.limit) { - if (!list.orderedResIDs && list.orderedBy.length) { + if (list.res_ids.length > list.limit && list.orderedBy.length) { + if (!list.orderedResIDs) { var fieldNames = _.pluck(list.orderedBy, 'name'); def = this._readMissingFields(list, _.filter(list.res_ids, _.isNumber), fieldNames); - def.then(function () { - self._sortList(list); - }); } + def.then(function () { + self._sortList(list); + }); } return def.then(function () { var resIDs = []; -- cgit v1.2.3 From 742ac28a4476b01cab0ed7c053bcd79e07595ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20K=C3=BChn?= Date: Fri, 23 Feb 2018 10:16:30 +0100 Subject: [FIX] web: doAction always resolved with the action Before this commit, doAction returns a deferred that is resolved when the action is performed (the exact moment depends of the type of the action). However, the deferred was not resolved with the action. This is misleading, hence a traceback in the web_client when clicking on 'Run JS Tests' in debug mode (enterprise): after doAction, the web_client should hide the home_menu depending on the type of the action. With this commit, any doAction returns a deferred that is resolved with the performed action. --- addons/web/static/src/js/chrome/action_manager.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js index 1651e5d72d9..236d7517d04 100644 --- a/addons/web/static/src/js/chrome/action_manager.js +++ b/addons/web/static/src/js/chrome/action_manager.js @@ -140,9 +140,10 @@ var ActionManager = Widget.extend({ * is useful when we come from a loadState()) * @param {boolean} [options.replace_last_action=false] set to true to * replace last part of the breadcrumbs with the action - * @return {Deferred} resolved when the action is loaded and appended to the - * DOM ; rejected if the action can't be executed (e.g. if doAction has - * been called to execute another action before this one was complete). + * @return {$.Deferred} resolved with the action when the action is + * loaded and appended to the DOM ; rejected if the action can't be + * executed (e.g. if doAction has been called to execute another action + * before this one was complete). */ doAction: function (action, options) { var self = this; @@ -179,7 +180,9 @@ var ActionManager = Widget.extend({ self._preprocessAction(action, options); - return self._handleAction(action, options); + return self._handleAction(action, options).then(function () { + return action; + }); }); }, /** -- cgit v1.2.3 From 034c075e7e58ad9e5a2dce34782106c0dbd1c786 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Mon, 26 Feb 2018 08:48:19 +0100 Subject: [FIX] web: default_get returning unknown field Before this rev., it crashed when trying to create records of the res.users model. This is due to commit ad1cceb. The 'default_get' of 'res.users' always returns a value for field 'groups_id', whereas the docstring of 'default_get' states that it only returns default values for fields given in arguments. Rev. ad1cceb changed a bit the way the result of 'default_get' was processed, by iterating over the values instead of over the fields in the view. So, before ad1cceb, 'groups_id' was simply ignored, and after, the BasicModel tried to process it and it crashed when it attempted to retrieve its fieldsInfo, as this field isn't in the view. This rev. restores the previous behavior. Fixes #23303 Fixes #23305 --- addons/web/static/src/js/views/basic/basic_model.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'addons/web/static/src') 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 18193a5c364..c7f61edd541 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -219,6 +219,10 @@ var BasicModel = AbstractModel.extend({ var fieldName; record._changes = record._changes || {}; + // ignore values for non requested fields (for instance, fields that are + // not in the view) + values = _.pick(values, fieldNames); + // fill default values for missing fields for (var i = 0; i < fieldNames.length; i++) { fieldName = fieldNames[i]; @@ -240,9 +244,6 @@ var BasicModel = AbstractModel.extend({ var defs = []; for (fieldName in values) { field = record.fields[fieldName]; - if (!field) { - continue; // ignore values for unknown fields - } record.data[fieldName] = null; var dp; if (field.type === 'many2one' && values[fieldName]) { -- cgit v1.2.3 From 462c3956d9988e9477de48788d370be1b1c4f107 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Mon, 26 Feb 2018 14:50:55 +0100 Subject: [FIX] web: NotificationManager: warnings style Warnings are styled a bit differently than notifications, as their background is red (thanks to classname 'o_error' on the warning root element). This classname isn't correctly added since rev. 920df59, which changed the root element of the 'Notification' template, which is extended by the 'Warning' template, making qweb.js fail to add the 'o_error' classname. --- addons/web/static/src/js/widgets/notification.js | 8 +++++++- addons/web/static/src/xml/base.xml | 1 - 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/widgets/notification.js b/addons/web/static/src/js/widgets/notification.js index 6e11b4c8647..a162c6bc330 100644 --- a/addons/web/static/src/js/widgets/notification.js +++ b/addons/web/static/src/js/widgets/notification.js @@ -1,7 +1,6 @@ odoo.define('web.notification', function (require) { "use strict"; -var core = require('web.core'); var Widget = require('web.Widget'); var Notification = Widget.extend({ @@ -47,6 +46,13 @@ var Notification = Widget.extend({ var Warning = Notification.extend({ template: 'Warning', + /** + * @override + */ + start: function () { + this.$el.addClass('o_error'); + return this._super.apply(this, arguments); + }, }); var NotificationManager = Widget.extend({ diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index c0386a0a87f..87a9ca2c989 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -125,7 +125,6 @@ - $(this).addClass('o_error'); -- cgit v1.2.3 From d4126f72788af03d33c64c11b3cbb97f431580bc Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Wed, 28 Feb 2018 08:47:54 +0100 Subject: [FIX] web: DomainSelectorDialog: prevent overflow The DomainSelectorDialog behaves differently from classical dialogs in terms of height and overflow: (1) its height can be larger than the screen height, and (2) its content is never scrollable (overflow visible). However, before this rev., the 'height: auto' rule ensuring (1) didn't apply, so the dialog's height was limited to the screen's height, and when it had to much content to display, it overflowed. It didn't work because the code tried to apply the rule too soon, so the .modal-dialog element was not yet available. Fixes #23292 --- addons/web/static/src/js/widgets/domain_selector_dialog.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/widgets/domain_selector_dialog.js b/addons/web/static/src/js/widgets/domain_selector_dialog.js index 5f41973951d..46b54089c5f 100644 --- a/addons/web/static/src/js/widgets/domain_selector_dialog.js +++ b/addons/web/static/src/js/widgets/domain_selector_dialog.js @@ -40,7 +40,11 @@ return Dialog.extend({ this.domainSelector = new DomainSelector(this, model, domain, options); }, start: function () { - this.$el.css("overflow", "visible").closest(".modal-dialog").css("height", "auto"); // This restores default modal height (bootstrap) and allows field selector to overflow + var self = this; + this.opened().then(function () { + // this restores default modal height (bootstrap) and allows field selector to overflow + self.$el.css('overflow', 'visible').closest('.modal-dialog').css('height', 'auto'); + }); return $.when( this._super.apply(this, arguments), this.domainSelector.appendTo(this.$el) -- cgit v1.2.3 From 3ad3e450e468f3ad37c627cf7a0d5f92aa4777b8 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Wed, 28 Feb 2018 11:00:13 +0100 Subject: [FIX] web: Calendar: only highlight today Before this rev., in week and month modes, when navigating through weeks or months, the weekday/monthday of today was still highlighted in other weeks and months than the current ones. This rev. ensures to only highlight today in the main calendar view (this doesn't impact the mini calendar). --- addons/web/static/src/js/views/calendar/calendar_controller.js | 2 +- addons/web/static/src/js/views/calendar/calendar_model.js | 8 ++------ addons/web/static/src/js/views/calendar/calendar_renderer.js | 5 ----- addons/web/static/src/less/web_calendar.less | 5 ----- 4 files changed, 3 insertions(+), 17 deletions(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/views/calendar/calendar_controller.js b/addons/web/static/src/js/views/calendar/calendar_controller.js index 2b17710935c..d79f26ffcd2 100644 --- a/addons/web/static/src/js/views/calendar/calendar_controller.js +++ b/addons/web/static/src/js/views/calendar/calendar_controller.js @@ -129,7 +129,7 @@ var CalendarController = AbstractController.extend({ // When clicking on a random day of a random other week, switch to week view this.model.setScale('week'); } - this.model.setDate(event.data.date, true); + this.model.setDate(event.data.date); this.reload(); }, /** diff --git a/addons/web/static/src/js/views/calendar/calendar_model.js b/addons/web/static/src/js/views/calendar/calendar_model.js index fac6845a31f..23c0aea3094 100644 --- a/addons/web/static/src/js/views/calendar/calendar_model.js +++ b/addons/web/static/src/js/views/calendar/calendar_model.js @@ -216,9 +216,9 @@ return AbstractModel.extend({ filters: params.filters, }; + this.setDate(params.initialDate); // Use mode attribute in xml file to specify zoom timeline (day,week,month) // by default month. - this.setDate(params.initialDate, true); this.setScale(params.mode); _.each(this.data.filters, function (filter) { @@ -251,9 +251,8 @@ return AbstractModel.extend({ }, /** * @param {Moment} start - * @param {boolean} highlight */ - setDate: function (start, highlight) { + setDate: function (start) { this.data.start_date = this.data.end_date = this.data.target_date = this.data.highlight_date = start; this.data.start_date.utc().add(this.getSession().getTZOffset(this.data.start_date), 'minutes'); @@ -270,9 +269,6 @@ return AbstractModel.extend({ this.data.start_date = this.data.start_date.clone().startOf('day'); this.data.end_date = this.data.end_date.clone().endOf('day'); } - if (highlight) { - this.data.highlight_date = this.data.target_date; - } }, setScale: function (scale) { if (!_.contains(scales, scale)) { diff --git a/addons/web/static/src/js/views/calendar/calendar_renderer.js b/addons/web/static/src/js/views/calendar/calendar_renderer.js index 6283a3539f3..e5f29cdca67 100644 --- a/addons/web/static/src/js/views/calendar/calendar_renderer.js +++ b/addons/web/static/src/js/views/calendar/calendar_renderer.js @@ -422,11 +422,6 @@ return AbstractRenderer.extend({ this.target_date = this.state.target_date.toString(); } - var highlightDate = moment(this.state.highlight_date).format('YYYY-MM-DD'); - $calendar.find('.o_target_date').removeClass('o_target_date'); - $calendar.find('.fc-bg .fc-day[data-date="'+highlightDate+'"]') - .addClass('o_target_date'); - this.$small_calendar.datepicker("setDate", this.state.highlight_date.toDate()) .find('.o_selected_range') .removeClass('o_color o_selected_range'); diff --git a/addons/web/static/src/less/web_calendar.less b/addons/web/static/src/less/web_calendar.less index d6aab5e0e38..1d2238a3177 100644 --- a/addons/web/static/src/less/web_calendar.less +++ b/addons/web/static/src/less/web_calendar.less @@ -71,11 +71,6 @@ } } } - - .o_target_date:not(.fc-today) { - background-color: @odoo-brand-primary; - opacity: 0.1; - } } .o_calendar_sidebar_container { -- cgit v1.2.3 From 132137939cc8460868a0e77ff78814a9d95e5a56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 26 Feb 2018 09:38:44 +0100 Subject: [FIX] web: prevent footer from being rerendered In some cases, the footer in a form view dialog could be duplicated. The reason was that the form view was rerendered completely, footer included, by the renderer, out of the formcontroller control (it is the formcontroller's job to move the footer buttons to the footer). A possible fix is to work on the form renderer to make it aware that the footer buttons are expected to be only rendered once, but it has the disadvantage to be a fix specific to this issue, and any other form view behaviour with some side effects may have the same issue. This commit tries to prevent the form view from rerendering itself completely in those cases. This also has the disadvantage of possibly not being a complete fix, if some edge cases are missed. However, I have the feeling that we still want to avoid rerendering the full view if possible. --- addons/web/static/src/js/fields/relational_fields.js | 5 ++++- addons/web/static/src/js/views/basic/basic_controller.js | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index ee5b8c7726c..af3467d8c82 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -1007,7 +1007,10 @@ var FieldX2Many = AbstractField.extend({ * @param {OdooEvent} ev */ _onDiscardChanges: function (ev) { - ev.data.fieldName = this.name; + if (ev.target !== this) { + ev.stopPropagation(); + this.trigger_up('discard_changes', _.extend({}, ev.data, {fieldName: this.name})); + } }, /** * Called when the renderer asks to edit a line, in that case simply tells 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 086428c2613..328daf956ee 100644 --- a/addons/web/static/src/js/views/basic/basic_controller.js +++ b/addons/web/static/src/js/views/basic/basic_controller.js @@ -271,6 +271,13 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { * @returns {Deferred} */ _confirmChange: function (id, fields, e) { + if (e.name === 'discard_changes' && e.target.reset) { + // the target of the discard event is a field widget. In that + // case, we simply want to reset the specific field widget, + // not the full view + return e.target.reset(this.model.get(e.target.dataPointID), e, true); + } + var state = this.model.get(this.handle); return this.renderer.confirmChange(state, id, fields, e); }, -- cgit v1.2.3 From d67d0caee209ad18f5a1aa5d87109917035f919a Mon Sep 17 00:00:00 2001 From: sum1201 Date: Sat, 3 Feb 2018 00:02:29 +0800 Subject: [FIX] web: translate 'External ID' string Closes #22763 --- addons/web/static/src/js/widgets/data_export.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/widgets/data_export.js b/addons/web/static/src/js/widgets/data_export.js index 4b9f6a29a47..dd0096f269b 100644 --- a/addons/web/static/src/js/widgets/data_export.js +++ b/addons/web/static/src/js/widgets/data_export.js @@ -455,7 +455,7 @@ var DataExport = Dialog.extend({ Dialog.alert(this, _t("Please select fields to export...")); return; } - exported_fields.unshift({name: 'id', label: 'External ID'}); + exported_fields.unshift({name: 'id', label: _t('External ID')}); var export_format = this.$export_format_inputs.filter(':checked').val(); -- cgit v1.2.3 From e710e5df7a88b601f117c84d62930486b1d58415 Mon Sep 17 00:00:00 2001 From: len-odoo Date: Mon, 5 Mar 2018 11:51:19 +0100 Subject: [FIX] web: show error when trying to export a report with more than 256 columns If the user tried to generate a report with more than 256 columns, he would get a traceback instead of an error message. Furthermore, if only one measure was selected, then the check was not done and the user would also get a traceback. opw-1816931 --- addons/web/static/src/js/views/pivot_view.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/views/pivot_view.js b/addons/web/static/src/js/views/pivot_view.js index c874a87665f..40b18fff86c 100644 --- a/addons/web/static/src/js/views/pivot_view.js +++ b/addons/web/static/src/js/views/pivot_view.js @@ -803,7 +803,7 @@ var PivotView = View.extend({ framework.blockUI(); var nbr_measures = this.active_measures.length, headers = this.compute_headers(), - measure_row = nbr_measures > 1 ? _.last(headers) : [], + measure_row = nbr_measures >= 1 ? _.last(headers) : [], rows = this.compute_rows(), i, j, value; headers[0].splice(0,1); @@ -829,7 +829,8 @@ var PivotView = View.extend({ title: this.title, }; if(table.measure_row.length + 1 > 256) { - c.show_message(_t("For Excel compatibility, data cannot be exported if there are more than 256 columns.\n\nTip: try to flip axis, filter further or reduce the number of measures.")); + crash_manager.show_message(_t("For Excel compatibility, data cannot be exported if there are more than 256 columns.\n\nTip: try to flip axis, filter further or reduce the number of measures.")); + framework.unblockUI(); return; } session.get_file({ -- cgit v1.2.3 From b771dd8a5cb9fdc37b9ed3e071c00ac9404d9074 Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Thu, 1 Mar 2018 16:37:59 +0100 Subject: [FIX] web: Impossible to delete a line in a tree view When creating a record in a form view of model A, and adding a line in an inside tree view of model B(with a One2Many from A to B), it was impossible to delete a wrong line even if the record has not been saved because the user had no right to delete a record on model B. opw:1819099 --- addons/web/static/src/xml/base.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index d76dd44f469..76230befea5 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -381,7 +381,8 @@ t-att-data-field="column.id" > - + + -- cgit v1.2.3 From a34357275fe731634c45ea07607f744d8c44b36e Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Mon, 5 Mar 2018 15:11:12 +0100 Subject: [FIX] web: bug introduced by forwardport 36945a58 PhantomJS doesn't like widgets that define two start functions. --- addons/web/static/src/js/widgets/notification.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/widgets/notification.js b/addons/web/static/src/js/widgets/notification.js index 2d987944d30..d05a0be380c 100644 --- a/addons/web/static/src/js/widgets/notification.js +++ b/addons/web/static/src/js/widgets/notification.js @@ -34,7 +34,8 @@ var Notification = Widget.extend({ this.title = params.title; this.text = params.text; this.sticky = !!params.sticky; - if (params.type === 'warning') { + this.type = params.type; + if (this.type === 'warning') { this.template = 'Warning'; } }, @@ -43,6 +44,9 @@ var Notification = Widget.extend({ */ start: function () { var self = this; + if (this.type === 'warning') { + this.$el.addClass('o_error'); + } return this._super.apply(this, arguments).then(function () { self.$el.animate({opacity: 1.0}, 400, "swing", function () { if(!self.sticky) { @@ -68,13 +72,6 @@ var Notification = Widget.extend({ self.$el.animate({height: 0}, 400, "swing", self.destroy.bind(self)); }); }, - /** - * @override - */ - start: function () { - this.$el.addClass('o_error'); - return this._super.apply(this, arguments); - }, //-------------------------------------------------------------------------- // Handlers -- cgit v1.2.3 From b466b3bc608b54d96ca0242b4d9c6176b76d88b6 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Thu, 1 Mar 2018 13:32:22 +0100 Subject: [FIX] web: create favorite filters in dialogs Consider the following scenario: open in edit mode a form view containing a many2many field displayed with widget many2many_tags, on the many2many field dropdown, click on 'Search More' (it opens a dialog), and in the dialog, try to save the current search in the searchview's favorite menu. Before this rev., it tried to retrieve the context of the active view in the closest instance of a ViewManager-like widget in the SearchView's ancestors. First, it is wrong as in the given scenario, the requested context is actually the one of the list controller in the dialog, not the one of the form controller in the background. Second, it produces a crash in Studio, when trying to save filters in the dialog opened by clicking on 'Search More' on the groups many2many field in the sidebar. This part of the code can't be tested in this version, but tests will be added in master. Fixes #23293 --- addons/web/static/src/js/view_manager.js | 22 ++++++++++++++++++++++ addons/web/static/src/js/views/form_common.js | 20 ++++++++++++++++++++ addons/web/static/src/js/views/search_menus.js | 16 +++++++++------- 3 files changed, 51 insertions(+), 7 deletions(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/view_manager.js b/addons/web/static/src/js/view_manager.js index a351a813263..490b2d5cfba 100644 --- a/addons/web/static/src/js/view_manager.js +++ b/addons/web/static/src/js/view_manager.js @@ -15,6 +15,9 @@ var _t = core._t; var ViewManager = Widget.extend(ControlPanelMixin, { className: "o_view_manager_content", + custom_events: { + get_controller_context: '_onGetControllerContext', + }, /** * Called each time the view manager is attached into the DOM */ @@ -474,6 +477,25 @@ var ViewManager = Widget.extend(ControlPanelMixin, { } return this._super.apply(this, arguments); }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + // ONLY FORWARDPORT THIS COMMIT UP TO SAAS-15 + /** + * Handles a context request: provides to the caller the context of the + * active view. + * + * @private + * @param {OdooEvent} ev + * @param {function} ev.data.callback used to send the requested context + */ + _onGetControllerContext: function (ev) { + var controller = this.active_view && this.active_view.controller; + var context = controller && controller.get_context(); + ev.data.callback(context); + }, }); return ViewManager; diff --git a/addons/web/static/src/js/views/form_common.js b/addons/web/static/src/js/views/form_common.js index 16e8c088368..f5853475231 100644 --- a/addons/web/static/src/js/views/form_common.js +++ b/addons/web/static/src/js/views/form_common.js @@ -976,6 +976,9 @@ var SelectCreateListView = ListView.extend({ * Search dialog (displays a list of records and permits to create a new one by switching to a form view) */ var SelectCreateDialog = ViewDialog.extend({ + custom_events: { + get_controller_context: '_onGetControllerContext', + }, /** * options: * - initial_ids @@ -1106,6 +1109,23 @@ var SelectCreateDialog = ViewDialog.extend({ return new FormViewDialog(this.__parentedParent, this.options).open(); }, on_view_list_loaded: function() {}, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Handles a context request: provides to the caller the context of the + * list view. + * + * @private + * @param {OdooEvent} ev + * @param {function} ev.data.callback used to send the requested context + */ + _onGetControllerContext: function (ev) { + var context = this.view_list.get_context(); + ev.data.callback(context); + }, }); var DomainEditorDialog = SelectCreateDialog.extend({ diff --git a/addons/web/static/src/js/views/search_menus.js b/addons/web/static/src/js/views/search_menus.js index a8ce7274256..6c7069312aa 100644 --- a/addons/web/static/src/js/views/search_menus.js +++ b/addons/web/static/src/js/views/search_menus.js @@ -93,13 +93,15 @@ return Widget.extend({ this.$inputs.first().focus(); return; } - var search = this.searchview.build_search_data(), - view_manager = this.findAncestor(function (a) { - // HORRIBLE HACK. PLEASE SAVE ME FROM MYSELF (BUT IN A PAINLESS WAY IF POSSIBLE) - return 'active_view' in a; - }), - view_context = view_manager ? view_manager.active_view.controller.get_context() : {}, - results = pyeval.sync_eval_domains_and_contexts({ + var search = this.searchview.build_search_data(); + // ONLY FORWARDPORT THIS COMMIT UP TO SAAS-15 + var view_context; + this.trigger_up('get_controller_context', { + callback: function (ctx) { + view_context = ctx; + }, + }); + var results = pyeval.sync_eval_domains_and_contexts({ domains: search.domains, contexts: search.contexts.concat(view_context || []), group_by_seq: search.groupbys || [], -- cgit v1.2.3 From 16a27c94e01aa383fea9a14089668233f676692a Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Wed, 28 Feb 2018 14:15:06 +0100 Subject: [FIX] web: create favorite filters in dialogs This is a manual forwardport of rev. b466b3bc. Consider the following scenario: open in edit mode a form view containing a many2many field displayed with widget many2many_tags, on the many2many field dropdown, click on 'Search More' (it opens a dialog), and in the dialog, try to save the current search in the searchview's favorite menu. Before this rev., it tried to retrieve the context of the active controller in the closest instance of a ViewManager-like widget in the SearchView's ancestors. First, it is wrong as in the given scenario, the requested context is actually the one of the list controller in the dialog, not the one of the form controller in the background. Second, it produces a crash in Studio, when trying to save filters in the dialog opened by clicking on 'Search More' on the groups many2many field in the sidebar. This part of the code can't be tested in this version, but tests will be added in master. Fixes #23293 --- addons/web/static/src/js/chrome/search_menus.js | 14 ++++++++------ addons/web/static/src/js/chrome/view_manager.js | 16 ++++++++++++++++ addons/web/static/src/js/views/view_dialogs.js | 19 +++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/chrome/search_menus.js b/addons/web/static/src/js/chrome/search_menus.js index fe2ce5f3c32..d41052c882e 100644 --- a/addons/web/static/src/js/chrome/search_menus.js +++ b/addons/web/static/src/js/chrome/search_menus.js @@ -96,14 +96,16 @@ return Widget.extend({ } var user_context = this.getSession().user_context; var search = this.searchview.build_search_data(); - var view_manager = this.findAncestor(function (a) { - // HORRIBLE HACK. PLEASE SAVE ME FROM MYSELF (BUT IN A PAINLESS WAY IF POSSIBLE) - return 'active_view' in a; - }); - var view_context = view_manager ? view_manager.active_view.controller.getContext() : {}; + // DO NOT FORWARDPORT THIS + var controllerContext; + this.trigger_up('get_controller_context', { + callback: function (ctx) { + controllerContext = ctx; + }, + }); var results = pyeval.eval_domains_and_contexts({ domains: search.domains, - contexts: [user_context].concat(search.contexts.concat(view_context || [])), + contexts: [user_context].concat(search.contexts.concat(controllerContext || [])), group_by_seq: search.groupbys || [], }); if (!_.isEmpty(results.group_by)) { diff --git a/addons/web/static/src/js/chrome/view_manager.js b/addons/web/static/src/js/chrome/view_manager.js index 3f3cec9ec7f..6e44bb8ad63 100644 --- a/addons/web/static/src/js/chrome/view_manager.js +++ b/addons/web/static/src/js/chrome/view_manager.js @@ -46,6 +46,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, { push_state: function(event) { this.do_push_state(event.data); }, + get_controller_context: '_onGetControllerContext', switch_to_previous_view: '_onSwitchToPreviousView', }, /** @@ -631,6 +632,21 @@ var ViewManager = Widget.extend(ControlPanelMixin, { // Handlers //-------------------------------------------------------------------------- + // DO NOT FORWARDPORT THIS + /** + * Handles a context request: provides to the caller the context of the + * active controller. + * + * @private + * @param {OdooEvent} ev + * @param {function} ev.data.callback used to send the requested context + */ + _onGetControllerContext: function (ev) { + ev.stopPropagation(); + var controller = this.active_view && this.active_view.controller; + var context = controller && controller.getContext(); + ev.data.callback(context); + }, /** * This handler is probably called by a sub form view when the user discards * its value. The usual result of this is that we switch back to the diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index a33f6f740b8..6f62483a81c 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -262,6 +262,7 @@ var SelectCreateDialog = ViewDialog.extend({ var searchData = this._process_search_data(d.domains, d.contexts, d.groupbys); this.list_controller.reload(searchData); }, + get_controller_context: '_onGetControllerContext', }), /** @@ -418,6 +419,24 @@ var SelectCreateDialog = ViewDialog.extend({ dialog.on('closed', this, this.close); return dialog; }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Handles a context request: provides to the caller the context of the + * list controller. + * + * @private + * @param {OdooEvent} ev + * @param {function} ev.data.callback used to send the requested context + */ + _onGetControllerContext: function (ev) { + ev.stopPropagation(); + var context = this.list_controller.getContext(); + ev.data.callback(context); + }, }); return { -- cgit v1.2.3 From cdb01d52ea0a7b3b02594f2bd98d1a7bdf2fa2a3 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Thu, 1 Mar 2018 11:19:23 +0100 Subject: [FIX] web: create favorite filters in dialogs This is a manual forwardport of rev. b466b3bc608. Consider the following scenario: open in edit mode a form view containing a many2many field displayed with widget many2many_tags, on the many2many field dropdown, click on 'Search More' (it opens a dialog), and in the dialog, try to save the current search in the searchview's favorite menu. Before this rev., it crashed because it tried to retrieve the action's context (to add to the saved filter), whereas there is no action in this case. Actually, the action's context should never been used in this case. What must be used is the controller's context (this mainly makes sense for Pivot and Graph views). Fixes #23293 --- .../static/src/js/chrome/abstract_web_client.js | 13 +++++++++ .../src/js/chrome/action_manager_act_window.js | 15 +++++++++++ addons/web/static/src/js/chrome/search_menus.js | 31 +++++++++++++++++++--- addons/web/static/src/js/views/view_dialogs.js | 19 +++++++++++++ 4 files changed, 75 insertions(+), 3 deletions(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js index b4b192c1ea9..9fe38adbde6 100644 --- a/addons/web/static/src/js/chrome/abstract_web_client.js +++ b/addons/web/static/src/js/chrome/abstract_web_client.js @@ -68,6 +68,7 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, { .load_filters(event.data.dataset, event.data.action_id) .then(event.data.on_success); }, + create_filter: '_onCreateFilter', push_state: '_onPushState', show_effect: '_onShowEffect', // session @@ -337,6 +338,18 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, { // Handlers //-------------------------------------------------------------------------- + /** + * @private + * @param {OdooEvent} e + * @param {Object} e.data.filter the filter description + * @param {function} e.data.on_success called when the RPC succeeds with its + * returned value as argument + */ + _onCreateFilter: function (e) { + data_manager + .create_filter(e.data.filter) + .then(e.data.on_success); + }, /** * Displays a warning in a dialog of with the NotificationManager * diff --git a/addons/web/static/src/js/chrome/action_manager_act_window.js b/addons/web/static/src/js/chrome/action_manager_act_window.js index 9cfa99a9e7d..6cc05ebc5d2 100644 --- a/addons/web/static/src/js/chrome/action_manager_act_window.js +++ b/addons/web/static/src/js/chrome/action_manager_act_window.js @@ -21,6 +21,7 @@ ActionManager.include({ custom_events: _.extend({}, ActionManager.prototype.custom_events, { env_updated: '_onEnvUpdated', execute_action: '_onExecuteAction', + get_controller_context: '_onGetControllerContext', search: '_onSearch', switch_view: '_onSwitchView', }), @@ -715,6 +716,20 @@ ActionManager.include({ return self.doAction(action, options).then(ev.data.on_success, ev.data.on_fail); }); }, + /** + * Handles a context request: provides to the caller the context of the + * current controller. + * + * @private + * @param {OdooEvent} ev + * @param {function} ev.data.callback used to send the requested context + */ + _onGetControllerContext: function (ev) { + ev.stopPropagation(); + var currentController = this.getCurrentController(); + var context = currentController && currentController.widget.getContext(); + ev.data.callback(context || {}); + }, /** * Called when there is a change in the search view, so the current action's * environment needs to be updated with the new domain, context and groupby. diff --git a/addons/web/static/src/js/chrome/search_menus.js b/addons/web/static/src/js/chrome/search_menus.js index 38f2bb7374c..d33cdce6500 100644 --- a/addons/web/static/src/js/chrome/search_menus.js +++ b/addons/web/static/src/js/chrome/search_menus.js @@ -97,10 +97,15 @@ return Widget.extend({ } var user_context = this.getSession().user_context; var search = this.searchview.build_search_data(); - var view_context = this.action.env.context; + var controllerContext; + this.trigger_up('get_controller_context', { + callback: function (ctx) { + controllerContext = ctx; + }, + }); var results = pyeval.eval_domains_and_contexts({ domains: search.domains, - contexts: [user_context].concat(search.contexts.concat(view_context || [])), + contexts: [user_context].concat(search.contexts.concat(controllerContext || [])), group_by_seq: search.groupbys || [], }); if (!_.isEmpty(results.group_by)) { @@ -123,7 +128,7 @@ return Widget.extend({ is_default: default_filter, action_id: this.action_id, }; - return data_manager.create_filter(filter).done(function (id) { + return this._createFilter(filter).then(function (id) { filter.id = id; self.toggle_save_menu(false); self.$save_name.find('input').val('').prop('checked', false); @@ -273,6 +278,26 @@ return Widget.extend({ } }); }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Creates a new search view filter. + * + * @private + * @param {Object} filter the filter description + * @returns {$.Deferred} resolved with the RPC's result when it succeeds + */ + _createFilter: function (filter) { + var def = $.Deferred(); + this.trigger_up('create_filter', { + filter: filter, + on_success: def.resolve.bind(def), + }); + return def; + }, }); }); diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index 4be84c23b71..1b74068d44d 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -260,6 +260,7 @@ var SelectCreateDialog = ViewDialog.extend({ var searchData = this._process_search_data(d.domains, d.contexts, d.groupbys); this.list_controller.reload(searchData); }, + get_controller_context: '_onGetControllerContext', }), /** @@ -416,6 +417,24 @@ var SelectCreateDialog = ViewDialog.extend({ dialog.on('closed', this, this.close); return dialog; }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Handles a context request: provides to the caller the context of the + * list controller. + * + * @private + * @param {OdooEvent} ev + * @param {function} ev.data.callback used to send the requested context + */ + _onGetControllerContext: function (ev) { + ev.stopPropagation(); + var context = this.list_controller && this.list_controller.getContext(); + ev.data.callback(context || {}); + } }); return { -- cgit v1.2.3 From 4f084a2fe12debbf41d3a560bbee7e7202d7117f Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Mon, 5 Mar 2018 11:55:12 +0100 Subject: [FIX] web: ActionManager: F5 on form view in mobile This rev. fixes two issues with the loading (e.g. after an F5) of form views in mobile. First, the form view being not a mobile-friendly view, it opened the kanban view instead (if there were one defined in the action), whether it should have kept the form view as they don't diplay the same things (one is multiRecord, the other isn't). Second, after fixing the first issue, the main (multiRecord) view which was lazyloaded when loading a form view wasn't mobile-friendly so if the user clicked on the breadcrumbs to go back, he landed on a list view (if the list view was the first view of the action), whether he should be on a kanban view, which is mobile-friendly. --- .../src/js/chrome/action_manager_act_window.js | 38 ++++++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/chrome/action_manager_act_window.js b/addons/web/static/src/js/chrome/action_manager_act_window.js index 6cc05ebc5d2..dfb512900e8 100644 --- a/addons/web/static/src/js/chrome/action_manager_act_window.js +++ b/addons/web/static/src/js/chrome/action_manager_act_window.js @@ -255,18 +255,26 @@ ActionManager.include({ action.env = self._generateActionEnv(action, options); action.controllers = {}; - // select the first view to display - var lazyLoadFirstView = false; + // select the first view to display, and optionally the main view + // which will be lazyloaded var firstView = options.viewType && _.findWhere(views, {type: options.viewType}); + var mainView; if (firstView) { if (!firstView.multiRecord && views[0].multiRecord) { - lazyLoadFirstView = true; + mainView = views[0]; } } else { firstView = views[0]; } - if (config.device.isMobile && !firstView.isMobileFriendly) { - firstView = _.findWhere(action.views, {isMobileFriendly: true}) || firstView; + + // use mobile-friendly view by default in mobile, if possible + if (config.device.isMobile) { + if (!firstView.isMobileFriendly) { + firstView = self._findMobileView(views, firstView.multiRecord) || firstView; + } + if (mainView && !mainView.isMobileFriendly) { + mainView = self._findMobileView(views, mainView.multiRecord) || mainView; + } } var def; @@ -280,8 +288,8 @@ ActionManager.include({ return $.when(def).then(function () { var defs = []; defs.push(self._createViewController(action, firstView.type)); - if (lazyLoadFirstView) { - defs.push(self._createViewController(action, views[0].type, {}, {lazy: true})); + if (mainView) { + defs.push(self._createViewController(action, mainView.type, {}, {lazy: true})); } return $.when.apply($, defs); }).then(function (controller, lazyLoadedController) { @@ -297,6 +305,22 @@ ActionManager.include({ }); }); }, + /** + * Helper function to find the first mobile-friendly view, if any. + * + * @private + * @param {Array} views an array of views + * @param {boolean} multiRecord set to true iff we search for a multiRecord + * view + * @returns {Object|undefined} a mobile-friendly view of the requested + * multiRecord type, undefined if there is no such view + */ + _findMobileView: function (views, multiRecord) { + return _.findWhere(views, { + isMobileFriendly: true, + multiRecord: multiRecord, + }); + }, /** * Generates the initial environment of a given action, which is a dict * containing information like the model name, the domain, the context... -- cgit v1.2.3 From 66acbe30f1c08ba703b9294194dd4dfeb0c2df67 Mon Sep 17 00:00:00 2001 From: Dhawal Limbuwala Date: Tue, 6 Feb 2018 14:48:30 +0530 Subject: [FIX] web: set cursor pointer on link hovering in nocontent help While displaying no content help, hovering over link was not displaying cursor as a pointer type. This bug has been introduced with the new design on nocontent helpers. --- addons/web/static/src/less/views.less | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/less/views.less b/addons/web/static/src/less/views.less index aaa75b9156a..a4a400f1268 100644 --- a/addons/web/static/src/less/views.less +++ b/addons/web/static/src/less/views.less @@ -60,6 +60,10 @@ font-size: 125%; } + a { + cursor: pointer; + } + .o-init-image() { content: ""; display: block; -- cgit v1.2.3 From 9ccda2076444631a3a491bd0ddb231054a139bce Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Tue, 6 Mar 2018 14:13:28 +0100 Subject: [FIX] web: BasicView: prevent crash in tests A crash occured in several Studio tests that have just been forwardported to saas-11.2, because of a line that hadn't been adapted in rev. cb339842 (technically, the previous code was correct, but it crashed when viewInfo.fields was undefined, which doesn't happen outside the tests environment). --- addons/web/static/src/js/views/basic/basic_view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/views/basic/basic_view.js b/addons/web/static/src/js/views/basic/basic_view.js index a0574c6a401..b4df9c8e09e 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -45,7 +45,7 @@ var BasicView = AbstractView.extend({ this.rendererParams.viewType = this.viewType; this.controllerParams.confirmOnDelete = true; - this.controllerParams.archiveEnabled = 'active' in viewInfo.fields; + this.controllerParams.archiveEnabled = 'active' in this.fields; this.controllerParams.hasButtons = 'action_buttons' in params ? params.action_buttons : true; -- cgit v1.2.3 From b7b8634575f3720593f5ad05d9dc828fb5b4d943 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Wed, 7 Mar 2018 11:40:25 +0100 Subject: [FIX] web: statusbar buttons design when no statusbar status When there were no status widget in the form view statusbar, the left buttons appeared without any vertical space around them in a smaller statusbar. There also was a 1px mistake that made the buttons appear non-centered. Now they appear centered in any case and a row of buttons is always forcing the correct statusbar height. Closes https://github.com/odoo/odoo/pull/22053 opw-(1)813479 --- addons/web/static/src/less/form_view.less | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/less/form_view.less b/addons/web/static/src/less/form_view.less index cd9d550843a..ceff30ee4c2 100644 --- a/addons/web/static/src/less/form_view.less +++ b/addons/web/static/src/less/form_view.less @@ -165,7 +165,9 @@ .o-flex-flow(row, wrap); > .btn { - margin-right: 3px; + @o-statusbar-buttons-vmargin: 4px; + min-height: @odoo-statusbar-height - 2 * @o-statusbar-buttons-vmargin; + margin: @o-statusbar-buttons-vmargin 3px @o-statusbar-buttons-vmargin 0; padding-top: 2px; padding-bottom: 2px; } -- cgit v1.2.3 From 3809fc891f5b4794493b6ad4ed4bc4000c9a416e Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Wed, 7 Mar 2018 09:23:24 +0100 Subject: [FIX] web,mail: always reload widget mail_activity Before this rev., the field widget 'mail_activity' kept an internal cache of fetched activities (like the 'mail_thread' and 'mail_followers'). However, for activities, this cache may cause issues. For instance, create a new lead in 'My pipeline', schedule a 'Meeting' activity on that lead (it opens the Calendar view so that real meetings can be created), create a meeting 2 weeks from now, go back to the lead form view: the activity still diplays that the meeting is today (when the view is refreshed, the correct date is displayed). This rev. removes the internal cache for this field widget, as the activities can easily be updated in business flows (unlike messages or followers). This rev. also fixes a bug in the BasicModel, where the res_ids of a list datapoint was altered in place to return a list of x2many commands instead of the list of ids when being stringified. This caused a traceback when opening a lead with activities, as soon as the internal cache had been removed. --- addons/web/static/src/js/views/basic/basic_model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/web/static/src') 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 c7f61edd541..c8bd4ae030f 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -3047,7 +3047,7 @@ var BasicModel = AbstractModel.extend({ if (field.type === 'one2many' || field.type === 'many2many') { var ids; if (!context[fieldName] || _.isArray(context[fieldName])) { // no dataPoint created yet - ids = context[fieldName] || []; + ids = context[fieldName] ? context[fieldName].slice(0) : []; } else { relDataPoint = this._applyX2ManyOperations(this.localData[context[fieldName]]); ids = relDataPoint.res_ids.slice(0); -- cgit v1.2.3 From 7fac8010752ec2af309e860d1793d905f106fb14 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Wed, 7 Mar 2018 14:58:31 +0100 Subject: [FIX] web: mobile: display searchview toggler There was an issue with the control panel in mobile since rev. 7b6056a. In the enterprise webclient, by default, the search view is hidden, and a search icon should be displayed on the top right corner of the control panel so that the user can toggle the searchview. Before this rev., the icon was invisible at the first rendering of a view, and only appeared once the view was reloaded. The change in rev. 7b6056a that produced the issue is that the control panel is now updated twice when a view is rendered (once by the view itself with the buttons, searchview..., and once by the ActionManager with the breadcrumbs). The control panel didn't handle correctly that scenario. A test case will been added in web_enterprise. --- addons/web/static/src/js/chrome/control_panel.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/chrome/control_panel.js b/addons/web/static/src/js/chrome/control_panel.js index 16b22761581..03b9e756bb7 100644 --- a/addons/web/static/src/js/chrome/control_panel.js +++ b/addons/web/static/src/js/chrome/control_panel.js @@ -154,7 +154,9 @@ var ControlPanel = Widget.extend({ this._attach_content(new_cp_content); // Update the searchview and switch buttons - this._update_search_view(status.searchview, status.search_view_hidden, status.groupable); + if (status.searchview || options.clear) { + this._update_search_view(status.searchview, status.search_view_hidden, status.groupable); + } if (status.active_view_selector) { this._update_switch_buttons(status.active_view_selector); } -- cgit v1.2.3 From 67f67b512477d7c87e47076468f7dadb144e1da8 Mon Sep 17 00:00:00 2001 From: true-datura Date: Wed, 7 Mar 2018 17:55:23 +0200 Subject: [FIX] web: 'image_small' field not displayed for non-latin translations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this commit, images in kanban view of the Contact app were not displayed on non-latin languages, such as Russian. This was caused by the is_bin_size helper that wrongly assumes that a certain string is not a binary size, although it is (e.g. 64.2 Кб). This commit fixes the issue by including such strings as valid binary sizes. Bug found and fixed by @true-datura Test by @alexkuhn closes #23526 closes #22519 --- addons/web/static/src/js/core/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/core/utils.js b/addons/web/static/src/js/core/utils.js index 496754aedea..99240870c18 100644 --- a/addons/web/static/src/js/core/utils.js +++ b/addons/web/static/src/js/core/utils.js @@ -228,7 +228,7 @@ var utils = { * @returns {boolean} */ is_bin_size: function (v) { - return (/^\d+(\.\d*)? \w+$/).test(v); + return (/^\d+(\.\d*)? [^0-9]+$/).test(v); }, /** * @param {any} node -- cgit v1.2.3 From c7b5b12be6b6faf6b58057a64d5f7ba1e57b85fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Thu, 8 Mar 2018 16:34:01 +0100 Subject: [FIX] web: add the node attribute to the form view widget This could (should?) be considered a refactoring, but this is actually trying to restore lost functionality. In v10, the custom form widgets were created with access to the node element that define them. We lost that in v11, with the new views. However, it is actually very useful for generic widgets. Since this is not really a risky fix, I think that it can be done in 11.0. Note: this feature was already reintroduced in master in commit https://github.com/odoo/odoo/commit/b7aa439826df6b3e442b3f60bb593ff28efc8679 --- addons/web/static/src/js/views/basic/basic_renderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js index 204c35acaef..4b22a5dc66f 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -607,7 +607,7 @@ var BasicRenderer = AbstractRenderer.extend({ */ _renderWidget: function (record, node) { var Widget = widgetRegistry.get(node.attrs.name); - var widget = new Widget(this, record); + var widget = new Widget(this, record, node); this.widgets.push(widget); -- cgit v1.2.3 From 0a66ca3909c6562c59ee34d3b5f33ed1f6d38a43 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Thu, 8 Mar 2018 16:06:00 +0100 Subject: [FIX] web: ActionManager: push state of loaded actions Actions loaded from the url are flagged with 'pushState: false', to prevent them from pushing their state back to the url, thus producing an infinite loop. However, if the state of a loaded action changes in the future (e.g. load an action in kanban view, then switch to a form view), this state should be pushed to update the url. Before this rev., this wasn't the case. --- addons/web/static/src/js/chrome/action_manager.js | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'addons/web/static/src') diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js index 236d7517d04..99830402984 100644 --- a/addons/web/static/src/js/chrome/action_manager.js +++ b/addons/web/static/src/js/chrome/action_manager.js @@ -356,6 +356,11 @@ var ActionManager = Widget.extend({ // toggle the fullscreen mode for actions in target='fullscreen' self._toggleFullscreen(); + // now that the action has been executed, force its 'pushState' + // flag to 'true', as we don't want to prevent its controller + // from pushing its state if it changes in the future + action.pushState = true; + return action; }) .fail(function () { -- cgit v1.2.3