diff --git a/addons/account/views/report_journal.xml b/addons/account/views/report_journal.xml
index 06dbab99ae4..c642b89347a 100644
--- a/addons/account/views/report_journal.xml
+++ b/addons/account/views/report_journal.xml
@@ -50,7 +50,7 @@
|
|
|
- |
+ |
|
|
diff --git a/addons/base_address_city/models/res_partner.py b/addons/base_address_city/models/res_partner.py
index c35430d1f92..07c42694ef1 100644
--- a/addons/base_address_city/models/res_partner.py
+++ b/addons/base_address_city/models/res_partner.py
@@ -10,7 +10,7 @@ from odoo.tools.translate import _
class Partner(models.Model):
_inherit = 'res.partner'
- country_enforce_cities = fields.Boolean(related='country_id.enforce_cities')
+ country_enforce_cities = fields.Boolean(related='country_id.enforce_cities', readonly=True)
city_id = fields.Many2one('res.city', string='City of Address')
@api.onchange('city_id')
diff --git a/addons/event/models/event.py b/addons/event/models/event.py
index faa4466b7c7..1c32394da62 100644
--- a/addons/event/models/event.py
+++ b/addons/event/models/event.py
@@ -443,9 +443,14 @@ class EventRegistration(models.Model):
@api.multi
def message_get_suggested_recipients(self):
recipients = super(EventRegistration, self).message_get_suggested_recipients()
+ public_users = self.env['res.users'].sudo()
+ public_groups = self.env.ref("base.group_public", raise_if_not_found=False)
+ if public_groups:
+ public_users = public_groups.sudo().with_context(active_test=False).mapped("users")
try:
for attendee in self:
- if attendee.partner_id:
+ is_public = attendee.sudo().with_context(active_test=False).partner_id.user_ids in public_users if public_users else False
+ if attendee.partner_id and not is_public:
attendee._message_add_suggested_recipient(recipients, partner=attendee.partner_id, reason=_('Customer'))
elif attendee.email:
attendee._message_add_suggested_recipient(recipients, email=attendee.email, reason=_('Customer Email'))
diff --git a/addons/event/models/res_partner.py b/addons/event/models/res_partner.py
index 727de940fa1..009b182c1ae 100644
--- a/addons/event/models/res_partner.py
+++ b/addons/event/models/res_partner.py
@@ -9,8 +9,10 @@ class ResPartner(models.Model):
event_count = fields.Integer("Events", compute='_compute_event_count', help="Number of events the partner has participated.")
def _compute_event_count(self):
+ if not self.user_has_groups('event.group_event_user'):
+ return
for partner in self:
- partner.event_count = self.env['event.event'].sudo().search_count([('registration_ids.partner_id', 'child_of', partner.ids)])
+ partner.event_count = self.env['event.event'].search_count([('registration_ids.partner_id', 'child_of', partner.ids)])
@api.multi
def action_event_view(self):
diff --git a/addons/event/views/res_partner_views.xml b/addons/event/views/res_partner_views.xml
index 59d096efcd1..22aeba8686a 100644
--- a/addons/event/views/res_partner_views.xml
+++ b/addons/event/views/res_partner_views.xml
@@ -11,6 +11,7 @@
diff --git a/addons/l10n_nl/data/account_tax_template.xml b/addons/l10n_nl/data/account_tax_template.xml
index c26dc9a8345..e9b937b98f5 100644
--- a/addons/l10n_nl/data/account_tax_template.xml
+++ b/addons/l10n_nl/data/account_tax_template.xml
@@ -593,7 +593,7 @@
none
-
+
@@ -735,7 +735,7 @@
none
-
+
diff --git a/addons/mail/static/src/js/services/chat_manager.js b/addons/mail/static/src/js/services/chat_manager.js
index 074d3d192d3..c4bb3303dde 100644
--- a/addons/mail/static/src/js/services/chat_manager.js
+++ b/addons/mail/static/src/js/services/chat_manager.js
@@ -1545,7 +1545,7 @@ var ChatManager = AbstractService.extend({
}
this._removeChannel(channel);
this.chatBus.trigger("unsubscribe_from_channel", data.id);
- web_client.do_notify(_("Unsubscribed"), msg);
+ web_client.do_notify(_t("Unsubscribed"), msg);
}
} else if (data.type === 'toggle_star') {
this._manageToggleStarNotification(data);
diff --git a/addons/mrp/views/mrp_workorder_views.xml b/addons/mrp/views/mrp_workorder_views.xml
index a764585eede..ffe9239673a 100644
--- a/addons/mrp/views/mrp_workorder_views.xml
+++ b/addons/mrp/views/mrp_workorder_views.xml
@@ -202,12 +202,13 @@
-
+
+
+
diff --git a/addons/payment_ogone/controllers/main.py b/addons/payment_ogone/controllers/main.py
index 1d03a688630..409e09b8a9e 100644
--- a/addons/payment_ogone/controllers/main.py
+++ b/addons/payment_ogone/controllers/main.py
@@ -110,7 +110,7 @@ class OgoneController(http.Controller):
def feedback(self, **kwargs):
try:
tx = request.env['payment.transaction'].sudo()._ogone_form_get_tx_from_data(kwargs)
- tx._ogone_s2s_validate()
+ tx._ogone_s2s_validate_tree(kwargs)
except ValidationError:
return 'ko'
return 'ok'
diff --git a/addons/project/controllers/portal.py b/addons/project/controllers/portal.py
index 105e90e9fba..1e94b2e2a04 100644
--- a/addons/project/controllers/portal.py
+++ b/addons/project/controllers/portal.py
@@ -143,7 +143,7 @@ class CustomerPortal(CustomerPortal):
# pager
pager = portal_pager(
url="/my/tasks",
- url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby, 'filterby': filterby},
+ url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby, 'filterby': filterby, 'search_in': search_in, 'search': search},
total=task_count,
page=page,
step=self._items_per_page
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index 1bdf4b3bf94..397952619d1 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -536,7 +536,7 @@ class PurchaseOrder(models.Model):
result['context'] = {}
pick_ids = self.mapped('picking_ids')
#choose the view_mode accordingly
- if len(pick_ids) > 1:
+ if not pick_ids or len(pick_ids) > 1:
result['domain'] = "[('id','in',%s)]" % (pick_ids.ids)
elif len(pick_ids) == 1:
res = self.env.ref('stock.view_picking_form', False)
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index 97a9082d776..53111d925fe 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -661,7 +661,7 @@ class StockMove(models.Model):
self.product_uom = product.uom_id.id
return {'domain': {'product_uom': [('category_id', '=', product.uom_id.category_id.id)]}}
- @api.onchange('date')
+ @api.onchange('date_expected')
def onchange_date(self):
if self.date_expected:
self.date = self.date_expected
@@ -872,11 +872,6 @@ class StockMove(models.Model):
package_id=package_id, owner_id=owner_id, strict=strict
)
except UserError:
- # If it raises here, it means that the `available_quantity` brought by a done move line
- # is not available on the quants itself. This could be the result of an inventory
- # adjustment that removed totally of partially `available_quantity`. When this happens, we
- # chose to do nothing. This situation could not happen on MTS move, because in this case
- # `available_quantity` is directly the quantity on the quants themselves.
taken_quantity = 0
# Find a candidate move line to update or create a new one.
@@ -992,7 +987,17 @@ class StockMove(models.Model):
available_move_lines[(move_line.location_id, move_line.lot_id, move_line.result_package_id, move_line.owner_id)] -= move_line.product_qty
for (location_id, lot_id, package_id, owner_id), quantity in available_move_lines.items():
need = move.product_qty - sum(move.move_line_ids.mapped('product_qty'))
- taken_quantity = move._update_reserved_quantity(need, quantity, location_id, lot_id, package_id, owner_id)
+ # `quantity` is what is brought by chained done move lines. We double check
+ # here this quantity is available on the quants themselves. If not, this
+ # could be the result of an inventory adjustment that removed totally of
+ # partially `quantity`. When this happens, we chose to reserve the maximum
+ # still available. This situation could not happen on MTS move, because in
+ # this case `quantity` is directly the quantity on the quants themselves.
+ available_quantity = self.env['stock.quant']._get_available_quantity(
+ move.product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True)
+ if float_is_zero(available_quantity, precision_rounding=move.product_id.uom_id.rounding):
+ continue
+ taken_quantity = move._update_reserved_quantity(need, min(quantity, available_quantity), location_id, lot_id, package_id, owner_id)
if float_is_zero(taken_quantity, precision_rounding=move.product_id.uom_id.rounding):
continue
if need - taken_quantity == 0.0:
diff --git a/addons/stock/report/report_stockpicking_operations.xml b/addons/stock/report/report_stockpicking_operations.xml
index 836e0d0e843..98d850db36c 100644
--- a/addons/stock/report/report_stockpicking_operations.xml
+++ b/addons/stock/report/report_stockpicking_operations.xml
@@ -105,7 +105,9 @@
-
+
+
+
|
diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py
index 53948b477f9..8025cf4a13c 100644
--- a/addons/stock/tests/test_move.py
+++ b/addons/stock/tests/test_move.py
@@ -1751,7 +1751,7 @@ class StockMove(TransactionCase):
ml.qty_done = 1
res_dict_for_back_order = picking_pack_cust.button_validate()
backorder_wizard = self.env[(res_dict_for_back_order.get('res_model'))].browse(res_dict_for_back_order.get('res_id'))
- backorder_wizard.with_context(debug=True).process()
+ backorder_wizard.process()
backorder = self.env['stock.picking'].search([('backorder_id', '=', picking_pack_cust.id)])
backordered_move = backorder.move_lines
@@ -1782,6 +1782,57 @@ class StockMove(TransactionCase):
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.customer_location), 3)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.pack_location), 0)
+ def test_link_assign_10(self):
+ """ Test the assignment mechanism with partial availability.
+ """
+ # make some stock:
+ # stock location: 2.0
+ # pack location: -1.0
+ self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2.0)
+ self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0)
+
+ move_out = self.env['stock.move'].create({
+ 'name': 'test_link_assign_out',
+ 'location_id': self.pack_location.id,
+ 'location_dest_id': self.customer_location.id,
+ 'product_id': self.product1.id,
+ 'product_uom': self.uom_unit.id,
+ 'product_uom_qty': 1.0,
+ })
+ move_out._action_confirm()
+ move_out._action_assign()
+ move_out.quantity_done = 1.0
+ move_out._action_done()
+ self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.pack_location)), 1.0)
+
+ move_stock_pack = self.env['stock.move'].create({
+ 'name': 'test_link_assign_1_1',
+ 'location_id': self.stock_location.id,
+ 'location_dest_id': self.pack_location.id,
+ 'product_id': self.product1.id,
+ 'product_uom': self.uom_unit.id,
+ 'product_uom_qty': 2.0,
+ })
+ move_pack_cust = self.env['stock.move'].create({
+ 'name': 'test_link_assign_1_2',
+ 'location_id': self.pack_location.id,
+ 'location_dest_id': self.customer_location.id,
+ 'product_id': self.product1.id,
+ 'product_uom': self.uom_unit.id,
+ 'product_uom_qty': 2.0,
+ })
+ move_stock_pack.write({'move_dest_ids': [(4, move_pack_cust.id, 0)]})
+ move_pack_cust.write({'move_orig_ids': [(4, move_stock_pack.id, 0)]})
+
+ (move_stock_pack + move_pack_cust)._action_confirm()
+ move_stock_pack._action_assign()
+ move_stock_pack.quantity_done = 2.0
+ move_stock_pack._action_done()
+ self.assertEqual(len(move_pack_cust.move_line_ids), 1)
+
+ self.assertAlmostEqual(move_pack_cust.reserved_availability, 1.0)
+ self.assertEqual(move_pack_cust.state, 'partially_available')
+
def test_use_unreserved_move_line_1(self):
""" Test that validating a stock move linked to an untracked product reserved by another one
correctly unreserves the other one.
diff --git a/addons/web/static/lib/qweb/qweb-test-widgets.xml b/addons/web/static/lib/qweb/qweb-test-widgets.xml
new file mode 100644
index 00000000000..503880bcc34
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb-test-widgets.xml
@@ -0,0 +1,26 @@
+
+
+ {"value": "1988-09-16"}
+ 1988-09-16
+
+
+ {"value": "1988-09-16 14:00:00"}
+ 1988-09-16 14:00:00
+
+
+ {"value": "1988-09-16 14:00:00"}
+ 09/16/1988 16:00:00
+
+
+ {"value": "1988-09-16 14:00:00"}
+ 09/16/1988
+
+
+ {"value": "1988-09-16 01:00:00"}
+ 09/16/1988
+
+
+ {"value": "1988-09-16 23:00:00"}
+ 09/17/1988
+
+
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 9fe38adbde6..9e010986061 100644
--- a/addons/web/static/src/js/chrome/abstract_web_client.js
+++ b/addons/web/static/src/js/chrome/abstract_web_client.js
@@ -169,7 +169,7 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, {
core.bus.on('connection_restored', this, this.on_connection_restored);
// crash manager integration
- session.on('error', crash_manager, crash_manager.rpc_error);
+ core.bus.on('rpc_error', crash_manager, crash_manager.rpc_error);
window.onerror = function (message, file, line, col, error) {
// Scripts injected in DOM (eg: google API's js files) won't return a clean error on window.onerror.
// The browser will just give you a 'Script error.' as message and nothing else for security issue.
diff --git a/addons/web/static/src/js/chrome/loading.js b/addons/web/static/src/js/chrome/loading.js
index 0c1b5e5025f..6c7f102231b 100644
--- a/addons/web/static/src/js/chrome/loading.js
+++ b/addons/web/static/src/js/chrome/loading.js
@@ -26,9 +26,9 @@ var Loading = Widget.extend({
this._super(parent);
this.count = 0;
this.blocked_ui = false;
- session.on("request", this, this.request_call);
- session.on("response", this, this.response_call);
- session.on("response_failed", this, this.response_call);
+ core.bus.on('rpc_request', this, this.request_call);
+ core.bus.on("rpc_response", this, this.response_call);
+ core.bus.on("rpc_response_failed", this, this.response_call);
},
destroy: function() {
this.on_rpc_event(-this.count);
diff --git a/addons/web/static/src/js/core/ajax.js b/addons/web/static/src/js/core/ajax.js
index d6c67eacaec..f1f73bb5a28 100644
--- a/addons/web/static/src/js/core/ajax.js
+++ b/addons/web/static/src/js/core/ajax.js
@@ -5,7 +5,12 @@ var core = require('web.core');
var utils = require('web.utils');
var time = require('web.time');
-function genericJsonRpc (fct_name, params, fct) {
+function genericJsonRpc (fct_name, params, settings, fct) {
+ var shadow = settings.shadow || false;
+ delete settings.shadow;
+ if (! shadow)
+ core.bus.trigger('rpc_request');
+
var data = {
jsonrpc: "2.0",
method: fct_name,
@@ -30,11 +35,49 @@ function genericJsonRpc (fct_name, params, fct) {
});
// FIXME: jsonp?
result.abort = function () { if (xhr.abort) xhr.abort(); };
- return result;
+
+ var p = result.then(function (result) {
+ if (!shadow) {
+ core.bus.trigger('rpc_response');
+ }
+ return result;
+ }, function (type, error, textStatus, errorThrown) {
+ if (type === "server") {
+ if (!shadow) {
+ core.bus.trigger('rpc_response');
+ }
+ if (error.code === 100) {
+ core.bus.trigger('invalidate_session');
+ }
+ return $.Deferred().reject(error, $.Event());
+ } else {
+ if (!shadow) {
+ core.bus.trigger('rpc_response_failed');
+ }
+ var nerror = {
+ code: -32098,
+ message: "XmlHttpRequestError " + errorThrown,
+ data: {
+ type: "xhr"+textStatus,
+ debug: error.responseText,
+ objects: [error, errorThrown]
+ },
+ };
+ return $.Deferred().reject(nerror, $.Event());
+ }
+ });
+ return p.fail(function () { // Allow deferred user to disable rpc_error call in fail
+ p.fail(function (error, event) {
+ if (!event.isDefaultPrevented()) {
+ core.bus.trigger('rpc_error', error, event);
+ }
+ });
+ });
}
function jsonRpc(url, fct_name, params, settings) {
- return genericJsonRpc(fct_name, params, function(data) {
+ settings = settings || {};
+ return genericJsonRpc(fct_name, params, settings, function(data) {
return $.ajax(url, _.extend({}, settings, {
url: url,
dataType: 'json',
@@ -47,7 +90,7 @@ function jsonRpc(url, fct_name, params, settings) {
function jsonpRpc(url, fct_name, params, settings) {
settings = settings || {};
- return genericJsonRpc(fct_name, params, function(data) {
+ return genericJsonRpc(fct_name, params, settings, function(data) {
var payload_str = JSON.stringify(data, time.date_to_utc);
var payload_url = $.param({r:payload_str});
var force2step = settings.force2step || false;
diff --git a/addons/web/static/src/js/core/session.js b/addons/web/static/src/js/core/session.js
index 4760867e99f..cc372877e6d 100644
--- a/addons/web/static/src/js/core/session.js
+++ b/addons/web/static/src/js/core/session.js
@@ -49,6 +49,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
this.qweb_mutex = new concurrency.Mutex();
this.currencies = {};
this._groups_def = {};
+ core.bus.on('invalidate_session', this, this._onInvalidateSession);
},
setup: function (origin, options) {
// must be able to customize server
@@ -327,23 +328,17 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
rpc: function (url, params, options) {
var self = this;
options = _.clone(options || {});
- var shadow = options.shadow || false;
options.headers = _.extend({}, options.headers);
if (odoo.debug) {
options.headers["X-Debug-Mode"] = $.deparam($.param.querystring()).debug;
}
- delete options.shadow;
-
return self.check_session_id().then(function () {
// TODO: remove
if (! _.isString(url)) {
_.extend(options, url);
url = url.url;
}
- // TODO correct handling of timeouts
- if (! shadow)
- self.trigger('request');
var fct;
if (self.origin_server) {
fct = ajax.jsonRpc;
@@ -362,37 +357,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
url = self.url(url, null);
options.session_id = self.session_id || '';
}
- var p = fct(url, "call", params, options);
- p = p.then(function (result) {
- if (! shadow)
- self.trigger('response');
- return result;
- }, function (type, error, textStatus, errorThrown) {
- if (type === "server") {
- if (! shadow)
- self.trigger('response');
- if (error.code === 100) {
- self.uid = false;
- }
- return $.Deferred().reject(error, $.Event());
- } else {
- if (! shadow)
- self.trigger('response_failed');
- var nerror = {
- code: -32098,
- message: "XmlHttpRequestError " + errorThrown,
- data: {type: "xhr"+textStatus, debug: error.responseText, objects: [error, errorThrown] }
- };
- return $.Deferred().reject(nerror, $.Event());
- }
- });
- return p.fail(function () { // Allow deferred user to disable rpc_error call in fail
- p.fail(function (error, event) {
- if (!event.isDefaultPrevented()) {
- self.trigger('error', error, event);
- }
- });
- });
+ return fct(url, "call", params, options);
});
},
url: function (path, params) {
@@ -418,6 +383,17 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
getTZOffset: function (date) {
return -new Date(date).getTimezoneOffset();
},
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _onInvalidateSession: function () {
+ this.uid = false;
+ },
});
return Session;
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index a62043b28c8..fed57c8b178 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -482,7 +482,13 @@ var FieldDate = InputField.extend({
* @private
*/
_makeDatePicker: function () {
- return new datepicker.DateWidget(this, {defaultDate: this.value});
+ return new datepicker.DateWidget(
+ this,
+ _.defaults(
+ this.nodeOptions.datepicker || {},
+ {defaultDate: this.value}
+ )
+ );
},
/**
diff --git a/addons/web_editor/static/lib/summernote/src/js/module/Editor.js b/addons/web_editor/static/lib/summernote/src/js/module/Editor.js
index b7ba30bfcd1..99947c346cf 100644
--- a/addons/web_editor/static/lib/summernote/src/js/module/Editor.js
+++ b/addons/web_editor/static/lib/summernote/src/js/module/Editor.js
@@ -628,9 +628,9 @@ define([
}
var anchors = [];
- // ODOO: adding this branch to modify existing anchor
+ // ODOO: adding this branch to modify existing anchor if it fully contains the range
var ancestor_anchor = dom.ancestor(rng.sc, dom.isAnchor);
- if(ancestor_anchor) {
+ if(ancestor_anchor && ancestor_anchor === dom.ancestor(rng.ec, dom.isAnchor)) {
anchors.push($(ancestor_anchor).html(linkText).get(0));
} else if (isTextChanged) {
// Create a new link when text changed.
diff --git a/addons/web_editor/static/src/js/editor/rte.summernote.js b/addons/web_editor/static/src/js/editor/rte.summernote.js
index 3de70e5fcda..614ab4bfcf0 100644
--- a/addons/web_editor/static/src/js/editor/rte.summernote.js
+++ b/addons/web_editor/static/src/js/editor/rte.summernote.js
@@ -474,7 +474,7 @@ function prettify_html(html) {
while (i--) space += ' ';
return space;
},
- reg = /^<\/?(a|span|font|strong|u|i|strong|b)(\s|>)/i,
+ reg = /^<\/?(a|span|font|u|em|i|strong|b)(\s|>)/i,
inline_level = Infinity,
tokens = _.compact(_.flatten(_.map(html.split(/), function (value) {
value = value.replace(/\s+/g, ' ').split(/>/);
diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js
index 51ff585d200..7aa300b5bbb 100644
--- a/addons/web_editor/static/src/js/widgets/widgets.js
+++ b/addons/web_editor/static/src/js/widgets/widgets.js
@@ -1398,6 +1398,8 @@ var LinkDialog = Dialog.extend({
if (dom.ancestor(nodes[i], dom.isImg)) {
this.data.images.push(dom.ancestor(nodes[i], dom.isImg));
text += '[IMG]';
+ } else if (!is_link && nodes[i].nodeType === 1) {
+ // just use text nodes from listBetween
} else if (!is_link && i===0) {
text += nodes[i].textContent.slice(so, Infinity);
} else if (!is_link && i===nodes.length-1) {
diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py
index 0e5443ad79e..aa703071d9b 100644
--- a/addons/website/models/ir_http.py
+++ b/addons/website/models/ir_http.py
@@ -6,6 +6,7 @@ import traceback
import os
import unittest
+import pytz
import werkzeug
import werkzeug.routing
import werkzeug.utils
@@ -74,6 +75,10 @@ class Http(models.AbstractModel):
context = {}
if not request.context.get('tz'):
context['tz'] = request.session.get('geoip', {}).get('time_zone')
+ try:
+ pytz.timezone(context['tz'] or '')
+ except pytz.UnknownTimeZoneError:
+ context.pop('tz')
request.website = request.env['website'].get_current_website() # can use `request.env` since auth methods are called
context['website_id'] = request.website.id
diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py
index 38bd5b0ce7f..f3a8041d54e 100644
--- a/addons/website_event/controllers/main.py
+++ b/addons/website_event/controllers/main.py
@@ -261,6 +261,6 @@ class WebsiteEventController(http.Controller):
Attendees._prepare_attendee_values(registration))
return request.render("website_event.registration_complete", {
- 'attendees': Attendees,
+ 'attendees': Attendees.sudo(),
'event': event,
})
diff --git a/doc/cla/corporate/eficent.md b/doc/cla/corporate/eficent.md
index 284e2b7b691..70184f115c8 100644
--- a/doc/cla/corporate/eficent.md
+++ b/doc/cla/corporate/eficent.md
@@ -14,7 +14,6 @@ List of contributors:
Aarón Henríquez ahenriquez@eficent.com https://github.com/aheficent
Lois Rilo lois.rilo@eficent.com https://github.com/lreficent
-Roser Garcia roser.garcia@eficent.com https://github.com/rgarnau
Miquel Raich miquel.raich@eficent.com https://github.com/mreficent
Jordi Ballester jordi.ballester@eficent.com https://github.com/jbeficent
Hector Villarreal hector.villarreal@eficent.com https://github.com/hveficent
diff --git a/doc/howtos/themes.rst b/doc/howtos/themes.rst
index 5252ed5b776..f9158631282 100644
--- a/doc/howtos/themes.rst
+++ b/doc/howtos/themes.rst
@@ -686,7 +686,7 @@ The previous code will create the snippet’s content, but we still need to plac
.. code-block:: xml
-
+
diff --git a/doc/reference/cmdline.rst b/doc/reference/cmdline.rst
index 4171f12c5e1..12cd5f7a9d8 100644
--- a/doc/reference/cmdline.rst
+++ b/doc/reference/cmdline.rst
@@ -221,6 +221,45 @@ database
'verify-ca' or 'verify-full'
Default value is 'prefer'
+.. _reference/cmdline/server/internationalisation:
+
+Internationalisation
+--------------------
+
+Use these options to translate Odoo to another language. See i18n section of
+the user manual. Option '-d' is mandatory. Option '-l' is mandatory in case
+of importation
+
+.. option:: --load-language
+
+ specifies the languages (separated by commas) for the translations you
+ want to be loaded
+
+.. option:: -l, --language
+
+ specify the language of the translation file. Use it with --i18n-export
+ or --i18n-import
+
+.. option:: --i18n-export
+
+ export all sentences to be translated to a CSV file, a PO file or a TGZ
+ archive and exit.
+
+.. option:: --i18n-import
+
+ import a CSV or a PO file with translations and exit. The '-l' option is
+ required.
+
+.. option:: --i18n-overwrite
+
+ overwrites existing translation terms on updating a module or importing
+ a CSV or a PO file.
+
+.. option:: --modules
+
+ specify modules to export. Use in combination with --i18n-export
+
+
built-in HTTP
-------------
diff --git a/doc/reference/javascript_reference.rst b/doc/reference/javascript_reference.rst
index b77aa9f5612..3c782ed00a8 100644
--- a/doc/reference/javascript_reference.rst
+++ b/doc/reference/javascript_reference.rst
@@ -1329,11 +1329,27 @@ order.
- Supported field types: *date*, *datetime*
+ Options:
+
+ - datepicker: extra settings for the datepicker_ widget.
+
+ .. code-block:: xml
+
+
+
- datetime (FieldDateTime)
This is the default field type for fields of type *datetime*.
- Supported field types: *date*, *datetime*
+ Options:
+
+ - datepicker: extra settings for the datepicker_ widget.
+
+ .. code-block:: xml
+
+
+
- monetary (FieldMonetary)
This is the default field type for fields of type 'monetary'. It is used to
display a currency. If there is a currency fields given in option, it will
@@ -1814,4 +1830,6 @@ Relational fields
.. _event delegation:
http://api.jquery.com/delegate/
+.. _datepicker: https://github.com/Eonasdan/bootstrap-datetimepicker
+
.. _deferred: http://api.jquery.com/category/deferred-object/
diff --git a/odoo/addons/base/models/ir_model.py b/odoo/addons/base/models/ir_model.py
index da42722d390..4f7eb1e2c25 100644
--- a/odoo/addons/base/models/ir_model.py
+++ b/odoo/addons/base/models/ir_model.py
@@ -28,7 +28,7 @@ SAFE_EVAL_BASE = {
def make_compute(text, deps):
""" Return a compute function from its code body and dependencies. """
func = lambda self: safe_eval(text, SAFE_EVAL_BASE, {'self': self}, mode="exec")
- deps = [arg.strip() for arg in (deps or "").split(",")]
+ deps = [arg.strip() for arg in deps.split(",")] if deps else []
return api.depends(*deps)(func)
diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py
index e555567c90e..3c358a5bc52 100644
--- a/odoo/addons/test_new_api/tests/test_new_fields.py
+++ b/odoo/addons/test_new_api/tests/test_new_fields.py
@@ -61,6 +61,19 @@ class TestFields(common.TransactionCase):
self.assertTrue(field.store)
self.assertTrue(field.readonly)
+ def test_10_computed_custom(self):
+ """ check definition of custom computed fields """
+ self.env['ir.model.fields'].create({
+ 'name': 'x_bool_false_computed',
+ 'model_id': self.env.ref('test_new_api.model_test_new_api_message').id,
+ 'field_description': 'A boolean computed to false',
+ 'compute': "for r in self: r['x_bool_false_computed'] = False",
+ 'store': False,
+ 'ttype': 'boolean'
+ })
+ field = self.env['test_new_api.message']._fields['x_bool_false_computed']
+ self.assertFalse(field.depends)
+
def test_10_non_stored(self):
""" test non-stored fields """
# a field declared with store=False should not have a column
diff --git a/odoo/http.py b/odoo/http.py
index f984bcb44df..09999066941 100644
--- a/odoo/http.py
+++ b/odoo/http.py
@@ -1038,7 +1038,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
self.db = db
self.uid = uid
self.login = login
- self.session_token = uid and security.compute_session_token(self)
+ self.session_token = uid and security.compute_session_token(self, request.env)
request.uid = uid
request.disable_db = False
@@ -1053,9 +1053,11 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
"""
if not self.db or not self.uid:
raise SessionExpiredException("Session expired")
-
+ # We create our own environment instead of the request's one.
+ # to avoid creating it without the uid since request.uid isn't set yet
+ env = odoo.api.Environment(request.cr, self.uid, self.context)
# here we check if the session is still valid
- if not security.check_session(self):
+ if not security.check_session(self, env):
raise SessionExpiredException("Session expired")
def logout(self, keep_db=False):
diff --git a/odoo/modules/registry.py b/odoo/modules/registry.py
index 3115470adeb..38f45249ebc 100644
--- a/odoo/modules/registry.py
+++ b/odoo/modules/registry.py
@@ -153,9 +153,7 @@ class Registry(Mapping):
""" Delete the registry linked to a given database. """
with cls._lock:
if db_name in cls.registries:
- registry = cls.registries.pop(db_name)
- registry.clear_caches()
- registry.registry_invalidated = True
+ cls.registries.pop(db_name)
@classmethod
def delete_all(cls):
diff --git a/odoo/service/db.py b/odoo/service/db.py
index ce868f693dd..8f3c24d7d8a 100644
--- a/odoo/service/db.py
+++ b/odoo/service/db.py
@@ -394,6 +394,7 @@ def list_db_incompatible(databases):
incompatible_databases.append(database_name)
else:
incompatible_databases.append(database_name)
+ for database_name in incompatible_databases:
# release connection
odoo.sql_db.close_db(database_name)
return incompatible_databases
diff --git a/odoo/service/security.py b/odoo/service/security.py
index 229e4b84077..54d108fb4b1 100644
--- a/odoo/service/security.py
+++ b/odoo/service/security.py
@@ -12,15 +12,13 @@ def check(db, uid, passwd):
res_users = odoo.registry(db)['res.users']
return res_users.check(db, uid, passwd)
-def compute_session_token(session):
- with odoo.registry(session.db).cursor() as cr:
- self = odoo.api.Environment(cr, session.uid, {})['res.users'].browse(session.uid)
- return self._compute_session_token(session.sid)
+def compute_session_token(session, env):
+ self = env['res.users'].browse(session.uid)
+ return self._compute_session_token(session.sid)
-def check_session(session):
- with odoo.registry(session.db).cursor() as cr:
- self = odoo.api.Environment(cr, session.uid, {})['res.users'].browse(session.uid)
- if odoo.tools.misc.consteq(self._compute_session_token(session.sid), session.session_token):
- return True
- self._invalidate_session_cache()
- return False
+def check_session(session, env):
+ self = env['res.users'].browse(session.uid)
+ if odoo.tools.misc.consteq(self._compute_session_token(session.sid), session.session_token):
+ return True
+ self._invalidate_session_cache()
+ return False
diff --git a/odoo/service/server.py b/odoo/service/server.py
index c42c8d1343a..8899760ce91 100644
--- a/odoo/service/server.py
+++ b/odoo/service/server.py
@@ -832,7 +832,6 @@ class WorkerCron(Worker):
from odoo.addons import base
base.models.ir_cron.ir_cron._acquire_job(db_name)
- odoo.modules.registry.Registry.delete(db_name)
# dont keep cursors in multi database mode
if len(db_names) > 1:
diff --git a/odoo/tests/common.py b/odoo/tests/common.py
index 53efa5d458d..2dcfd121cd4 100644
--- a/odoo/tests/common.py
+++ b/odoo/tests/common.py
@@ -367,7 +367,7 @@ class HttpCase(TransactionCase):
session.db = db
session.uid = uid
session.login = user
- session.session_token = uid and security.compute_session_token(session)
+ session.session_token = uid and security.compute_session_token(session, env)
session.context = env['res.users'].context_get() or {}
session.context['uid'] = uid
session._fix_lang(session.context)
diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py
index 09ad85d278b..725971e0143 100644
--- a/odoo/tools/misc.py
+++ b/odoo/tools/misc.py
@@ -1177,13 +1177,15 @@ def format_date(env, value, lang_code=False, date_format=False):
'''
if not value:
return ''
- if isinstance(value, datetime.datetime):
- value = value.date()
- elif isinstance(value, pycompat.string_types):
+ if isinstance(value, pycompat.string_types):
if len(value) < DATE_LENGTH:
return ''
- value = value[:DATE_LENGTH]
- value = datetime.datetime.strptime(value, DEFAULT_SERVER_DATE_FORMAT).date()
+ if len(value) > DATE_LENGTH:
+ # a datetime, convert to correct timezone
+ value = odoo.fields.Datetime.from_string(value)
+ value = odoo.fields.Datetime.context_timestamp(env['res.lang'], value)
+ else:
+ value = odoo.fields.Datetime.from_string(value)
lang = env['res.lang']._lang_get(lang_code or env.context.get('lang') or 'en_US')
locale = babel.Locale.parse(lang.code)
|