+
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index 4492afb3f93..cd0f2ff0873 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -1496,6 +1496,8 @@ class ExcelExport(ExportFormat, http.Controller):
if isinstance(cell_value, pycompat.string_types):
cell_value = re.sub("\r", " ", pycompat.to_text(cell_value))
+ # Excel supports a maximum of 32767 characters in each cell:
+ cell_value = cell_value[:32767]
elif isinstance(cell_value, datetime.datetime):
cell_style = datetime_style
elif isinstance(cell_value, datetime.date):
diff --git a/addons/web/models/ir_http.py b/addons/web/models/ir_http.py
index 527dfeef0fe..dc081abaae3 100644
--- a/addons/web/models/ir_http.py
+++ b/addons/web/models/ir_http.py
@@ -26,7 +26,7 @@ class Http(models.AbstractModel):
"session_id": request.session.sid,
"uid": request.session.uid,
"is_system": user._is_system(),
- "is_superuser": user._is_superuser(),
+ "is_superuser": user._is_superuser() if request.session.uid else False,
"user_context": request.session.get_context() if request.session.uid else {},
"db": request.session.db,
"server_version": version_info.get('server_version'),
@@ -37,7 +37,7 @@ class Http(models.AbstractModel):
"company_id": user.company_id.id if request.session.uid else None,
"partner_id": user.partner_id.id if request.session.uid and user.partner_id else None,
"user_companies": {'current_company': (user.company_id.id, user.company_id.name), 'allowed_companies': [(comp.id, comp.name) for comp in user.company_ids]} if display_switch_company_menu else False,
- "currencies": self.get_currencies(),
+ "currencies": self.get_currencies() if request.session.uid else {},
"web.base.url": self.env['ir.config_parameter'].sudo().get_param('web.base.url', default=''),
"show_effect": True
}
diff --git a/addons/web/static/src/js/chrome/control_panel.js b/addons/web/static/src/js/chrome/control_panel.js
index 4104f37bd3e..0913b574a3d 100644
--- a/addons/web/static/src/js/chrome/control_panel.js
+++ b/addons/web/static/src/js/chrome/control_panel.js
@@ -148,15 +148,25 @@ var ControlPanel = Widget.extend({
}
// Detach control_panel old content and attach new elements
+ var toDetach = this.nodes;
+ if (status.searchview && this.searchview === status.searchview) {
+ // If the searchview is the same as before, don't detach it s.t.
+ // we don't loose any floating content, nor the focus
+ toDetach = _.omit(toDetach, '$searchview');
+ new_cp_content = _.omit(new_cp_content, '$searchview');
+ }
if (options.clear) {
- this._detach_content(this.nodes);
+ this._detach_content(toDetach);
// Show the searchview buttons area, which might have been hidden by
// the searchview, as client actions may insert elements into it
this.nodes.$searchview_buttons.show();
} else {
- this._detach_content(_.pick(this.nodes, _.keys(new_cp_content)));
+ this._detach_content(_.pick(toDetach, _.keys(new_cp_content)));
}
this._attach_content(new_cp_content);
+ if (options.clear || status.searchview) {
+ this.searchview = status.searchview;
+ }
// Update the searchview and switch buttons
if (status.searchview || options.clear) {
diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js
index 07aa8eacb4e..465afa1cd8a 100644
--- a/addons/web/static/src/js/views/list/list_editable_renderer.js
+++ b/addons/web/static/src/js/views/list/list_editable_renderer.js
@@ -866,8 +866,16 @@ ListRenderer.include({
*/
_onRemoveIconClick: function (event) {
event.stopPropagation();
- var id = $(event.target).closest('tr').data('id');
- this.trigger_up('list_record_remove', {id: id});
+ var $row = $(event.target).closest('tr');
+ var id = $row.data('id');
+ if ($row.hasClass('o_selected_row')) {
+ this.trigger_up('list_record_remove', {id: id});
+ } else {
+ var self = this;
+ this.unselectRow().then(function () {
+ self.trigger_up('list_record_remove', {id: id});
+ });
+ }
},
/**
* If the list view editable, just let the event bubble. We don't want to
diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js
index 216e6cf6756..bc0b20b21cc 100644
--- a/addons/web/static/tests/chrome/action_manager_tests.js
+++ b/addons/web/static/tests/chrome/action_manager_tests.js
@@ -3432,6 +3432,62 @@ QUnit.module('ActionManager', {
actionManager.destroy();
});
+
+ QUnit.module('Search View Action');
+
+ QUnit.test('search view should keep focus during do_search', function (assert) {
+ assert.expect(5);
+
+ /* One should be able to type something in the search view, press on enter to
+ * make the facet and trigger the search, then do this process
+ * over and over again seamlessly.
+ * Verifying the input's value is a lot trickier than verifying the search_read
+ * because of how native events are handled in tests
+ */
+
+ var searchDeferred = $.Deferred();
+
+ var actionManager = createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/search_read') {
+ assert.step('search_read ' + args.domain);
+ if ( _.isEqual(args.domain, [['foo', 'ilike', 'm']])) {
+ return searchDeferred.then(this._super.bind(this, route, args));
+ }
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ actionManager.doAction(3);
+
+ var $searchInput = $('.o_searchview input');
+ $searchInput.trigger($.Event('keypress', {key: 'm', which: 109, keyCode: 109}));
+ $searchInput.trigger($.Event('keydown', {key: 'Enter', which: 13, keyCode: 13}));
+
+ assert.verifySteps(["search_read ",
+ "search_read foo,ilike,m"]);
+
+ // Triggering the do_search above will kill the current searchview Input
+ $searchInput = $('.o_searchview input');
+ $searchInput.trigger($.Event('keypress', {key: 'o', which: 111, keyCode: 111}));
+
+ // We have something in the input of the search view. Making the search_read
+ // return at this point will trigger the redraw of the view.
+ // However we want to hold on to what we just typed
+ searchDeferred.resolve();
+
+ $searchInput.trigger($.Event('keydown', {key: 'Enter', which: 13, keyCode: 13}));
+
+ assert.verifySteps(["search_read ",
+ "search_read foo,ilike,m",
+ "search_read |,foo,ilike,m,foo,ilike,o"]);
+
+ actionManager.destroy();
+ });
});
});
diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js
index d8950ae44ab..d13446a958a 100644
--- a/addons/web/static/tests/views/form_tests.js
+++ b/addons/web/static/tests/views/form_tests.js
@@ -3257,6 +3257,42 @@ QUnit.module('Views', {
form.destroy();
});
+ QUnit.test('delete a line in a one2many while editing another line triggers a warning', function (assert) {
+ assert.expect(3);
+
+ this.data.partner.records[0].p = [1, 2];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
',
+ res_id: 1,
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_data_cell').first().click(); // edit first row
+ form.$('input').val('').trigger('input');
+ form.$('.fa-trash-o').eq(1).click(); // delete second row
+
+ assert.strictEqual($('.modal').find('.modal-title').first().text(), "Warning",
+ "Clicking out of a dirty line while editing should trigger a warning modal.");
+
+ $('.modal').find('.btn-primary').click(); // discard changes
+
+ assert.strictEqual(form.$('.o_data_cell').first().text(), "first record",
+ "Value should have been reset to what it was before editing began.");
+ assert.strictEqual(form.$('.o_data_row').length, 1,
+ "The other line should have been deleted.");
+ form.destroy();
+ });
+
QUnit.test('properly apply onchange on many2many fields', function (assert) {
assert.expect(14);
diff --git a/addons/web/views/report_templates.xml b/addons/web/views/report_templates.xml
index 9347dff1c07..4f97fedee66 100644
--- a/addons/web/views/report_templates.xml
+++ b/addons/web/views/report_templates.xml
@@ -14,9 +14,6 @@
-
-
-
diff --git a/addons/web_editor/static/src/js/backend/fields.js b/addons/web_editor/static/src/js/backend/fields.js
index a636b7e5be3..2bed78bf84f 100644
--- a/addons/web_editor/static/src/js/backend/fields.js
+++ b/addons/web_editor/static/src/js/backend/fields.js
@@ -111,9 +111,9 @@ var FieldTextHtmlSimple = basic_fields.DebouncedField.extend(TranslatableFieldMi
attachedDocumentDomain.unshift('&');
attachedDocumentDomain.push(['create_uid', '=', session.uid]);
}
- if (this.recordData.model) {
+ if (this.recordData.res_model || this.recordData.model) {
var relatedDomain = ['&',
- ['res_model', '=', this.recordData.model],
+ ['res_model', '=', this.recordData.res_model || this.recordData.model],
['res_id', '=', this.recordData.res_id|0]];
if (!this.recordData.res_id) {
relatedDomain.unshift('&');
@@ -142,7 +142,7 @@ var FieldTextHtmlSimple = basic_fields.DebouncedField.extend(TranslatableFieldMi
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
- ['insert', ['link', 'picture']],
+ ['insert', this.nodeOptions['no-attachment'] ? ['link'] : ['link', 'picture']],
['history', ['undo', 'redo']]
],
prettifyHtml: false,
@@ -150,6 +150,7 @@ var FieldTextHtmlSimple = basic_fields.DebouncedField.extend(TranslatableFieldMi
inlinemedia: ['p'],
lang: "odoo",
onChange: this._doDebouncedAction.bind(this),
+ disableDragAndDrop: !!this.nodeOptions['no-attachment'],
};
var fieldNameAttachment =_.chain(this.recordData)
@@ -164,8 +165,9 @@ var FieldTextHtmlSimple = basic_fields.DebouncedField.extend(TranslatableFieldMi
this.fieldNameAttachment = fieldNameAttachment;
this.attachments = [];
summernoteConfig.onUpload = this._onUpload.bind(this);
- summernoteConfig.getMediaDomain = this._getAttachmentsDomain.bind(this);
}
+ summernoteConfig.getMediaDomain = this._getAttachmentsDomain.bind(this);
+
if (config.debug) {
summernoteConfig.toolbar.splice(7, 0, ['view', ['codeview']]);
@@ -178,9 +180,9 @@ var FieldTextHtmlSimple = basic_fields.DebouncedField.extend(TranslatableFieldMi
*/
_getValue: function () {
if (this.nodeOptions['style-inline']) {
- transcoder.linkImgToAttachmentThumbnail(this.$content);
- transcoder.classToStyle(this.$content);
+ transcoder.attachmentThumbnailToLinkImg(this.$content);
transcoder.fontToImg(this.$content);
+ transcoder.classToStyle(this.$content);
}
return this.$content.html();
},
@@ -226,6 +228,8 @@ var FieldTextHtmlSimple = basic_fields.DebouncedField.extend(TranslatableFieldMi
this.$content.trigger('mouseup');
if (this.nodeOptions['style-inline']) {
transcoder.styleToClass(this.$content);
+ transcoder.imgToFont(this.$content);
+ transcoder.linkImgToAttachmentThumbnail(this.$content);
}
// reset the history (otherwise clicking on undo before editing the
// value will empty the editor)
diff --git a/addons/web_editor/static/src/js/inline.js b/addons/web_editor/static/src/js/inline.js
index b1de23cb293..7409f9190f3 100644
--- a/addons/web_editor/static/src/js/inline.js
+++ b/addons/web_editor/static/src/js/inline.js
@@ -41,6 +41,7 @@ snippet_editor.Class.include({
start: function () {
_.defer(function () {
var $editable = $('#editable_area');
+ transcoder.linkImgToAttachmentThumbnail($editable);
transcoder.imgToFont($editable);
transcoder.styleToClass($editable);
@@ -53,7 +54,7 @@ snippet_editor.Class.include({
this._super.apply(this, arguments);
var $editable = $('#editable_area');
- transcoder.linkImgToAttachmentThumbnail($editable);
+ transcoder.attachmentThumbnailToLinkImg($editable);
transcoder.fontToImg($editable);
transcoder.classToStyle($editable);
diff --git a/addons/web_editor/static/src/scss/web_editor.common.scss b/addons/web_editor/static/src/scss/web_editor.common.scss
index 2d760ff3193..ed125d62c1c 100644
--- a/addons/web_editor/static/src/scss/web_editor.common.scss
+++ b/addons/web_editor/static/src/scss/web_editor.common.scss
@@ -3,6 +3,7 @@
///
html, body {
+ position: relative;
width: 100%;
height: 100%;
}
diff --git a/addons/website/static/src/js/menu/content.js b/addons/website/static/src/js/menu/content.js
index 772ee02a3f8..1ff050b1b97 100644
--- a/addons/website/static/src/js/menu/content.js
+++ b/addons/website/static/src/js/menu/content.js
@@ -162,6 +162,7 @@ var PagePropertiesDialog = weWidgets.Dialog.extend({
horizontal: 'auto',
vertical: 'top',
},
+ widgetParent: 'body',
};
if (this.page.date_publish) {
datepickersOptions.defaultDate = time.str_to_datetime(this.page.date_publish);
diff --git a/addons/website_blog/models/website_blog.py b/addons/website_blog/models/website_blog.py
index 5ec51b99683..9e9fdb8fba0 100644
--- a/addons/website_blog/models/website_blog.py
+++ b/addons/website_blog/models/website_blog.py
@@ -237,7 +237,7 @@ class BlogPost(models.Model):
return super(BlogPost, self).get_access_action(access_uid)
return {
'type': 'ir.actions.act_url',
- 'url': self.url,
+ 'url': self.website_url,
'target': 'self',
'target_type': 'public',
'res_id': self.id,
diff --git a/addons/website_gengo/static/src/js/website_gengo.js b/addons/website_gengo/static/src/js/website_gengo.js
index ece25a58846..bb5bd609ab6 100644
--- a/addons/website_gengo/static/src/js/website_gengo.js
+++ b/addons/website_gengo/static/src/js/website_gengo.js
@@ -8,9 +8,6 @@ var Widget = require('web.Widget');
var weContext = require('web_editor.context');
require('web_editor.editor');
var translate = require('web_editor.translate');
-var websiteNavbarData = require('website.navbar');
-
-var WebsiteNavbar = websiteNavbarData.WebsiteNavbar;
var qweb = core.qweb;
var _t = core._t;
@@ -83,7 +80,7 @@ translate.Class.include({
'lang': weContext.get().lang,
}).then(function () {
ajax.jsonRpc('/website/post_gengo_jobs', 'call', {});
- self.save_and_reload();
+ self._save();
}).fail(function () {
Dialog.alert(null, _t("Could not Post translation"));
});
@@ -113,12 +110,12 @@ translate.Class.include({
});
var GengoTranslatorPostDialog = Widget.extend({
- events: _.extend({}, WebsiteNavbar.prototype.events, {
+ events: {
'hidden.bs.modal': 'destroy',
'click button[data-action=service_level]': function () {
this.trigger('service_level');
},
- }),
+ },
template: 'website.GengoTranslatorPostDialog',
init: function (new_words){
this.new_words = new_words;
@@ -130,9 +127,9 @@ var GengoTranslatorPostDialog = Widget.extend({
});
var GengoTranslatorStatisticDialog = Widget.extend({
- events: _.extend({}, WebsiteNavbar.prototype.events, {
+ events: {
'hidden.bs.modal': 'destroy',
- }),
+ },
template: 'website.GengoTranslatorStatisticDialog',
init: function (res) {
var self = this;
@@ -154,10 +151,10 @@ var GengoTranslatorStatisticDialog = Widget.extend({
});
var GengoApiConfigDialog = Widget.extend({
- events: _.extend({}, WebsiteNavbar.prototype.events, {
+ events: {
'hidden.bs.modal': 'destroy',
'click button[data-action=set_config]': 'set_config'
- }),
+ },
template: 'website.GengoApiConfigDialog',
init:function (company_id){
this.company_id = company_id;
diff --git a/addons/website_sale_delivery/controllers/main.py b/addons/website_sale_delivery/controllers/main.py
index 70b3b446303..5a06774fcad 100644
--- a/addons/website_sale_delivery/controllers/main.py
+++ b/addons/website_sale_delivery/controllers/main.py
@@ -36,13 +36,14 @@ class WebsiteSaleDelivery(WebsiteSale):
def _get_shop_payment_values(self, order, **kwargs):
values = super(WebsiteSaleDelivery, self)._get_shop_payment_values(order, **kwargs)
- if not order._get_delivery_methods():
+ has_stockable_products = any(line.product_id.type in ['consu', 'product'] for line in order.order_line)
+
+ if not order._get_delivery_methods() and has_stockable_products:
values['errors'].append(
(_('Sorry, we are unable to ship your order'),
_('No shipping method is available for your current order and shipping address. '
'Please contact us for more information.')))
- has_stockable_products = any(line.product_id.type in ['consu', 'product'] for line in order.order_line)
if has_stockable_products:
if order.carrier_id and not order.delivery_rating_success:
order._remove_delivery_line()
@@ -50,6 +51,7 @@ class WebsiteSaleDelivery(WebsiteSale):
delivery_carriers = order._get_delivery_methods()
values['deliveries'] = delivery_carriers.sudo()
+ values['delivery_has_stockable'] = has_stockable_products
values['delivery_action_id'] = request.env.ref('delivery.action_delivery_carrier_form').id
return values
diff --git a/addons/website_sale_delivery/views/website_sale_delivery_templates.xml b/addons/website_sale_delivery/views/website_sale_delivery_templates.xml
index 8568908032e..d9f8fd2c53a 100644
--- a/addons/website_sale_delivery/views/website_sale_delivery_templates.xml
+++ b/addons/website_sale_delivery/views/website_sale_delivery_templates.xml
@@ -52,6 +52,10 @@