[MERGE] forward port branch 11.0 up to b05e4d5f95

This commit is contained in:
Christophe Simonis
2018-06-15 10:15:27 +02:00
38 changed files with 292 additions and 97 deletions
+1 -1
View File
@@ -50,7 +50,7 @@
<td><span t-field="aml.date"/></td>
<td><span t-field="aml.account_id.code"/></td>
<td><span t-esc="aml.sudo().partner_id and aml.sudo().partner_id.name and aml.sudo().partner_id.name[:23] or ''"/></td>
<td><span t-esc="aml.name[:35]"/></td>
<td><span t-esc="aml.name and aml.name[:35]"/></td>
<td><span t-esc="aml.debit" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/></td>
<td><span t-esc="aml.credit" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/></td>
<td t-if="data['form']['amount_currency'] and aml.amount_currency">
@@ -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')
+6 -1
View File
@@ -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'))
+3 -1
View File
@@ -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):
+1
View File
@@ -11,6 +11,7 @@
<button class="oe_stat_button"
type="object"
icon="fa-ticket"
groups="event.group_event_user"
name="action_event_view" attrs="{'invisible': [('event_count','=', 0)]}">
<field string="Events" name="event_count" widget="statinfo"/>
</button>
+2 -2
View File
@@ -593,7 +593,7 @@
<field name="refund_account_id" ref="vat_refund_h_non_eu"/>
<field name="account_id" ref="vat_refund_h_non_eu"/>
<field name="type_tax_use">none</field>
<field name="tag_ids" eval="[(6,0,[ref('tag_nl_34')])]"/>
<field name="tag_ids" eval="[(6,0,[ref('tag_nl_33')])]"/>
<field name="tax_group_id" ref="tax_group_21_niet_eu"/>
</record>
<record id="btw_E2" model="account.tax.template">
@@ -735,7 +735,7 @@
<field name="refund_account_id" ref="vat_refund_h_d_non_eu"/>
<field name="account_id" ref="vat_refund_h_d_non_eu"/>
<field name="type_tax_use">none</field>
<field name="tag_ids" eval="[(6,0,[ref('tag_nl_34')])]"/>
<field name="tag_ids" eval="[(6,0,[ref('tag_nl_33')])]"/>
<field name="tax_group_id" ref="tax_group_21_niet_eu"/>
</record>
<record id="btw_E2_d" model="account.tax.template">
@@ -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);
+3 -1
View File
@@ -202,12 +202,13 @@
</group>
</group>
<group>
<field name="time_ids" nolabel="1">
<field name="time_ids" nolabel="1" context="{'default_workcenter_id': workcenter_id}">
<tree>
<field name="date_start"/>
<field name="date_end"/>
<field name="duration" widget="float_time" sum="Total duration"/>
<field name="user_id"/>
<field name="workcenter_id" invisible="1"/>
<field name="loss_id" string="Efficiency"/>
</tree>
<form>
@@ -219,6 +220,7 @@
</group>
<group>
<field name="user_id"/>
<field name="workcenter_id"/>
<field name="loss_id"/>
</group>
</group>
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+12 -7
View File
@@ -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:
@@ -105,7 +105,9 @@
<td>
<t t-if="has_barcode">
<span t-if="move.product_id and move.product_id.barcode">
<img t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('Code128', move.product_id.barcode, 600, 100)" style="width:100%;height:50px"/>
<img t-if="move.product_id.barcode and len(move.product_id.barcode) == 13" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('EAN13', move.product_id.barcode, 600, 100)" style="width:100%;height:50px"/>
<img t-elif="move.product_id.barcode and len(move.product_id.barcode) == 8" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('EAN8', move.product_id.barcode, 600, 100)" style="width:100%;height:50px"/>
<img t-else="" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('Code128', move.product_id.barcode, 600, 100)" style="width:100%;height:50px"/>
</span>
</t>
</td>
+52 -1
View File
@@ -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.
@@ -0,0 +1,26 @@
<templates>
<t t-name="date-simple"><t t-esc='value' /></t>
<params id="date-simple">{"value": "1988-09-16"}</params>
<result id="date-simple">1988-09-16</result>
<t t-name="datetime-simple"><t t-esc='value' /></t>
<params id="datetime-simple">{"value": "1988-09-16 14:00:00"}</params>
<result id="datetime-simple">1988-09-16 14:00:00</result>
<t t-name="datetime-widget-datetime"><t t-esc='value' t-options="{'widget': 'datetime'}" /></t>
<params id="datetime-widget-datetime">{"value": "1988-09-16 14:00:00"}</params>
<result id="datetime-widget-datetime">09/16/1988 16:00:00</result>
<t t-name="datetime-widget-date"><t t-esc='value' t-options="{'widget': 'date'}" /></t>
<params id="datetime-widget-date">{"value": "1988-09-16 14:00:00"}</params>
<result id="datetime-widget-date">09/16/1988</result>
<t t-name="datetime-widget-date-tz2"><t t-esc='value' t-options="{'widget': 'date'}" /></t>
<params id="datetime-widget-date-tz2">{"value": "1988-09-16 01:00:00"}</params>
<result id="datetime-widget-date-tz2">09/16/1988</result>
<t t-name="datetime-widget-date-tz"><t t-esc='value' t-options="{'widget': 'date'}" /></t>
<params id="datetime-widget-date-tz">{"value": "1988-09-16 23:00:00"}</params>
<result id="datetime-widget-date-tz">09/17/1988</result>
</templates>
@@ -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.
+3 -3
View File
@@ -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);
+47 -4
View File
@@ -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;
+13 -37
View File
@@ -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;
@@ -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}
)
);
},
/**
@@ -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.
@@ -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(/>/);
@@ -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) {
+5
View File
@@ -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
+1 -1
View File
@@ -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,
})
-1
View File
@@ -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
+1 -1
View File
@@ -686,7 +686,7 @@ The previous code will create the snippet’s content, but we still need to plac
.. code-block:: xml
<template id="place_into_bar" inherit_id="website.snippets" name="Place into bar">
<xpath expr="//div[@id='snippet_content']/div[@class='o_panel_body']" position="inside">
<xpath expr="//div[@id='snippet_structure']/div[@class='o_panel_body']" position="inside">
<t t-snippet="theme_tutorial.snippet_testimonial"
t-thumbnail="/theme_tutorial/static/src/img/ui/snippet_thumb.jpg"/>
</xpath>
+39
View File
@@ -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 <languages>
specifies the languages (separated by commas) for the translations you
want to be loaded
.. option:: -l, --language <language>
specify the language of the translation file. Use it with --i18n-export
or --i18n-import
.. option:: --i18n-export <filename>
export all sentences to be translated to a CSV file, a PO file or a TGZ
archive and exit.
.. option:: --i18n-import <filename>
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
-------------
+18
View File
@@ -1329,11 +1329,27 @@ order.
- Supported field types: *date*, *datetime*
Options:
- datepicker: extra settings for the datepicker_ widget.
.. code-block:: xml
<field name="datefield" options='{"datepicker": {"daysOfWeekDisabled": [0, 6]}}'/>
- 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
<field name="datefield" options='{"datepicker": {"daysOfWeekDisabled": [0, 6]}}'/>
- 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/
+1 -1
View File
@@ -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)
@@ -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
+5 -3
View File
@@ -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):
+1 -3
View File
@@ -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):
+1
View File
@@ -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
+9 -11
View File
@@ -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
-1
View File
@@ -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:
+1 -1
View File
@@ -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)
+7 -5
View File
@@ -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)