diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index 520b3ddad80..1caddec7f37 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -1272,7 +1272,7 @@ class account_move_line(osv.osv): period = period_obj.browse(cr, uid, period_id, context=context) for (state,) in result: if state == 'done': - raise UserError(_('You can not add/modify entries in a closed period %s of journal %s.') % (period.name,journal.name)) + raise UserError(_('You can not add/modify entries in a closed period %s of journal %s.') % (period.name, journal.name)) if not result: jour_period_obj.create(cr, uid, { 'name': (journal.code or journal.name)+':'+(period.name or ''), diff --git a/addons/base_import/models.py b/addons/base_import/models.py index 9778391521a..784ae7f49f1 100644 --- a/addons/base_import/models.py +++ b/addons/base_import/models.py @@ -158,12 +158,19 @@ class ir_import(orm.TransientModel): all the fields to traverse :rtype: list(Field) """ + string_match = None for field in fields: # FIXME: should match all translations & original # TODO: use string distance (levenshtein? hamming?) - if header.lower() == field['name'].lower() \ - or header.lower() == field['string'].lower(): + if header.lower() == field['name'].lower(): return [field] + if header.lower() == field['string'].lower(): + # matching string are not reliable way because + # strings have no unique constraint + string_match = field + if string_match: + # this behavior is only applied if there is no matching field['name'] + return [string_match] if '/' not in header: return [] diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index ea0eef6535d..4bcd2e58905 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -480,13 +480,13 @@ class calendar_alarm_manager(osv.AbstractModel): def get_next_notif(self, cr, uid, context=None): ajax_check_every_seconds = 300 - partner = self.pool['res.users'].read(cr, SUPERUSER_ID, uid, ['calendar_last_notif_ack'], context=context) + partner = self.pool['res.users'].read(cr, SUPERUSER_ID, uid, ['partner_id', 'calendar_last_notif_ack'], context=context) all_notif = [] if not partner: return [] - all_events = self.get_next_potential_limit_alarm(cr, uid, ajax_check_every_seconds, partner_id=partner['id'], mail=False, context=context) + all_events = self.get_next_potential_limit_alarm(cr, uid, ajax_check_every_seconds, partner_id=partner['partner_id'][0], mail=False, context=context) for event in all_events: # .values() max_delta = all_events[event]['max_duration'] @@ -586,7 +586,7 @@ class calendar_alarm(osv.Model): def _update_cron(self, cr, uid, context=None): try: cron = self.pool['ir.model.data'].get_object( - cr, uid, 'calendar', 'ir_cron_scheduler_alarm', context=context) + cr, SUPERUSER_ID, 'calendar', 'ir_cron_scheduler_alarm', context=context) except ValueError: return False return cron.toggle(model=self._name, domain=[('type', '=', 'email')]) diff --git a/addons/google_calendar/google_calendar.py b/addons/google_calendar/google_calendar.py index 1eb3f918d3e..df90f78f5d1 100644 --- a/addons/google_calendar/google_calendar.py +++ b/addons/google_calendar/google_calendar.py @@ -976,7 +976,9 @@ class calendar_event(osv.Model): _inherit = "calendar.event" def get_fields_need_update_google(self, cr, uid, context=None): - return ['name', 'description', 'allday', 'date', 'date_end', 'stop', 'attendee_ids', 'alarm_ids', 'location', 'class', 'active'] + return ['name', 'description', 'allday', 'start', 'date_end', 'stop', + 'attendee_ids', 'alarm_ids', 'location', 'class', 'active', + 'start_date', 'start_datetime', 'stop_date', 'stop_datetime'] def write(self, cr, uid, ids, vals, context=None): if context is None: diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 2e0823add68..ac4154e80d0 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -319,7 +319,7 @@ class mail_compose_message(osv.TransientModel): # static wizard (mail.message) values mail_values = { 'subject': wizard.subject, - 'body': wizard.body, + 'body': wizard.body or '', 'parent_id': wizard.parent_id and wizard.parent_id.id, 'partner_ids': [partner.id for partner in wizard.partner_ids], 'attachment_ids': [attach.id for attach in wizard.attachment_ids], diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index fbcee18d3e4..851f7368497 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -214,6 +214,8 @@ class mrp_bom(osv.osv): @param properties: List of related properties. @return: False or BoM id. """ + if not context: + context = {} if properties is None: properties = [] if product_id: @@ -231,6 +233,8 @@ class mrp_bom(osv.osv): else: # neither product nor template, makes no sense to search return False + if context.get('company_id'): + domain = domain + [('company_id', '=', context['company_id'])] domain = domain + [ '|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATE_FORMAT)), '|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATE_FORMAT))] # order to prioritize bom with product_id over the one without diff --git a/addons/mrp/procurement.py b/addons/mrp/procurement.py index 8eb61d6c5cc..a0a9863491e 100644 --- a/addons/mrp/procurement.py +++ b/addons/mrp/procurement.py @@ -87,7 +87,7 @@ class procurement_order(osv.osv): else: properties = [x.id for x in procurement.property_ids] bom_id = bom_obj._bom_find(cr, uid, product_id=procurement.product_id.id, - properties=properties, context=context) + properties=properties, context=dict(context, company_id=procurement.company_id.id)) bom = bom_obj.browse(cr, uid, bom_id, context=context) routing_id = bom.routing_id.id return { diff --git a/addons/mrp/stock.py b/addons/mrp/stock.py index fa73c994a38..77e3b0d27b6 100644 --- a/addons/mrp/stock.py +++ b/addons/mrp/stock.py @@ -44,6 +44,23 @@ class StockMove(osv.osv): if move.raw_material_production_id and move.location_dest_id.usage == 'production' and move.raw_material_production_id.product_id.track_production and not move.consumed_for: raise UserError(_("Because the product %s requires it, you must assign a serial number to your raw material %s to proceed further in your production. Please use the 'Produce' button to do so.") % (move.raw_material_production_id.product_id.name, move.product_id.name)) + # TODO master: remove me, no longer used + def _check_phantom_bom(self, cr, uid, move, context=None): + """check if product associated to move has a phantom bom + return list of ids of mrp.bom for that product """ + user_company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id + #doing the search as SUPERUSER because a user with the permission to write on a stock move should be able to explode it + #without giving him the right to read the boms. + domain = [ + '|', ('product_id', '=', move.product_id.id), + '&', ('product_id', '=', False), ('product_tmpl_id.product_variant_ids', '=', move.product_id.id), + ('type', '=', 'phantom'), + '|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)), + '|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)), + ('company_id', '=', user_company)] + return self.pool.get('mrp.bom').search(cr, SUPERUSER_ID, domain, context=context) + + def _action_explode(self, cr, uid, move, context=None): """ Explodes pickings. @param move: Stock moves diff --git a/addons/mrp_operations/mrp_operations.py b/addons/mrp_operations/mrp_operations.py index 86a50626558..d86c8e6cd63 100644 --- a/addons/mrp_operations/mrp_operations.py +++ b/addons/mrp_operations/mrp_operations.py @@ -337,7 +337,7 @@ class mrp_production(osv.osv): for po in self.browse(cr, uid, ids, context=context): direction[po.id] = cmp(po.date_start, vals.get('date_start', False)) result = super(mrp_production, self).write(cr, uid, ids, vals, context=context) - if (vals.get('workcenter_lines', False) or vals.get('date_start', False)) and update: + if (vals.get('workcenter_lines', False) or vals.get('date_start', False) or vals.get('date_planned', False)) and update: self._compute_planned_workcenter(cr, uid, ids, context=context, mini=mini) for d in direction: if direction[d] == 1: diff --git a/addons/payment_adyen/models/adyen.py b/addons/payment_adyen/models/adyen.py index 52336cb48d9..6539ebb5ae0 100644 --- a/addons/payment_adyen/models/adyen.py +++ b/addons/payment_adyen/models/adyen.py @@ -172,7 +172,7 @@ class TxAdyen(osv.Model): }) return True else: - error = _('Paypal: feedback error') + error = _('Adyen: feedback error') _logger.info(error) tx.write({ 'state': 'error', diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index d1c4dff4cec..c8b07da4cc6 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -1362,9 +1362,9 @@ class pos_order_line(osv.osv): 'name': fields.char('Line No', required=True, copy=False), 'notice': fields.char('Discount Notice'), 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True, change_default=True), - 'price_unit': fields.float(string='Unit Price', digits_compute=dp.get_precision('Account')), + 'price_unit': fields.float(string='Unit Price', digits_compute=dp.get_precision('Product Price')), 'qty': fields.float('Quantity', digits_compute=dp.get_precision('Product UoS')), - 'price_subtotal': fields.function(_amount_line_all, multi='pos_order_line_amount', digits_compute=dp.get_precision('Account'), string='Subtotal w/o Tax', store=True), + 'price_subtotal': fields.function(_amount_line_all, multi='pos_order_line_amount', digits_compute=dp.get_precision('Product Price'), string='Subtotal w/o Tax', store=True), 'price_subtotal_incl': fields.function(_amount_line_all, multi='pos_order_line_amount', digits_compute=dp.get_precision('Account'), string='Subtotal', store=True), 'discount': fields.float('Discount (%)', digits_compute=dp.get_precision('Account')), 'order_id': fields.many2one('pos.order', 'Order Ref', ondelete='cascade'), diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index ba751839ab1..4976d8da07e 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1646,34 +1646,34 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo }, /* ---- Payment Status --- */ get_subtotal : function(){ - return this.orderlines.reduce((function(sum, orderLine){ + return round_pr(this.orderlines.reduce((function(sum, orderLine){ return sum + orderLine.get_display_price(); - }), 0); + }), 0), this.pos.currency.rounding); }, get_total_with_tax: function() { - return this.orderlines.reduce((function(sum, orderLine) { + return round_pr(this.orderlines.reduce((function(sum, orderLine) { return sum + orderLine.get_price_with_tax(); - }), 0); + }), 0), this.pos.currency.rounding); }, get_total_without_tax: function() { - return this.orderlines.reduce((function(sum, orderLine) { + return round_pr(this.orderlines.reduce((function(sum, orderLine) { return sum + orderLine.get_price_without_tax(); - }), 0); + }), 0), this.pos.currency.rounding); }, get_total_discount: function() { - return this.orderlines.reduce((function(sum, orderLine) { + return round_pr(this.orderlines.reduce((function(sum, orderLine) { return sum + (orderLine.get_unit_price() * (orderLine.get_discount()/100) * orderLine.get_quantity()); - }), 0); + }), 0), this.pos.currency.rounding); }, get_total_tax: function() { - return this.orderlines.reduce((function(sum, orderLine) { + return round_pr(this.orderlines.reduce((function(sum, orderLine) { return sum + orderLine.get_tax(); - }), 0); + }), 0), this.pos.currency.rounding); }, get_total_paid: function() { - return this.paymentlines.reduce((function(sum, paymentLine) { + return round_pr(this.paymentlines.reduce((function(sum, paymentLine) { return sum + paymentLine.get_amount(); - }), 0); + }), 0), this.pos.currency.rounding); }, get_tax_details: function(){ var details = {}; diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index b31472cb63e..0e817695a6d 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -1453,6 +1453,7 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ // this.inputbuffer = ""; this.firstinput = true; + this.decimal_point = instance.web._t.database.parameters.decimal_point; // This is a keydown handler that prevents backspace from // doing a back navigation @@ -1515,7 +1516,14 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ // this.inputbuffer = newbuf; var order = this.pos.get_order(); if (order.selected_paymentline) { - order.selected_paymentline.set_amount(parseFloat(this.inputbuffer)); + var amount; + try{ + amount = instance.web.parse_value(this.inputbuffer, {type: "float"}); + } + catch(e){ + amount = 0; + } + order.selected_paymentline.set_amount(amount); this.order_changes(); this.render_paymentlines(); this.$('.paymentline.selected .edit').text(this.inputbuffer); diff --git a/addons/point_of_sale/static/src/js/widget_base.js b/addons/point_of_sale/static/src/js/widget_base.js index a391b3d19d3..a4006723fe8 100644 --- a/addons/point_of_sale/static/src/js/widget_base.js +++ b/addons/point_of_sale/static/src/js/widget_base.js @@ -48,6 +48,7 @@ openerp.point_of_sale.load_basewidget = function load_basewidget(instance, modul if (typeof amount === 'number') { amount = round_di(amount,decimals).toFixed(decimals); + amount = openerp.instances[this.session.name].web.format_value(parseFloat(amount), { type : 'float' }); } return amount; diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 449e5511dc9..9e5ce7e5008 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -1174,7 +1174,10 @@
- + diff --git a/addons/pos_restaurant/static/src/js/splitbill.js b/addons/pos_restaurant/static/src/js/splitbill.js index f3d672ca7ac..519bc7104c0 100644 --- a/addons/pos_restaurant/static/src/js/splitbill.js +++ b/addons/pos_restaurant/static/src/js/splitbill.js @@ -43,7 +43,7 @@ openerp.pos_restaurant.load_splitbill = function(instance, module){ } }else{ if( split.quantity < line.get_quantity()){ - split.quantity += line.get_unit().rounding; + split.quantity += line.get_unit().is_unit ? 1 : line.get_unit().rounding; if(split.quantity > line.get_quantity()){ split.quantity = line.get_quantity(); } diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index d0fde9e74ad..8c928e11b77 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -1666,7 +1666,7 @@ class account_invoice(osv.Model): else: user_id = uid po_ids = purchase_order_obj.search(cr, user_id, [('invoice_ids', 'in', ids)], context=context) - for order in purchase_order_obj.browse(cr, uid, po_ids, context=context): + for order in purchase_order_obj.browse(cr, user_id, po_ids, context=context): purchase_order_obj.message_post(cr, user_id, order.id, body=_("Invoice received"), context=context) invoiced = [] shipped = True @@ -1679,8 +1679,8 @@ class account_invoice(osv.Model): if all(line.invoice_id.state not in ['draft', 'cancel'] for line in po_line.invoice_lines): invoiced.append(po_line.id) if invoiced and shipped: - self.pool['purchase.order.line'].write(cr, uid, invoiced, {'invoiced': True}) - workflow.trg_write(uid, 'purchase.order', order.id, cr) + self.pool['purchase.order.line'].write(cr, user_id, invoiced, {'invoiced': True}) + workflow.trg_write(user_id, 'purchase.order', order.id, cr) return res def confirm_paid(self, cr, uid, ids, context=None): diff --git a/addons/report/models/report.py b/addons/report/models/report.py index 604bf5aa37b..11937235a77 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -39,7 +39,6 @@ import lxml.html import os import subprocess from contextlib import closing -from datetime import datetime from distutils.version import LooseVersion from functools import partial from pyPdf import PdfFileWriter, PdfFileReader @@ -141,7 +140,6 @@ class Report(osv.Model): context = dict(context, translatable=context.get('lang') != request.website.default_lang_code) values.update( time=time, - datetime=datetime, context_timestamp=lambda t: fields.datetime.context_timestamp(cr, uid, t, context), translate_doc=translate_doc, editable=True, diff --git a/addons/report/views/layouts.xml b/addons/report/views/layouts.xml index 620bfb9b3b5..37d93226e7f 100644 --- a/addons/report/views/layouts.xml +++ b/addons/report/views/layouts.xml @@ -124,7 +124,7 @@
- +
diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py index 80d3553284a..c849331d5e0 100644 --- a/addons/sale_stock/sale_stock.py +++ b/addons/sale_stock/sale_stock.py @@ -363,6 +363,14 @@ class sale_order_line(osv.osv): res.update({'warning': warning}) return res + def button_cancel(self, cr, uid, ids, context=None): + lines = self.browse(cr, uid, ids, context=context) + for procurement in lines.mapped('procurement_ids'): + for move in procurement.move_ids: + if move.state == 'done' and not move.scrapped: + raise osv.except_osv(_('Invalid Action!'), _('You cannot cancel a sale order line which is linked to a stock move already done.')) + return super(sale_order_line, self).button_cancel(cr, uid, ids, context=context) + class stock_move(osv.osv): _inherit = 'stock.move' @@ -389,7 +397,7 @@ class stock_move(osv.osv): if move.procurement_id and move.procurement_id.sale_line_id and move.procurement_id.sale_line_id.order_id.order_policy == 'picking': sale_order = move.procurement_id.sale_line_id.order_id return sale_order.partner_invoice_id, sale_order.user_id.id, sale_order.pricelist_id.currency_id.id - elif move.picking_id.sale_id and context.get('inv_type') == 'out_invoice': + elif move.picking_id.sale_id and context.get('inv_type') in ('out_invoice', 'out_refund'): # In case of extra move, it is better to use the same data as the original moves sale_order = move.picking_id.sale_id return sale_order.partner_invoice_id, sale_order.user_id.id, sale_order.pricelist_id.currency_id.id @@ -397,7 +405,7 @@ class stock_move(osv.osv): def _get_invoice_line_vals(self, cr, uid, move, partner, inv_type, context=None): res = super(stock_move, self)._get_invoice_line_vals(cr, uid, move, partner, inv_type, context=context) - if move.procurement_id and move.procurement_id.sale_line_id: + if inv_type in ('out_invoice', 'out_refund') and move.procurement_id and move.procurement_id.sale_line_id: sale_line = move.procurement_id.sale_line_id res['invoice_line_tax_id'] = [(6, 0, [x.id for x in sale_line.tax_id])] res['account_analytic_id'] = sale_line.order_id.project_id and sale_line.order_id.project_id.id or False diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 6b8fb997fad..8a502a10b78 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -794,7 +794,7 @@ class stock_picking(osv.osv): 'name': fields.char('Reference', select=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, copy=False), 'origin': fields.char('Source Document', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="Reference of the document", select=True), 'backorder_id': fields.many2one('stock.picking', 'Back Order of', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="If this shipment was split, then this field links to the shipment which contains the already processed part.", select=True, copy=False), - 'note': fields.text('Notes', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}), + 'note': fields.text('Notes'), 'move_type': fields.selection([('direct', 'Partial'), ('one', 'All at once')], 'Delivery Method', required=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="It specifies goods to be deliver partially or all at once"), 'state': fields.function(_state_get, type="selection", copy=False, store={ diff --git a/addons/stock/wizard/stock_transfer_details.py b/addons/stock/wizard/stock_transfer_details.py index 17f302f6317..437509aacf3 100644 --- a/addons/stock/wizard/stock_transfer_details.py +++ b/addons/stock/wizard/stock_transfer_details.py @@ -91,7 +91,7 @@ class stock_transfer_details(models.TransientModel): 'owner_id': prod.owner_id.id, } if prod.packop_id: - prod.packop_id.write(pack_datas) + prod.packop_id.with_context(no_recompute=True).write(pack_datas) processed_ids.append(prod.packop_id.id) else: pack_datas['picking_id'] = self.picking_id.id @@ -99,8 +99,7 @@ class stock_transfer_details(models.TransientModel): processed_ids.append(packop_id.id) # Delete the others packops = self.env['stock.pack.operation'].search(['&', ('picking_id', '=', self.picking_id.id), '!', ('id', 'in', processed_ids)]) - for packop in packops: - packop.unlink() + packops.unlink() # Execute the transfer of the picking self.picking_id.do_transfer() diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 43efd2c38d7..5aaf63d034f 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1183,6 +1183,7 @@ class Binary(http.Controller): } except Exception: args = {'error': "Something horrible happened"} + _logger.exception("Fail to upload attachment %s" % ufile.filename) return out % (simplejson.dumps(callback), simplejson.dumps(args)) @http.route([ diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index d0f77b77593..73cb4e1161c 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -1023,7 +1023,7 @@ instance.web.Sidebar = instance.web.Widget.extend({ init: function(parent) { var self = this; this._super(parent); - var view = this.getParent(); + this.view = this.getParent(); this.sections = [ { 'name' : 'print', 'label' : _t('Print'), }, { 'name' : 'other', 'label' : _t('More'), } diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 379b28597ef..39173add5c6 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -625,12 +625,12 @@ - + -
  • +
  • /web/binary/upload_attachment diff --git a/addons/website/static/src/js/website.translator.js b/addons/website/static/src/js/website.translator.js index 12f6a1397f8..64683f7001f 100644 --- a/addons/website/static/src/js/website.translator.js +++ b/addons/website/static/src/js/website.translator.js @@ -60,9 +60,11 @@ ev.preventDefault(); var link = $('.js_language_selector a[data-default-lang]')[0]; if (link) { - link.pathname = $(link).data('lang') + link.pathname; link.search += (link.search ? '&' : '?') + 'enable_editor=1'; - window.location = link.attributes.href.value; + var url = link.pathname + link.search + window.location.hash; + link.pathname = '/website/lang/default'; + link.search = '?' + $.param({r: url}); + window.location = link.href; } }, translate: function () { diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index b8bdb8e0edd..071c7ff636d 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -92,8 +92,8 @@ - - + + diff --git a/doc/cla/individual/colinnewell.md b/doc/cla/individual/colinnewell.md new file mode 100644 index 00000000000..89a4ab891bb --- /dev/null +++ b/doc/cla/individual/colinnewell.md @@ -0,0 +1,12 @@ +United Kingdom, 2015-05-18 + +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, + +Colin Newell colin.newell@gmail.com https://github.com/colinnewell + diff --git a/doc/cla/individual/samuellefever.md b/doc/cla/individual/samuellefever.md new file mode 100644 index 00000000000..83fb7bc6bd5 --- /dev/null +++ b/doc/cla/individual/samuellefever.md @@ -0,0 +1,11 @@ +Belgium, 2015-04-14 + +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, + +Samuel Lefever sam@niboo.be https://github.com/samuellefever \ No newline at end of file diff --git a/openerp/addons/base/ir/ir_http.py b/openerp/addons/base/ir/ir_http.py index e6eb793aa76..6181ee19c31 100644 --- a/openerp/addons/base/ir/ir_http.py +++ b/openerp/addons/base/ir/ir_http.py @@ -137,9 +137,10 @@ class ir_http(osv.AbstractModel): # This is done first as the attachment path may # not match any HTTP controller - attach = self._serve_attachment() - if attach: - return attach + if isinstance(exception, werkzeug.exceptions.HTTPException) and exception.code == 404: + attach = self._serve_attachment() + if attach: + return attach # Don't handle exception but use werkeug debugger if server in --dev mode if openerp.tools.config['dev_mode']: diff --git a/openerp/addons/base/res/ir_property.py b/openerp/addons/base/res/ir_property.py index df3f1d43677..f6597de537c 100644 --- a/openerp/addons/base/res/ir_property.py +++ b/openerp/addons/base/res/ir_property.py @@ -261,6 +261,7 @@ class ir_property(osv.osv): def search_multi(self, name, model, operator, value): """ Return a domain for the records that match the given condition. """ default_matches = False + include_zero = False field = self.env[model]._fields[name] if field.type == 'many2one': @@ -282,6 +283,27 @@ class ir_property(osv.osv): target_names = target.name_search(value, operator=operator, limit=None) target_ids = map(itemgetter(0), target_names) operator, value = 'in', map(makeref, target_ids) + elif field.type in ('integer', 'float'): + # No record is created in ir.property if the field's type is float or integer with a value + # equal to 0. Then to match with the records that are linked to a property field equal to 0, + # the negation of the operator must be taken to compute the goods and the domain returned + # to match the searched records is just the opposite. + if value == 0 and operator == '=': + operator = '!=' + include_zero = True + elif value <= 0 and operator == '>=': + operator = '<' + include_zero = True + elif value <= 0 and operator == '>': + operator = '<=' + include_zero = True + elif value >= 0 and operator == '<=': + operator = '>' + include_zero = True + elif value >= 0 and operator == '<': + operator = '>=' + include_zero = True + # retrieve the properties that match the condition domain = self._get_domain(name, model) @@ -298,7 +320,9 @@ class ir_property(osv.osv): else: default_matches = True - if default_matches: + if include_zero: + return [('id', 'not in', good_ids)] + elif default_matches: # exclude all records with a property that does not match all_ids = [] props = self.search(domain + [('res_id', '!=', False)]) diff --git a/openerp/fields.py b/openerp/fields.py index 7463e41e32d..43d977fc460 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -1054,7 +1054,8 @@ class Float(Field): @property def digits(self): if callable(self._digits): - return self._digits(fields.get_cursor()) + with fields._get_cursor() as cr: + return self._digits(cr) else: return self._digits diff --git a/openerp/http.py b/openerp/http.py index 6bf147e3f95..a315a6faf02 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -203,7 +203,11 @@ class WebRequest(object): def env(self): """ The :class:`~openerp.api.Environment` bound to current request. + Raises a :class:`RuntimeError` if the current requests is not bound + to a database. """ + if not self.db: + return RuntimeError('request not bound to a database') return openerp.api.Environment(self.cr, self.uid, self.context) @lazy_property @@ -238,6 +242,8 @@ class WebRequest(object): """ # can not be a lazy_property because manual rollback in _call_function # if already set (?) + if not self.db: + return RuntimeError('request not bound to a database') if not self._cr: self._cr = self.registry.cursor() return self._cr @@ -924,6 +930,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): def __init__(self, *args, **kwargs): self.inited = False self.modified = False + self.rotate = False super(OpenERPSession, self).__init__(*args, **kwargs) self.inited = True self._default_values() @@ -959,6 +966,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): uid = dispatch_rpc('common', 'authenticate', [db, login, password, env]) else: security.check(db, uid, password) + self.rotate = True self.db = db self.uid = uid self.login = login @@ -984,6 +992,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): if not (keep_db and k == 'db'): del self[k] self._default_values() + self.rotate = True def _default_values(self): self.setdefault("db", None) @@ -1391,6 +1400,10 @@ class Root(object): } if httprequest.session.should_save: + if httprequest.session.rotate: + self.session_store.delete(httprequest.session) + httprequest.session.sid = self.session_store.generate_key() + httprequest.session.modified = True self.session_store.save(httprequest.session) # We must not set the cookie if the session id was specified using a http header or a GET parameter. # There are two reasons to this: diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py index 0dbe417b814..496e7b9dc94 100644 --- a/openerp/osv/expression.py +++ b/openerp/osv/expression.py @@ -401,7 +401,7 @@ def is_leaf(element, internal=False): and len(element) == 3 \ and element[1] in INTERNAL_OPS \ and ((isinstance(element[0], basestring) and element[0]) - or element in (TRUE_LEAF, FALSE_LEAF)) + or tuple(element) in (TRUE_LEAF, FALSE_LEAF)) # -------------------------------------------------- diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 615085093e0..ee3021f76db 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -43,6 +43,7 @@ import pytz import re import xmlrpclib from operator import itemgetter +from contextlib import contextmanager from psycopg2 import Binary import openerp @@ -52,13 +53,22 @@ from openerp.tools import float_repr, float_round, frozendict, html_sanitize import simplejson from openerp import SUPERUSER_ID, registry -def get_cursor(): - # retrieve a valid cursor from any environment +@contextmanager +def _get_cursor(): + # yield a valid cursor from any environment or create a new one if none found from openerp.api import Environment + from openerp.http import request + try: + request.env # force request's env to be computed + except RuntimeError: + pass # ignore if not in a request for env in Environment.envs: if not env.cr.closed: - return env.cr - raise RuntimeError("No valid cursor found") + yield env.cr + break + else: + with registry().cursor() as cr: + yield cr EMPTY_DICT = frozendict() @@ -396,7 +406,8 @@ class float(_column): @property def digits(self): if self._digits_compute: - return self._digits_compute(get_cursor()) + with _get_cursor() as cr: + return self._digits_compute(cr) else: return self._digits @@ -1316,7 +1327,8 @@ class function(_column): @property def digits(self): if self._digits_compute: - return self._digits_compute(get_cursor()) + with _get_cursor() as cr: + return self._digits_compute(cr) else: return self._digits