summaryrefslogtreecommitdiff
path: root/addons/web
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-08-08 11:52:13 +0200
committerChristophe Simonis <chs@odoo.com>2019-08-08 11:52:13 +0200
commitb792ba7d9a80e856e3c69c2852e6d44b78d0e13f (patch)
treea3ee5b7ef9f9317866deeb2b3e5964020c87b8fb /addons/web
parent22e0f88bce5bf162a6c67e915a3c27b101f74f2b (diff)
parent752b2553c6fad061ebf23e13585f7b7489b20840 (diff)
[MERGE] forward port branch saas-12.3 up to 752b2553c6f
Diffstat (limited to 'addons/web')
-rw-r--r--addons/web/controllers/main.py4
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js2
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js1
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_controller.js7
-rw-r--r--addons/web/static/src/js/views/control_panel/control_panel_model.js3
-rw-r--r--addons/web/static/src/js/views/control_panel/search/groupby_menu.js5
-rw-r--r--addons/web/static/src/js/views/control_panel/search/groupby_menu_mixin.js5
-rw-r--r--addons/web/static/src/js/views/graph/graph_controller.js7
-rw-r--r--addons/web/static/src/js/views/graph/graph_renderer.js33
-rw-r--r--addons/web/static/src/js/views/list/list_renderer.js2
-rw-r--r--addons/web/static/src/scss/list_view.scss3
-rw-r--r--addons/web/static/tests/views/calendar_tests.js58
-rw-r--r--addons/web/static/tests/views/search_view_tests.js17
13 files changed, 123 insertions, 24 deletions
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index 860cf454dd4..18a36885f5f 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -201,11 +201,11 @@ def concat_xml(file_list):
:param list(str) file_list: list of files to check
:returns: (concatenation_result, checksum)
- :rtype: (str, str)
+ :rtype: (bytes, str)
"""
checksum = hashlib.new('sha1')
if not file_list:
- return '', checksum.hexdigest()
+ return b'', checksum.hexdigest()
root = None
for fname in file_list:
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index 171a6da7735..160f445f1c4 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -1603,8 +1603,8 @@ var FieldBinaryImage = AbstractFieldBinary.extend({
}
this.$('> img').remove();
this.$el.prepend($img);
+
$img.one('error', function () {
- self._clearFile();
$img.attr('src', self.placeholder);
self.do_warn(_t("Image"), _t("Could not display the selected image."));
});
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 bc66594fee3..d3165420ef8 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -796,6 +796,7 @@ var BasicModel = AbstractModel.extend({
});
_.each(fields, function (field) {
var dataPoint;
+ record.data[field.name] = null;
if (field.type === 'many2one') {
if (field.value) {
var id = _.isArray(field.value) ? field.value[0] : field.value;
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 b3a04ec9a2b..bbfcb02def5 100644
--- a/addons/web/static/src/js/views/calendar/calendar_controller.js
+++ b/addons/web/static/src/js/views/calendar/calendar_controller.js
@@ -55,6 +55,7 @@ var CalendarController = AbstractController.extend({
this.readonlyFormViewId = params.readonlyFormViewId;
this.mapping = params.mapping;
this.context = params.context;
+ this.previousOpen = null;
// The quickCreating attribute ensures that we don't do several create
this.quickCreating = false;
},
@@ -271,7 +272,8 @@ var CalendarController = AbstractController.extend({
title += ': ' + this.renderer.arch.attrs.string;
}
if (this.eventOpenPopup) {
- new dialogs.FormViewDialog(self, {
+ if (this.previousOpen) { this.previousOpen.close(); }
+ this.previousOpen = new dialogs.FormViewDialog(self, {
res_model: this.modelName,
context: context,
title: title,
@@ -283,7 +285,8 @@ var CalendarController = AbstractController.extend({
}
self.reload();
},
- }).open();
+ });
+ this.previousOpen.open();
} else {
this.do_action({
type: 'ir.actions.act_window',
diff --git a/addons/web/static/src/js/views/control_panel/control_panel_model.js b/addons/web/static/src/js/views/control_panel/control_panel_model.js
index f2e28858ba3..0229daa41ee 100644
--- a/addons/web/static/src/js/views/control_panel/control_panel_model.js
+++ b/addons/web/static/src/js/views/control_panel/control_panel_model.js
@@ -157,6 +157,9 @@ var ControlPanelModel = mvc.Model.extend({
if (filter.autoCompleteValues) {
filter.autoCompleteValues = [];
}
+ if (filter.currentOptionId) {
+ filter.currentOptionId = false;
+ }
});
// TODO: use toggleFilter here
group.activeFilterIds = [];
diff --git a/addons/web/static/src/js/views/control_panel/search/groupby_menu.js b/addons/web/static/src/js/views/control_panel/search/groupby_menu.js
index b67bf9ee75d..fac67beb3f4 100644
--- a/addons/web/static/src/js/views/control_panel/search/groupby_menu.js
+++ b/addons/web/static/src/js/views/control_panel/search/groupby_menu.js
@@ -41,6 +41,7 @@ var GroupByMenu = DropdownMenu.extend({
*/
init: function (parent, groupBys, fields, options) {
var self = this;
+ options = options || {};
this._super(parent, groupBys);
this.fields = fields;
// determines when the 'Add custom groupby' submenu is open
@@ -65,9 +66,9 @@ var GroupByMenu = DropdownMenu.extend({
this.dropdownCategory = 'groupby';
this.dropdownTitle = _t('Group By');
this.dropdownIcon = 'fa fa-bars';
- this.dropdownSymbol = this.isMobile ? 'fa fa-chevron-right float-right mt4' : false;
+ this.dropdownSymbol = this.isMobile && !options.noSymbol ? 'fa fa-chevron-right float-right mt4' : false;
// the default style of the groupby menu can be changed here using the options key 'headerStyle'
- if (options && options.headerStyle === 'primary') {
+ if (options.headerStyle === 'primary') {
this.dropdownStyle = {
el: {class: 'btn-group o_group_by_menu o_dropdown', attrs: {'role': 'group'}},
mainButton: {class: 'btn btn-primary dropdown-toggle'},
diff --git a/addons/web/static/src/js/views/control_panel/search/groupby_menu_mixin.js b/addons/web/static/src/js/views/control_panel/search/groupby_menu_mixin.js
index dc7a5dc3cc8..c9a17d6225e 100644
--- a/addons/web/static/src/js/views/control_panel/search/groupby_menu_mixin.js
+++ b/addons/web/static/src/js/views/control_panel/search/groupby_menu_mixin.js
@@ -36,6 +36,7 @@ var GroupByMenuMixin = {
* private
* @param {jQuery} $node
* @param {Object} groupableFields
+ * @param {Promise}
*/
_addGroupByMenu: function ($node, groupableFields) {
this.sortedFieldNames = _.sortBy(Object.keys(groupableFields), function (fieldName) {
@@ -43,8 +44,8 @@ var GroupByMenuMixin = {
});
this.groupableFields = groupableFields;
var groupBys = this._getGroupBys(this.model.get().groupBy);
- this.groupByMenu = new GroupByMenu(this, groupBys, this.groupableFields);
- this.groupByMenu.insertAfter($node.find('div:first'));
+ this.groupByMenu = new GroupByMenu(this, groupBys, this.groupableFields, {noSymbol: true});
+ return this.groupByMenu.insertAfter($node.find('div:first'));
},
/**
* This method puts the active groupBys in a convenient form.
diff --git a/addons/web/static/src/js/views/graph/graph_controller.js b/addons/web/static/src/js/views/graph/graph_controller.js
index 9e2ebfc0a78..1c580a25bd6 100644
--- a/addons/web/static/src/js/views/graph/graph_controller.js
+++ b/addons/web/static/src/js/views/graph/graph_controller.js
@@ -96,8 +96,13 @@ var GraphController = AbstractController.extend(GroupByMenuMixin,{
this._updateButtons();
this.$buttons.appendTo($node);
if (this.isEmbedded) {
- this._addGroupByMenu($node, this.groupableFields);
+ this._addGroupByMenu($node, this.groupableFields).then(function(){
+ var groupByButton = $node.find('.o_dropdown_toggler_btn');
+ groupByButton.removeClass("o_dropdown_toggler_btn btn btn-secondary dropdown-toggle");
+ groupByButton.addClass("btn dropdown-toggle btn-outline-secondary");
+ });
}
+
}
},
diff --git a/addons/web/static/src/js/views/graph/graph_renderer.js b/addons/web/static/src/js/views/graph/graph_renderer.js
index 02fdb102358..9c70811ed18 100644
--- a/addons/web/static/src/js/views/graph/graph_renderer.js
+++ b/addons/web/static/src/js/views/graph/graph_renderer.js
@@ -216,6 +216,24 @@ return AbstractRenderer.extend({
return this.state.origins[dataPt.originIndex];
},
/**
+ * Returns an object used to style chart elements independently from the datasets.
+ *
+ * @private
+ * @returns {Object}
+ */
+ _getElementOptions: function () {
+ var elementOptions = {};
+ if (this.state.mode === 'bar') {
+ elementOptions.rectangle = {borderWidth: 1};
+ } else if (this.state.mode === 'line') {
+ elementOptions.line = {
+ tension: 0,
+ fill: false,
+ };
+ }
+ return elementOptions;
+ },
+ /**
* Returns a DateClasses instance used to manage equivalence of dates.
*
* @private
@@ -250,9 +268,8 @@ return AbstractRenderer.extend({
} else {
return dataPt.labels.slice(0, 1);
}
- } else if (i >= 0) {
+ } else if (i === 0) {
return Array.prototype.concat.apply([], [
- dataPt.labels.slice(0, i),
this.dateClasses.dateClass(dataPt.originIndex, dataPt.labels[i]),
dataPt.labels.slice(i+1)
]);
@@ -563,6 +580,7 @@ return AbstractRenderer.extend({
scales: this._getScaleOptions(),
legend: this._getLegendOptions(datasetsCount),
tooltips: this._getTooltipOptions(datasetsCount),
+ elements: this._getElementOptions(),
};
},
/**
@@ -583,7 +601,7 @@ return AbstractRenderer.extend({
if (_.contains(['bar', 'line'], this.state.mode) && i === 0) {
// here label is an array of length 1 and contains a number
return this.dateClasses.representative(label, originIndex) || '';
- } else if (this.state.mode === 'pie' && i >= 0) {
+ } else if (this.state.mode === 'pie' && i === 0) {
// here label is an array of length at least one containing string or numbers
var labelCopy = label.slice(0);
if (originIndex !== undefined) {
@@ -637,7 +655,7 @@ return AbstractRenderer.extend({
this.$el.append($canvasContainer);
var i = this.state.comparisonFieldIndex;
- if (i === 0 || (i > 0 && this.state.mode === 'pie')) {
+ if (i === 0) {
this.dateClasses = this._getDateClasses(dataPoints);
}
if (this.state.mode === 'bar') {
@@ -661,9 +679,6 @@ return AbstractRenderer.extend({
_renderBarChart: function (dataPoints) {
var self = this;
- // style rectangles
- Chart.defaults.global.elements.rectangle.borderWidth = 1;
-
// prepare data
var data = this._prepareData(dataPoints);
data.datasets.forEach(function (dataset, index) {
@@ -694,10 +709,6 @@ return AbstractRenderer.extend({
_renderLineChart: function (dataPoints) {
var self = this;
- // style lines
- Chart.defaults.global.elements.line.tension = 0;
- Chart.defaults.global.elements.line.fill = false;
-
// prepare data
var data = this._prepareData(dataPoints);
data.datasets.forEach(function (dataset, index) {
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 2a8e44df4a2..afa8f09a857 100644
--- a/addons/web/static/src/js/views/list/list_renderer.js
+++ b/addons/web/static/src/js/views/list/list_renderer.js
@@ -761,7 +761,7 @@ var ListRenderer = BasicRenderer.extend({
}
if (field.type === 'float' || field.type === 'integer' || field.type === 'monetary') {
- $th.css({ textAlign: 'right' });
+ $th.addClass('o_list_number_th');
}
if (config.isDebug()) {
diff --git a/addons/web/static/src/scss/list_view.scss b/addons/web/static/src/scss/list_view.scss
index a18d9f70083..21743a75751 100644
--- a/addons/web/static/src/scss/list_view.scss
+++ b/addons/web/static/src/scss/list_view.scss
@@ -23,6 +23,9 @@
cursor: default;
}
}
+ &.o_list_number_th {
+ text-align: right;
+ }
}
tbody > tr > td:not(.o_list_record_selector) {
@include o-text-overflow(table-cell);
diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js
index 16e304c14a6..8a5b8343208 100644
--- a/addons/web/static/tests/views/calendar_tests.js
+++ b/addons/web/static/tests/views/calendar_tests.js
@@ -5,6 +5,7 @@ var AbstractStorageService = require('web.AbstractStorageService');
var CalendarView = require('web.CalendarView');
var CalendarRenderer = require('web.CalendarRenderer');
var Dialog = require('web.Dialog');
+var ViewDialogs = require('web.view_dialogs');
var fieldUtils = require('web.field_utils');
var mixins = require('web.mixins');
var RamStorage = require('web.RamStorage');
@@ -482,6 +483,63 @@ QUnit.module('Views', {
calendar.destroy();
});
+ QUnit.test('open multiple event form at the same time', async function (assert) {
+ assert.expect(2);
+
+ var prom = testUtils.makeTestPromise();
+ var counter = 0;
+ testUtils.mock.patch(ViewDialogs.FormViewDialog, {
+ open: function () {
+ counter++;
+ this.options = _.omit(this.options, 'fields_view'); // force loadFieldView
+ return this._super.apply(this, arguments);
+ },
+ loadFieldView: function () {
+ var self = this;
+ var args = arguments;
+ var _super = this._super;
+ return prom.then(function () {
+ return _super.apply(self, args);
+ });
+ },
+ });
+
+ var event = $.Event();
+ var calendar = await createCalendarView({
+ View: CalendarView,
+ model: 'event',
+ data: this.data,
+ arch:
+ '<calendar class="o_calendar_test" '+
+ 'string="Events" ' +
+ 'event_open_popup="true" '+
+ 'date_start="start" '+
+ 'quick_add="False" '+
+ 'date_stop="stop" '+
+ 'all_day="allday" '+
+ 'mode="month" '+
+ 'readonly_form_view_id="1">'+
+ '<field name="name"/>'+
+ '</calendar>',
+ archs: archs,
+ viewOptions: {
+ initialDate: initialDate,
+ },
+ });
+
+ var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)');
+ for (var i = 0; i < 5; i++) {
+ await testUtils.dom.triggerMouseEvent($cell, "mousedown");
+ await testUtils.dom.triggerMouseEvent($cell, "mouseup");
+ }
+ prom.resolve();
+ await testUtils.nextTick();
+ assert.equal(counter, 5, "there should had been 5 attemps to open a modal");
+ assert.containsOnce($('body'), '.modal', "there should be only one open modal");
+
+ calendar.destroy();
+ });
+
QUnit.test('create event with timezone in week mode European locale', async function (assert) {
assert.expect(5);
diff --git a/addons/web/static/tests/views/search_view_tests.js b/addons/web/static/tests/views/search_view_tests.js
index 3fe15d661dd..ccf5bf5b50d 100644
--- a/addons/web/static/tests/views/search_view_tests.js
+++ b/addons/web/static/tests/views/search_view_tests.js
@@ -981,8 +981,8 @@ QUnit.module('Search View', {
QUnit.module('Autocompletion');
- QUnit.test('selection via autocompletion modifies appropriately submenus', async function (assert) {
- assert.expect(4);
+ QUnit.test('selection via autocompletion and deletion modifies appropriately submenus', async function (assert) {
+ assert.expect(6);
var actionManager = await createActionManager({
actions: this.actions,
@@ -1014,6 +1014,19 @@ QUnit.module('Search View', {
assert.strictEqual($('.o_group_by_menu .o_item_option a.selected').text().trim(), "Day",
"The item 'Day' should be selected in the groupby menu");
+ await testUtils.dom.click($('div .o_searchview_facet .o_facet_remove').eq(0));
+ await testUtils.dom.click($('div .o_searchview_facet .o_facet_remove').eq(0));
+
+ await testUtils.dom.click($('button .fa-filter'));
+ await testUtils.dom.click($('.o_filters_menu .o_menu_item').eq(0));
+ assert.containsNone($('.o_filters_menu .o_item_option a.selected'),
+ "The item 'This Month' should have unselected in the filters menu");
+
+ await testUtils.dom.click($('button .fa-bars'));
+ await testUtils.dom.click($('.o_group_by_menu .o_menu_item').eq(0));
+ assert.containsNone($('.o_group_by_menu .o_item_option a.selected'),
+ "The item 'Day' should have been unselected in the groupby menu");
+
actionManager.destroy();
});