diff --git a/addons/delivery/stock.py b/addons/delivery/stock.py index adae3e63c50..d8aeaf617eb 100644 --- a/addons/delivery/stock.py +++ b/addons/delivery/stock.py @@ -140,6 +140,32 @@ class stock_picking(osv.osv): 'weight_uom_id': lambda self,cr,uid,c: self._get_default_uom(cr,uid,c) } + def copy(self, cr, uid, id, default=None, context=None): + default = dict(default or {}, + number_of_packages=0, + carrier_tracking_ref=False, + volume=0.0) + return super(stock_picking, self).copy(cr, uid, id, default=default, context=context) + + def do_partial(self, cr, uid, ids, partial_datas, context=None): + res = super(stock_picking, self).do_partial(cr, uid, ids, partial_datas, context=context) + for backorder_id, picking_vals in res.iteritems(): + if backorder_id != picking_vals.get('delivered_picking'): + # delivery info is set on backorder but not on new picking + backorder = self.browse(cr, uid, backorder_id, context=context) + self.write(cr, uid, picking_vals['delivered_picking'], { + 'carrier_tracking_ref': backorder.carrier_tracking_ref, + 'number_of_packages': backorder.number_of_packages, + 'volume': backorder.volume, + }, context=context) + # delivery info are not relevant to backorder + self.write(cr, uid, backorder_id, { + 'carrier_tracking_ref': False, + 'number_of_packages': 0, + 'volume': 0, + }, context=context) + + return res class stock_move(osv.osv): _inherit = 'stock.move' diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 7733239ae08..79a07f6c370 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -1363,18 +1363,34 @@ class mail_thread(osv.AbstractModel): if follower.email == email_address: partner_id = follower.id # second try: check in partners that are also users + # Escape special SQL characters in email_address to avoid invalid matches + email_address = (email_address.replace('\\', '\\\\').replace('%', '\\%').replace('_', '\\_')) + email_brackets = "<%s>" % email_address if not partner_id: - ids = partner_obj.search(cr, SUPERUSER_ID, [ - ('email', 'ilike', email_address), - ('user_ids', '!=', False) - ], limit=1, context=context) + # exact, case-insensitive match + ids = partner_obj.search(cr, SUPERUSER_ID, + [('email', '=ilike', email_address), + ('user_ids', '!=', False)], + limit=1, context=context) + if not ids: + # if no match with addr-spec, attempt substring match within name-addr pair + ids = partner_obj.search(cr, SUPERUSER_ID, + [('email', 'ilike', email_brackets), + ('user_ids', '!=', False)], + limit=1, context=context) if ids: partner_id = ids[0] # third try: check in partners if not partner_id: - ids = partner_obj.search(cr, SUPERUSER_ID, [ - ('email', 'ilike', email_address) - ], limit=1, context=context) + # exact, case-insensitive match + ids = partner_obj.search(cr, SUPERUSER_ID, + [('email', '=ilike', email_address)], + limit=1, context=context) + if not ids: + # if no match with addr-spec, attempt substring match within name-addr pair + ids = partner_obj.search(cr, SUPERUSER_ID, + [('email', 'ilike', email_brackets)], + limit=1, context=context) if ids: partner_id = ids[0] partner_ids.append(partner_id) @@ -1394,13 +1410,15 @@ class mail_thread(osv.AbstractModel): partner_id = partner_ids[idx] partner_info = {'full_name': email_address, 'partner_id': partner_id} result.append(partner_info) - # link mail with this from mail to the new partner id if link_mail and partner_info['partner_id']: + # Escape special SQL characters in email_address to avoid invalid matches + email_address = (email_address.replace('\\', '\\\\').replace('%', '\\%').replace('_', '\\_')) + email_brackets = "<%s>" % email_address message_ids = mail_message_obj.search(cr, SUPERUSER_ID, [ '|', - ('email_from', '=', email_address), - ('email_from', 'ilike', '<%s>' % email_address), + ('email_from', '=ilike', email_address), + ('email_from', 'ilike', email_brackets), ('author_id', '=', False) ], context=context) if message_ids: diff --git a/addons/mail/tests/test_mail_gateway.py b/addons/mail/tests/test_mail_gateway.py index 023a009a9ee..f4bfa61ae13 100644 --- a/addons/mail/tests/test_mail_gateway.py +++ b/addons/mail/tests/test_mail_gateway.py @@ -68,7 +68,7 @@ MAIL_TEMPLATE_PLAINTEXT = """Return-Path: +From: Sylvie Lelitre Subject: {subject} MIME-Version: 1.0 Content-Type: text/plain @@ -523,6 +523,16 @@ class TestMailgateway(TestMail): self.assertEqual(frog_group.message_ids[0].author_id.id, extra_partner_id, 'message_process: email_from -> author_id wrong') + # Do: post a new message with a non-existant email that is a substring of a partner email + format_and_process(MAIL_TEMPLATE, email_from='Not really Lombrik Lubrik ', + subject='Re: news (2)', + msg_id='', + extra='In-Reply-To: <1198923581.41972151344608186760.JavaMail@agrolait.com>\n') + frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')]) + frog_group = self.mail_group.browse(cr, uid, frog_groups[0]) + # Test: author must not be set, otherwise the system is confusing different users + self.assertFalse(frog_group.message_ids[0].author_id, 'message_process: email_from -> mismatching author_id') + # Do: post a new message, with a known partner -> duplicate emails -> user frog_group.message_unsubscribe([extra_partner_id]) self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'}) diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index c5ede21144e..8595008aab0 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -24,7 +24,7 @@ from datetime import datetime import openerp.addons.decimal_precision as dp from openerp.osv import fields, osv, orm -from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT from openerp.tools import float_compare from openerp.tools.translate import _ from openerp import tools, SUPERUSER_ID @@ -294,8 +294,8 @@ class mrp_bom(osv.osv): if properties is None: properties = [] domain = [('product_id', '=', product_id), ('bom_id', '=', False), - '|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)), - '|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))] + '|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATE_FORMAT)), + '|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATE_FORMAT))] ids = self.search(cr, uid, domain) max_prop = 0 result = False @@ -363,6 +363,9 @@ class mrp_bom(osv.osv): 'hour': float(wc_use.hour_nbr*mult + ((wc.time_start or 0.0)+(wc.time_stop or 0.0)+cycle*(wc.time_cycle or 0.0)) * (wc.time_efficiency or 1.0)), }) for bom2 in bom.bom_lines: + if (bom2.date_start and bom2.date_start > time.strftime(DEFAULT_SERVER_DATE_FORMAT)) or \ + (bom2.date_stop and bom2.date_stop < time.strftime(DEFAULT_SERVER_DATE_FORMAT)): + continue res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level+10) result = result + res[0] result2 = result2 + res[1] @@ -613,6 +616,26 @@ class mrp_production(osv.osv): self.write(cr, uid, ids, {'state': 'picking_except'}) return True + def _prepare_lines(self, cr, uid, production, properties=None, context=None): + # search BoM structure and route + bom_obj = self.pool.get('mrp.bom') + uom_obj = self.pool.get('product.uom') + bom_point = production.bom_id + bom_id = production.bom_id.id + if not bom_point: + bom_id = bom_obj._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties) + if bom_id: + bom_point = bom_obj.browse(cr, uid, bom_id) + routing_id = bom_point.routing_id.id or False + self.write(cr, uid, [production.id], {'bom_id': bom_id, 'routing_id': routing_id}) + + if not bom_id: + raise osv.except_osv(_('Error!'), _("Cannot find a bill of material for this product.")) + + # get components and workcenter_lines from BoM structure + factor = uom_obj._compute_qty(cr, uid, production.product_uom.id, production.product_qty, bom_point.product_uom.id) + return bom_obj._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties, routing_id=production.routing_id.id) + def _action_compute_lines(self, cr, uid, ids, properties=None, context=None): """ Compute product_lines and workcenter_lines from BoM structure @return: product_lines @@ -621,8 +644,6 @@ class mrp_production(osv.osv): if properties is None: properties = [] results = [] - bom_obj = self.pool.get('mrp.bom') - uom_obj = self.pool.get('product.uom') prod_line_obj = self.pool.get('mrp.production.product.line') workcenter_line_obj = self.pool.get('mrp.production.workcenter.line') @@ -632,23 +653,8 @@ class mrp_production(osv.osv): #unlink workcenter_lines workcenter_line_obj.unlink(cr, SUPERUSER_ID, [line.id for line in production.workcenter_lines], context=context) - - # search BoM structure and route - bom_point = production.bom_id - bom_id = production.bom_id.id - if not bom_point: - bom_id = bom_obj._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties) - if bom_id: - bom_point = bom_obj.browse(cr, uid, bom_id) - routing_id = bom_point.routing_id.id or False - self.write(cr, uid, [production.id], {'bom_id': bom_id, 'routing_id': routing_id}) - - if not bom_id: - raise osv.except_osv(_('Error!'), _("Cannot find a bill of material for this product.")) - - # get components and workcenter_lines from BoM structure - factor = uom_obj._compute_qty(cr, uid, production.product_uom.id, production.product_qty, bom_point.product_uom.id) - res = bom_obj._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties, routing_id=production.routing_id.id) + + res = self._prepare_lines(cr, uid, production, properties=properties, context=context) results = res[0] # product_lines results2 = res[1] # workcenter_lines @@ -895,7 +901,7 @@ class mrp_production(osv.osv): """ res = True for production in self.browse(cr, uid, ids): - boms = self._action_compute_lines(cr, uid, [production.id]) + boms = self._prepare_lines(cr, uid, production)[0] res = False for bom in boms: product = self.pool.get('product.product').browse(cr, uid, bom['product_id']) diff --git a/addons/plugin/plugin_handler.py b/addons/plugin/plugin_handler.py index 932ea4ccbb6..8243a5b72e8 100644 --- a/addons/plugin/plugin_handler.py +++ b/addons/plugin/plugin_handler.py @@ -112,7 +112,7 @@ class plugin_handler(osv.osv_memory): if not email_from: author_id = False else: - authors = mail_thread_obj.message_find_partner_from_emails(cr, uid, [res_id], [email_from]) + authors = mail_thread_obj.message_partner_info_from_emails(cr, uid, [res_id], [email_from]) author_id = authors and authors[0].get('partner_id') or False model_obj.message_post(cr, uid, [res_id], diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 3fffad332cd..e8ab7bf41b7 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -338,7 +338,7 @@ class pos_session(osv.osv): if not pos_config.journal_id: jid = jobj.default_get(cr, uid, ['journal_id'], context=context)['journal_id'] if jid: - jobj.write(cr, uid, [pos_config.id], {'journal_id': jid}, context=context) + jobj.write(cr, openerp.SUPERUSER_ID, [pos_config.id], {'journal_id': jid}, context=context) else: raise osv.except_osv( _('error!'), _("Unable to open the session. You have to assign a sale journal to your point of sale.")) @@ -352,8 +352,8 @@ class pos_session(osv.osv): if not cashids: cashids = journal_proxy.search(cr, uid, [('journal_user','=',True)], context=context) - journal_proxy.write(cr, uid, cashids, {'journal_user': True}) - jobj.write(cr, uid, [pos_config.id], {'journal_ids': [(6,0, cashids)]}) + journal_proxy.write(cr, openerp.SUPERUSER_ID, cashids, {'journal_user': True}) + jobj.write(cr, openerp.SUPERUSER_ID, [pos_config.id], {'journal_ids': [(6,0, cashids)]}) pos_config = jobj.browse(cr, uid, config_id, context=context) diff --git a/addons/point_of_sale/security/ir.model.access.csv b/addons/point_of_sale/security/ir.model.access.csv index 1e91406fcb1..acbe5671efd 100644 --- a/addons/point_of_sale/security/ir.model.access.csv +++ b/addons/point_of_sale/security/ir.model.access.csv @@ -39,7 +39,7 @@ access_product_template_pos_manager,product.template pos manager,product.model_p access_account_move_line,account.move.line,account.model_account_move_line,group_pos_user,1,1,1,0 access_account_move_line_manager,account.move.line manager,account.model_account_move_line,group_pos_manager,1,1,1,1 access_account_move,account.move,account.model_account_move,group_pos_manager,1,0,0,0 -access_account_journal,account.journal,account.model_account_journal,group_pos_user,1,1,1,0 +access_account_journal,account.journal,account.model_account_journal,group_pos_manager,1,1,1,0 access_account_journal_period_user,account.journal.period user,account.model_account_journal_period,group_pos_user,1,1,1,1 access_account_journal_period_manager,account.journal.period manager,account.model_account_journal_period,group_pos_manager,1,0,0,0 access_account_analytic_line,account.analytic.line,analytic.model_account_analytic_line,group_pos_user,1,1,1,0 @@ -57,7 +57,8 @@ access_res_partner_manager,res.partner manager,base.model_res_partner,group_pos_ access_product_category_manager,product.category manager,product.model_product_category,group_pos_manager,1,1,1,1 access_product_pricelist_manager,product.pricelist manager,product.model_product_pricelist,group_pos_manager,1,0,0,0 access_pos_session_user,pos.session user,model_pos_session,group_pos_user,1,1,1,0 -access_pos_config_user,pos.config user,model_pos_config,group_pos_user,1,1,1,0 +access_pos_config_user,pos.config user,model_pos_config,group_pos_user,1,0,0,0 +access_pos_config_manager,pos.config user,model_pos_config,group_pos_manager,1,1,1,0 access_ir_sequence_manager,ir.sequence manager,base.model_ir_sequence,group_pos_manager,1,1,1,1 access_product_category_pos_manager,product.public.category manager,product.model_product_public_category,group_pos_manager,1,1,1,1 access_product_category_pos_user,product.public.category user,product.model_product_public_category,group_pos_user,1,0,0,0 diff --git a/addons/purchase_requisition/purchase_requisition.py b/addons/purchase_requisition/purchase_requisition.py index 592058b30d3..a9cf85c872e 100644 --- a/addons/purchase_requisition/purchase_requisition.py +++ b/addons/purchase_requisition/purchase_requisition.py @@ -175,7 +175,7 @@ class purchase_requisition_line(osv.osv): _rec_name = 'product_id' _columns = { - 'product_id': fields.many2one('product.product', 'Product' ), + 'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok', '=', True)]), 'product_uom_id': fields.many2one('product.uom', 'Product Unit of Measure'), 'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure')), 'requisition_id' : fields.many2one('purchase.requisition','Purchase Requisition', ondelete='cascade'), diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 379d252854b..b68c009a05c 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -3045,6 +3045,10 @@ class stock_picking_in(osv.osv): defaults.update(in_defaults) return defaults + def copy(self, cr, uid, id, default=None, context=None): + return self.pool['stock.picking'].copy(cr, uid, id, default=default, context=context) + + _columns = { 'backorder_id': fields.many2one('stock.picking.in', '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), 'state': fields.selection( @@ -3128,6 +3132,10 @@ class stock_picking_out(osv.osv): defaults.update(out_defaults) return defaults + def copy(self, cr, uid, id, default=None, context=None): + return self.pool['stock.picking'].copy(cr, uid, id, default=default, context=context) + + _columns = { 'backorder_id': fields.many2one('stock.picking.out', '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), 'state': fields.selection( diff --git a/addons/warning/warning.py b/addons/warning/warning.py index 539316482d2..0cffc4dd34b 100644 --- a/addons/warning/warning.py +++ b/addons/warning/warning.py @@ -77,7 +77,9 @@ class sale_order(osv.osv): warning['title'] = title and title +' & '+ result['warning']['title'] or result['warning']['title'] warning['message'] = message and message + ' ' + result['warning']['message'] or result['warning']['message'] - return {'value': result.get('value',{}), 'warning':warning} + if warning: + result['warning'] = warning + return result class purchase_order(osv.osv): @@ -105,7 +107,9 @@ class purchase_order(osv.osv): warning['title'] = title and title +' & '+ result['warning']['title'] or result['warning']['title'] warning['message'] = message and message + ' ' + result['warning']['message'] or result['warning']['message'] - return {'value': result.get('value',{}), 'warning':warning} + if warning: + result['warning'] = warning + return result @@ -142,7 +146,9 @@ class account_invoice(osv.osv): warning['title'] = title and title +' & '+ result['warning']['title'] or result['warning']['title'] warning['message'] = message and message + ' ' + result['warning']['message'] or result['warning']['message'] - return {'value': result.get('value',{}), 'warning':warning} + if warning: + result['warning'] = warning + return result class stock_picking(osv.osv): @@ -170,7 +176,9 @@ class stock_picking(osv.osv): warning['title'] = title and title +' & '+ result['warning']['title'] or result['warning']['title'] warning['message'] = message and message + ' ' + result['warning']['message'] or result['warning']['message'] - return {'value': result.get('value',{}), 'warning':warning} + if warning: + result['warning'] = warning + return result # FIXME:(class stock_picking_in and stock_picking_out) this is a temporary workaround because of a framework bug (ref: lp:996816). @@ -200,7 +208,9 @@ class stock_picking_in(osv.osv): warning['title'] = title and title +' & '+ result['warning']['title'] or result['warning']['title'] warning['message'] = message and message + ' ' + result['warning']['message'] or result['warning']['message'] - return {'value': result.get('value',{}), 'warning':warning} + if warning: + result['warning'] = warning + return result class stock_picking_out(osv.osv): _inherit = 'stock.picking.out' @@ -227,7 +237,9 @@ class stock_picking_out(osv.osv): warning['title'] = title and title +' & '+ result['warning']['title'] or result['warning']['title'] warning['message'] = message and message + ' ' + result['warning']['message'] or result['warning']['message'] - return {'value': result.get('value',{}), 'warning':warning} + if warning: + result['warning'] = warning + return result class product_product(osv.osv): _inherit = 'product.product' @@ -276,7 +288,9 @@ class sale_order_line(osv.osv): warning['title'] = title and title +' & '+result['warning']['title'] or result['warning']['title'] warning['message'] = message and message +'\n\n'+result['warning']['message'] or result['warning']['message'] - return {'value': result.get('value',{}), 'warning':warning} + if warning: + result['warning'] = warning + return result class purchase_order_line(osv.osv): @@ -307,7 +321,9 @@ class purchase_order_line(osv.osv): warning['title'] = title and title +' & '+result['warning']['title'] or result['warning']['title'] warning['message'] = message and message +'\n\n'+result['warning']['message'] or result['warning']['message'] - return {'value': result.get('value',{}), 'warning':warning} + if warning: + result['warning'] = warning + return result diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 9a10841cfd0..f5db72de3fa 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -3243,7 +3243,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc reinit_value: function(val) { this.internal_set_value(val); this.floating = false; - if (this.is_started) + if (this.is_started && !this.no_rerender) this.render_value(); }, initialize_field: function() { @@ -3480,7 +3480,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc } if (! no_recurse) { var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context()); - this.alive(dataset.name_get([self.get("value")])).done(function(data) { + var def = this.alive(dataset.name_get([self.get("value")])).done(function(data) { if (!data[0]) { self.do_warn(_t("Render"), _t("No value found for the field "+self.field.string+" for value "+self.get("value"))); return; @@ -3493,6 +3493,9 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc self.display_value["" + self.get("value")] = self.display_value_backup["" + self.get("value")]; self.render_value(true); }); + if (this.view && this.view.render_value_defs){ + this.view.render_value_defs.push(def); + } } }, display_string: function(str) { @@ -3966,7 +3969,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ this.dataset.index = 0; } this.trigger_on_change(); - if (this.is_started) { + if (this.is_started && !this.no_rerender) { return self.reload_current_view(); } else { return $.when(); @@ -4112,12 +4115,19 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ return true; } var r; - return _.every(this.records.records, function(record){ + if (_.isEmpty(this.records.records)){ + return true; + } + current_values = {}; + _.each(this.editor.form.fields, function(field){ + field._inhibit_on_change_flag = true; + field.no_rerender = true; + current_values[field.name] = field.get('value'); + }); + var valid = _.every(this.records.records, function(record){ r = record; _.each(self.editor.form.fields, function(field){ - field._inhibit_on_change_flag = true; - field.internal_set_value(r.attributes[field.name]); - field._inhibit_on_change_flag = false; + field.set_value(r.attributes[field.name]); }); return _.every(self.editor.form.fields, function(field){ field.process_modifiers(); @@ -4125,6 +4135,12 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ return field.is_valid(); }); }); + _.each(this.editor.form.fields, function(field){ + field.set('value', current_values[field.name]); + field._inhibit_on_change_flag = false; + field.no_rerender = false; + }); + return valid; }, do_add_record: function () { if (this.editable()) { diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 4bff19baf30..159286d631a 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -231,8 +231,9 @@ this.records.add(record, { at: this.prepends_on_create() ? 0 : null}); } - - return this.ensure_saved().then(function () { + return this.ensure_saved().then(function(){ + return $.when.apply(null, self.editor.form.render_value_defs); + }).then(function () { var $recordRow = self.groups.get_row_for(record); var cells = self.get_cells_for($recordRow); var fields = {}; diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 0a9140ef8f0..6ce96dd0838 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -5242,10 +5242,10 @@ class ImportWarning(Warning): def convert_pgerror_23502(model, fields, info, e): m = re.match(r'^null value in column "(?P\w+)" violates ' r'not-null constraint\n', - str(e)) + tools.ustr(e)) field_name = m.group('field') if not m or field_name not in fields: - return {'message': unicode(e)} + return {'message': tools.ustr(e)} message = _(u"Missing required value for the field '%s'.") % field_name field = fields.get(field_name) if field: @@ -5257,10 +5257,10 @@ def convert_pgerror_23502(model, fields, info, e): def convert_pgerror_23505(model, fields, info, e): m = re.match(r'^duplicate key (?P\w+) violates unique constraint', - str(e)) + tools.ustr(e)) field_name = m.group('field') if not m or field_name not in fields: - return {'message': unicode(e)} + return {'message': tools.ustr(e)} message = _(u"The value for the field '%s' already exists.") % field_name field = fields.get(field_name) if field: @@ -5273,7 +5273,7 @@ def convert_pgerror_23505(model, fields, info, e): PGERROR_TO_OE = collections.defaultdict( # shape of mapped converters - lambda: (lambda model, fvg, info, pgerror: {'message': unicode(pgerror)}), { + lambda: (lambda model, fvg, info, pgerror: {'message': tools.ustr(pgerror)}), { # not_null_violation '23502': convert_pgerror_23502, # unique constraint error