diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 8894a278bec..bb480b534aa 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -1145,7 +1145,8 @@ class AccountInvoiceLine(models.Model): @api.one @api.depends('price_unit', 'discount', 'invoice_line_tax_ids', 'quantity', - 'product_id', 'invoice_id.partner_id', 'invoice_id.currency_id', 'invoice_id.company_id') + 'product_id', 'invoice_id.partner_id', 'invoice_id.currency_id', 'invoice_id.company_id', + 'invoice_id.date_invoice') def _compute_price(self): currency = self.invoice_id and self.invoice_id.currency_id or None price = self.price_unit * (1 - (self.discount or 0.0) / 100.0) @@ -1154,7 +1155,7 @@ class AccountInvoiceLine(models.Model): taxes = self.invoice_line_tax_ids.compute_all(price, currency, self.quantity, product=self.product_id, partner=self.invoice_id.partner_id) self.price_subtotal = price_subtotal_signed = taxes['total_excluded'] if taxes else self.quantity * price if self.invoice_id.currency_id and self.invoice_id.company_id and self.invoice_id.currency_id != self.invoice_id.company_id.currency_id: - price_subtotal_signed = self.invoice_id.currency_id.compute(price_subtotal_signed, self.invoice_id.company_id.currency_id) + price_subtotal_signed = self.invoice_id.currency_id.with_context(date=self.invoice_id.date_invoice).compute(price_subtotal_signed, self.invoice_id.company_id.currency_id) sign = self.invoice_id.type in ['in_refund', 'out_refund'] and -1 or 1 self.price_subtotal_signed = price_subtotal_signed * sign diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index bc5bd350de5..a0b297beee5 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -380,7 +380,7 @@ class AccountMoveLine(models.Model): statement_id = fields.Many2one('account.bank.statement', string='Statement', help="The bank statement used for bank reconciliation", index=True, copy=False) reconciled = fields.Boolean(compute='_amount_residual', store=True) - full_reconcile_id = fields.Many2one('account.full.reconcile', string="Matching Number") + full_reconcile_id = fields.Many2one('account.full.reconcile', string="Matching Number", copy=False) matched_debit_ids = fields.One2many('account.partial.reconcile', 'credit_move_id', String='Matched Debits', help='Debit journal items that are matched with this journal item.') matched_credit_ids = fields.One2many('account.partial.reconcile', 'debit_move_id', String='Matched Credits', @@ -1006,7 +1006,7 @@ class AccountMoveLine(models.Model): rec_move_ids = self.env['account.partial.reconcile'] for account_move_line in self: for invoice in account_move_line.payment_id.invoice_ids: - if account_move_line in invoice.payment_move_line_ids: + if invoice.id == self.env.context.get('invoice_id') and account_move_line in invoice.payment_move_line_ids: account_move_line.payment_id.write({'invoice_ids': [(3, invoice.id, None)]}) rec_move_ids += account_move_line.matched_debit_ids rec_move_ids += account_move_line.matched_credit_ids @@ -1325,7 +1325,7 @@ class AccountPartialReconcile(models.Model): company_currency_id = fields.Many2one('res.currency', related='company_id.currency_id', readonly=True, help='Utility field to express amount currency') company_id = fields.Many2one('res.company', related='debit_move_id.company_id', store=True, string='Currency') - full_reconcile_id = fields.Many2one('account.full.reconcile', string="Full Reconcile") + full_reconcile_id = fields.Many2one('account.full.reconcile', string="Full Reconcile", copy=False) def create_exchange_rate_entry(self, aml_to_fix, amount_diff, diff_in_currency, currency, move_date): """ Automatically create a journal entry to book the exchange rate difference. diff --git a/addons/account/static/src/js/account_payment_widget.js b/addons/account/static/src/js/account_payment_widget.js index 932de22eef5..a7f2b64c413 100644 --- a/addons/account/static/src/js/account_payment_widget.js +++ b/addons/account/static/src/js/account_payment_widget.js @@ -59,7 +59,7 @@ var ShowPaymentLineWidget = form_common.AbstractField.extend({ var payment_id = parseInt($(this).attr('payment-id')) if (payment_id !== undefined && payment_id !== NaN){ new Model("account.move.line") - .call("remove_move_reconcile", [payment_id]) + .call("remove_move_reconcile", [payment_id, {'invoice_id': self.view.datarecord.id}]) .then(function (result) { self.view.reload(); }); diff --git a/addons/bus/models/res_partner.py b/addons/bus/models/res_partner.py index 4e97437720d..b0b4c23d74f 100644 --- a/addons/bus/models/res_partner.py +++ b/addons/bus/models/res_partner.py @@ -34,24 +34,30 @@ class ResPartner(models.Model): :param name : the partner name to search :param limit : the limit of result to return """ - name = '%' + name + '%' - excluded_partner_ids = [self.env.user.partner_id.id] - self.env.cr.execute(""" - SELECT - U.id as user_id, - P.id as id, - P.name as name, - CASE WHEN B.last_poll IS NULL THEN 'offline' - WHEN age(now() AT TIME ZONE 'UTC', B.last_poll) > interval %s THEN 'offline' - WHEN age(now() AT TIME ZONE 'UTC', B.last_presence) > interval %s THEN 'away' - ELSE 'online' - END as im_status - FROM res_users U - JOIN res_partner P ON P.id = U.partner_id - LEFT JOIN bus_presence B ON B.user_id = U.id - WHERE P.name ILIKE %s - AND P.id NOT IN %s - AND U.active = 't' - LIMIT %s - """, ("%s seconds" % DISCONNECTION_TIMER, "%s seconds" % AWAY_TIMER, name, tuple(excluded_partner_ids), limit)) - return self.env.cr.dictfetchall() + # This method is supposed to be used only in the context of channel creation or + # extension via an invite. As both of these actions require the 'create' access + # right, we check this specific ACL. + if self.env['mail.channel'].check_access_rights('create', raise_exception=False): + name = '%' + name + '%' + excluded_partner_ids = [self.env.user.partner_id.id] + self.env.cr.execute(""" + SELECT + U.id as user_id, + P.id as id, + P.name as name, + CASE WHEN B.last_poll IS NULL THEN 'offline' + WHEN age(now() AT TIME ZONE 'UTC', B.last_poll) > interval %s THEN 'offline' + WHEN age(now() AT TIME ZONE 'UTC', B.last_presence) > interval %s THEN 'away' + ELSE 'online' + END as im_status + FROM res_users U + JOIN res_partner P ON P.id = U.partner_id + LEFT JOIN bus_presence B ON B.user_id = U.id + WHERE P.name ILIKE %s + AND P.id NOT IN %s + AND U.active = 't' + LIMIT %s + """, ("%s seconds" % DISCONNECTION_TIMER, "%s seconds" % AWAY_TIMER, name, tuple(excluded_partner_ids), limit)) + return self.env.cr.dictfetchall() + else: + return {} diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index e0303055a30..7db83a3cb29 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -169,6 +169,7 @@ class Attendee(models.Model): 'datas_fname': 'invitation.ics', 'datas': str(ics_file).encode('base64')})] vals['model'] = None # We don't want to have the mail in the tchatter while in queue! + vals['res_id'] = False current_mail = self.env['mail.mail'].browse(mail_id) current_mail.mail_message_id.write(vals) mails_to_send |= current_mail @@ -1357,11 +1358,11 @@ class Meeting(models.Model): super(Meeting, real_meetings).write(values) # set end_date for calendar searching - if values.get('recurrency') and values.get('end_type', 'count') in ('count', unicode('count')) and \ - (values.get('rrule_type') or values.get('count') or values.get('start') or values.get('stop')): + if any(field in values for field in ['recurrency', 'end_type', 'count', 'rrule_type', 'start', 'stop']): for real_meeting in real_meetings: - final_date = real_meeting._get_recurrency_end_date() - super(Meeting, real_meeting).write({'final_date': final_date}) + if real_meeting.recurrency and real_meeting.end_type in ('count', unicode('count')): + final_date = real_meeting._get_recurrency_end_date() + super(Meeting, real_meeting).write({'final_date': final_date}) attendees_create = False if values.get('partner_ids', False): @@ -1461,7 +1462,8 @@ class Meeting(models.Model): for r in result: if r['user_id']: user_id = type(r['user_id']) in (tuple, list) and r['user_id'][0] or r['user_id'] - if user_id == self.env.user.id: + partner_id = self.env.user.partner_id.id + if user_id == self.env.user.id or partner_id in r.get("partner_ids", []): continue if r['privacy'] == 'private': for f in r.keys(): diff --git a/addons/delivery/models/stock_picking.py b/addons/delivery/models/stock_picking.py index 916c00c1f30..32536372d5b 100644 --- a/addons/delivery/models/stock_picking.py +++ b/addons/delivery/models/stock_picking.py @@ -49,8 +49,11 @@ class StockPicking(models.Model): _inherit = 'stock.picking' def _default_uom(self): - uom_categ_id = self.env.ref('product.product_uom_categ_kgm').id - return self.env['product.uom'].search([('category_id', '=', uom_categ_id), ('factor', '=', 1)], limit=1) + weight_uom_id = self.env.ref('product.product_uom_kgm', raise_if_not_found=False) + if not weight_uom_id: + uom_categ_id = self.env.ref('product.product_uom_categ_kgm').id + weight_uom_id = self.env['product.uom'].search([('category_id', '=', uom_categ_id), ('factor', '=', 1)], limit=1) + return weight_uom_id @api.one @api.depends('pack_operation_ids') diff --git a/addons/hr/models/res_partner.py b/addons/hr/models/res_partner.py index 47bfe7e0534..8772bc46040 100644 --- a/addons/hr/models/res_partner.py +++ b/addons/hr/models/res_partner.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, models +from openerp.exceptions import AccessError class Partner(models.Model): @@ -13,12 +14,10 @@ class Partner(models.Model): """ Extend the mail's static mention suggestions by adding the employees. """ suggestions = super(Partner, self).get_static_mention_suggestions() - employee_group_id = self.env['ir.model.data'].xmlid_to_res_id('base.group_user') - self._cr.execute(""" - SELECT P.id, P.name, P.email - FROM res_users U - INNER JOIN res_groups_users_rel R ON U.id = R.uid - INNER JOIN res_partner P ON P.id = U.partner_id - WHERE R.gid = %s AND U.active = 't'""", (employee_group_id,)) - suggestions.append(self._cr.dictfetchall()) - return suggestions + try: + employee_group = self.env.ref('base.group_user') + for user in employee_group.users: + suggestions.append((user.partner_id.id, user.name, user.email)) + return suggestions + except AccessError: + return suggestions diff --git a/addons/mail/models/mail_followers.py b/addons/mail/models/mail_followers.py index b902231ba2e..a2c80e75f48 100644 --- a/addons/mail/models/mail_followers.py +++ b/addons/mail/models/mail_followers.py @@ -113,23 +113,31 @@ class Followers(models.Model): # Modifying followers change access rights to individual documents. As the # cache may contain accessible/inaccessible data, one has to refresh it. # + @api.multi + def _invalidate_documents(self): + """ Invalidate the cache of the documents followed by ``self``. """ + for record in self: + if record.res_id: + self.env[record.res_model].invalidate_cache(ids=[record.res_id]) + @api.model def create(self, vals): res = super(Followers, self).create(vals) - self.invalidate_cache() + res._invalidate_documents() return res @api.multi def write(self, vals): + if 'res_model' in vals or 'res_id' in vals: + self._invalidate_documents() res = super(Followers, self).write(vals) - self.invalidate_cache() + self._invalidate_documents() return res @api.multi def unlink(self): - res = super(Followers, self).unlink() - self.invalidate_cache() - return res + self._invalidate_documents() + return super(Followers, self).unlink() _sql_constraints = [ ('mail_followers_res_partner_res_model_id_uniq', 'unique(res_model,res_id,partner_id)', 'Error, a partner cannot follow twice the same object.'), diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index c16b67a8d20..e901c19d069 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -701,6 +701,13 @@ class Message(models.Model): message_id = tools.generate_tracking_message_id('private') return message_id + @api.multi + def _invalidate_documents(self): + """ Invalidate the cache of the documents followed by ``self``. """ + for record in self: + if record.res_id: + self.env[record.model].invalidate_cache(ids=[record.res_id]) + @api.model def create(self, values): # coming from mail.js that does not have pid in its values @@ -717,6 +724,7 @@ class Message(models.Model): values['record_name'] = self._get_record_name(values) message = super(Message, self).create(values) + message._invalidate_documents() if not self.env.context.get('message_create_from_mail_mail'): message._notify(force_send=self.env.context.get('mail_notify_force_send', True), @@ -730,6 +738,14 @@ class Message(models.Model): self.check_access_rule('read') return super(Message, self).read(fields=fields, load=load) + @api.multi + def write(self, vals): + if 'model' in vals or 'res_id' in vals: + self._invalidate_documents() + res = super(Message, self).write(vals) + self._invalidate_documents() + return res + @api.multi def unlink(self): # cascade-delete attachments that are directly attached to the message (should only happen @@ -738,6 +754,7 @@ class Message(models.Model): self.mapped('attachment_ids').filtered( lambda attach: attach.res_model == self._name and (attach.res_id in self.ids or attach.res_id == 0) ).unlink() + self._invalidate_documents() return super(Message, self).unlink() #------------------------------------------------------ diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py index 9f870b86ac2..a4935bb8ce0 100644 --- a/addons/mail/models/mail_template.py +++ b/addons/mail/models/mail_template.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import babel import base64 import copy import datetime @@ -18,6 +19,20 @@ from odoo.exceptions import UserError _logger = logging.getLogger(__name__) +def format_date(env, date, pattern=False): + date = datetime.datetime.strptime(date[:10], tools.DEFAULT_SERVER_DATE_FORMAT) + lang_code = env.context.get('lang') or 'en_US' + if not pattern: + lang = env['res.lang']._lang_get(lang_code) + pattern = lang.date_format + try: + locale = babel.Locale.parse(lang_code) + pattern = tools.posix_to_ldml(pattern, locale=locale) + return babel.dates.format_date(date, format=pattern, locale=locale) + except babel.core.UnknownLocaleError: + return date.strftime(pattern) + + def format_tz(env, dt, tz=False, format=False): record_user_timestamp = env.user.sudo().with_context(tz=tz or env.user.sudo().tz or 'UTC') timestamp = datetime.datetime.strptime(dt, tools.DEFAULT_SERVER_DATETIME_FORMAT) @@ -350,6 +365,7 @@ class MailTemplate(models.Model): for record in records: res_to_rec[record.id] = record variables = { + 'format_date': lambda date, format=False, context=self._context: format_dae(self.env, date, format), 'format_tz': lambda dt, tz=False, format=False, context=self._context: format_tz(self.env, dt, tz, format), 'user': self.env.user, 'ctx': self._context, # context kw would clash with mako internals diff --git a/addons/mail/tests/test_mail_features.py b/addons/mail/tests/test_mail_features.py index d95b13975b6..7c9fe1e49ab 100644 --- a/addons/mail/tests/test_mail_features.py +++ b/addons/mail/tests/test_mail_features.py @@ -12,6 +12,17 @@ class TestMailFeatures(TestMail): alias = self.env['mail.alias'].with_context(alias_model_name='mail.channel').create({'alias_name': 'b4r+_#_R3wl$$'}) self.assertEqual(alias.alias_name, 'b4r+_-_r3wl-', 'Disallowed chars should be replaced by hyphens') + def test_10_cache_invalidation(self): + """ Test that creating a mail-thread record does not invalidate the whole cache. """ + # make a new record in cache + record = self.env['res.partner'].new({'name': 'Brave New Partner'}) + self.assertTrue(record.name) + + # creating a mail-thread record should not invalidate the whole cache + self.env['res.partner'].create({'name': 'Actual Partner'}) + self.assertTrue(record.name) + + @mute_logger('odoo.addons.mail.models.mail_mail') def test_needaction(self): na_emp1_base = self.env['mail.message'].sudo(self.user_employee)._needaction_count(domain=[]) diff --git a/addons/mail/tests/test_mail_message.py b/addons/mail/tests/test_mail_message.py index cc68a1dd8bc..21f6e1850a2 100644 --- a/addons/mail/tests/test_mail_message.py +++ b/addons/mail/tests/test_mail_message.py @@ -268,3 +268,8 @@ class TestMailMessage(TestMail): msg.toggle_message_starred() self.assertFalse(msg.starred) self.assertTrue(msg_emp.starred) + + def test_60_cache_invalidation(self): + msg_cnt = len(self.group_pigs.message_ids) + self.group_pigs.message_post(body='Hi!', subject='test') + self.assertEqual(len(self.group_pigs.message_ids), msg_cnt + 1) diff --git a/addons/project/models/project.py b/addons/project/models/project.py index cf5ef270375..39a6aa468a8 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -394,7 +394,6 @@ class Task(models.Model): self.partner_id = self.project_id.partner_id self.stage_id = self.stage_find(self.project_id.id, [('fold', '=', False)]) else: - self.partner_id = False self.stage_id = False @api.onchange('user_id') diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 090e7892b8f..d41d8f62c33 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -25,7 +25,12 @@ class PurchaseOrder(models.Model): amount_untaxed = amount_tax = 0.0 for line in order.order_line: amount_untaxed += line.price_subtotal - amount_tax += line.price_tax + # FORWARDPORT UP TO 10.0 + if order.company_id.tax_calculation_rounding_method == 'round_globally': + taxes = line.taxes_id.compute_all(line.price_unit, line.order_id.currency_id, line.product_qty, product=line.product_id, partner=line.order_id.partner_id) + amount_tax += sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])) + else: + amount_tax += line.price_tax order.update({ 'amount_untaxed': order.currency_id.round(amount_untaxed), 'amount_tax': order.currency_id.round(amount_tax), diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 4daa504b573..b1cc00fd5b8 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -27,7 +27,13 @@ class SaleOrder(models.Model): amount_untaxed = amount_tax = 0.0 for line in order.order_line: amount_untaxed += line.price_subtotal - amount_tax += line.price_tax + # FORWARDPORT UP TO 10.0 + if order.company_id.tax_calculation_rounding_method == 'round_globally': + price = line.price_unit * (1 - (line.discount or 0.0) / 100.0) + taxes = line.tax_id.compute_all(price, line.order_id.currency_id, line.product_uom_qty, product=line.product_id, partner=line.order_id.partner_id) + amount_tax += sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])) + else: + amount_tax += line.price_tax order.update({ 'amount_untaxed': order.pricelist_id.currency_id.round(amount_untaxed), 'amount_tax': order.pricelist_id.currency_id.round(amount_tax), diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index 46704ffdcd3..566b882b5ac 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -262,7 +262,7 @@
- +

diff --git a/addons/web_editor/static/src/js/summernote.js b/addons/web_editor/static/src/js/summernote.js index 5c344fbbcd1..9d7304caf75 100644 --- a/addons/web_editor/static/src/js/summernote.js +++ b/addons/web_editor/static/src/js/summernote.js @@ -134,7 +134,7 @@ dom.hasProgrammaticStyle = function (node) { }; dom.mergeFilter = function (prev, cur, parent) { // merge text nodes - if (prev && (dom.isText(prev) || ("H1 H2 H3 H4 H5 H6 LI P".indexOf(prev.tagName) !== -1 && prev !== cur.parentNode)) && dom.isText(cur)) { + if (prev && (dom.isText(prev) || (['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'LI', 'P'].indexOf(prev.tagName) !== -1 && prev !== cur.parentNode)) && dom.isText(cur)) { return true; } if (prev && prev.tagName === "P" && dom.isText(cur)) { diff --git a/addons/website/static/src/less/website.snippets.less b/addons/website/static/src/less/website.snippets.less index 7b941acda2f..15e42fbe33f 100644 --- a/addons/website/static/src/less/website.snippets.less +++ b/addons/website/static/src/less/website.snippets.less @@ -112,7 +112,6 @@ table.table_desc tr td { height: 100%; .item { height: 100%; - background-size: cover; } .carousel-control { cursor: pointer; diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index dcf2a6dc200..79f8b38a0a1 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -76,7 +76,7 @@

-

+

diff --git a/addons/website_sale_stock/__init__.py b/addons/website_sale_stock/__init__.py index 67dee8c60db..5f9ef702708 100644 --- a/addons/website_sale_stock/__init__.py +++ b/addons/website_sale_stock/__init__.py @@ -1,2 +1,3 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import models \ No newline at end of file diff --git a/addons/website_sale_stock/models/__init__.py b/addons/website_sale_stock/models/__init__.py new file mode 100644 index 00000000000..b8b239729b6 --- /dev/null +++ b/addons/website_sale_stock/models/__init__.py @@ -0,0 +1 @@ +import website \ No newline at end of file diff --git a/addons/website_sale_stock/models/website.py b/addons/website_sale_stock/models/website.py new file mode 100644 index 00000000000..b5c866c27bb --- /dev/null +++ b/addons/website_sale_stock/models/website.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from odoo import api, models + +class Website(models.Model): + _inherit = 'website' + + @api.multi + def _prepare_sale_order_values(self, partner, pricelist): + self.ensure_one() + values = super(Website, self)._prepare_sale_order_values(partner, pricelist) + if values['company_id']: + warehouses = self.env['stock.warehouse'].sudo().search([('company_id', '=', values['company_id'])], limit=1) + if warehouses: + values['warehouse_id'] = warehouses.id + return values diff --git a/addons/website_slides/models/slides.py b/addons/website_slides/models/slides.py index e31f9c01415..58b5a670d6f 100644 --- a/addons/website_slides/models/slides.py +++ b/addons/website_slides/models/slides.py @@ -476,7 +476,7 @@ class Slide(models.Model): base_url = self.env['ir.config_parameter'].get_param('web.base.url') for slide in self.filtered(lambda slide: slide.website_published and slide.channel_id.publish_template_id): publish_template = slide.channel_id.publish_template_id - html_body = publish_template.with_context({'base_url': base_url}).render_template(publish_template.body_html, 'slide.slide', slide.id) + html_body = publish_template.with_context(base_url=base_url).render_template(publish_template.body_html, 'slide.slide', slide.id) subject = publish_template.render_template(publish_template.subject, 'slide.slide', slide.id) slide.channel_id.message_post( subject=subject, @@ -487,7 +487,7 @@ class Slide(models.Model): @api.one def send_share_email(self, email): base_url = self.env['ir.config_parameter'].get_param('web.base.url') - return self.channel_id.share_template_id.with_context({'email': email, 'base_url': base_url}).send_mail(self.id) + return self.channel_id.share_template_id.with_context(email=email, base_url=base_url).send_mail(self.id) # -------------------------------------------------- # Parsing methods diff --git a/doc/cla/corporate/bacgroup.md b/doc/cla/corporate/bacgroup.md new file mode 100644 index 00000000000..140a12e2e31 --- /dev/null +++ b/doc/cla/corporate/bacgroup.md @@ -0,0 +1,21 @@ +Honduras, 2017-01-12 + +Business Analytics Consulting Group S.A. de C.V. agrees to the terms of the Odoo Corporate Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Leandro Gabriel Di Pasquale dipasquale@bacgroup.net https://github.com/leodipasquale + +List of contributors: + +Allan Daniel Aguilar Archaga aguilar@bacgroup.net https://github.com/allanaguilar +Cesar Alejandro Rodriguez Castillo rodriguez@bacgroup.net https://github.com/hondurasopen +Dewin Javier Garcia Armijo garcia@bacgroup.net https://github.com/dewingarcia +Jose Reynaldo Cabrera Ayala cabrera@bacgroup.net https://github.com/jcabrera1990 +Leandro Gabriel Di Pasquale dipasquale@bacgroup.net https://github.com/leodipasquale +Edwin Omar McClellan Salinas mcclellan@bacgroup.net https://github.com/prometeomcclellan +Salvatore Josue Trimarchi Pinto trimarchi@bacgroup.net https://github.com/salvatoretrimarchi diff --git a/doc/cla/individual/boogerlad.md b/doc/cla/individual/boogerlad.md new file mode 100644 index 00000000000..af450cfd20a --- /dev/null +++ b/doc/cla/individual/boogerlad.md @@ -0,0 +1,11 @@ +canada, 2017/1/3 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +kevin hua boogerlad@gmail.com https://github.com/boogerlad diff --git a/odoo/addons/base/res/ir_property.py b/odoo/addons/base/res/ir_property.py index de3f4d9d214..0bca1578b69 100644 --- a/odoo/addons/base/res/ir_property.py +++ b/odoo/addons/base/res/ir_property.py @@ -265,13 +265,13 @@ class Property(models.Model): elif value <= 0 and operator == '>=': operator = '<' include_zero = True - elif value <= 0 and operator == '>': + elif value < 0 and operator == '>': operator = '<=' include_zero = True elif value >= 0 and operator == '<=': operator = '>' include_zero = True - elif value >= 0 and operator == '<': + elif value > 0 and operator == '<': operator = '>=' include_zero = True diff --git a/odoo/addons/base/res/res_lang.py b/odoo/addons/base/res/res_lang.py index 59a22f721b8..c43baf0ad49 100644 --- a/odoo/addons/base/res/res_lang.py +++ b/odoo/addons/base/res/res_lang.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import json import locale import logging import re @@ -69,7 +70,7 @@ class Lang(models.Model): 'Provided as the thousand separator in each case.') for lang in self: try: - if not all(isinstance(x, int) for x in safe_eval(lang.grouping)): + if not all(isinstance(x, int) for x in json.loads(lang.grouping)): raise ValidationError(warning) except Exception: raise ValidationError(warning) diff --git a/odoo/http.py b/odoo/http.py index f39354510df..ae7e2ae5e2b 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -820,7 +820,7 @@ more details. passing the `csrf=False` parameter to the `route` decorator. """, request.httprequest.path) - raise werkzeug.exceptions.BadRequest('Invalid CSRF Token') + raise werkzeug.exceptions.BadRequest('Session expired (invalid CSRF token)') r = self._call_function(**self.params) if not r: