The import logging (ish) assumes that if an exception has at least 2 args the second arg is metadata added by the callee. As it turns out, `UnicodeEncodeError` has *five* arguments, none of which is added by us. So if encoding something fails during the process (e.g. because the file contains a lone surrogate, which leads to the database insert failing when psycopg2 tries to encode the query to UTF8), then the `_log` function itself will fail, yielding a very unhelpful error of: dictionary update sequence element #0 has length 1; 2 is required (because we tried to update a dict using a string). This issue occurs only during *field conversion* and most fields have no need to interact with the database (so don't need to encode the value, which is what fails), however it is a problem when the invalid string is used as a record name to look for (e.g. an m2o). Further improve the experience by converting the UnicodeEncodeError to a ValueError using the stringified UEE: `_log` assumes the first argument to the exception is an error message of some sort, but for UnicodeError subclasses it's just the encoding involved in the error (here `utf-8`), which doesn't really serve as an error message. Stringifying the exception generates a complete error message which is quite a bit more helpful. Specific update notes: * avoid modifying the exception in-place, doesn't seem useful * not sure why `field_name` was added as part of the augmentation rather than up-front when `record` is created Issue 2480064 closes odoo/odoo#72517 X-original-commit: 6c3c500929cd463cd3a1749f4ede8f3a8afd5748 Signed-off-by: Xavier Morel (xmo) <xmo@odoo.com>
187 lines
6.0 KiB
Python
187 lines
6.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import api, fields, models
|
|
|
|
|
|
def selection_fn(model):
|
|
return [(str(key), val) for key, val in enumerate(["Corge", "Grault", "Wheee", "Moog"])]
|
|
|
|
def compute_fn(records):
|
|
for record in records:
|
|
record.value = 3
|
|
|
|
def inverse_fn(records):
|
|
pass
|
|
|
|
MODELS = [
|
|
('boolean', fields.Boolean()),
|
|
('integer', fields.Integer()),
|
|
('float', fields.Float()),
|
|
('decimal', fields.Float(digits=(16, 3))),
|
|
('string.bounded', fields.Char(size=16)),
|
|
('string.required', fields.Char(size=None, required=True)),
|
|
('string', fields.Char(size=None)),
|
|
('date', fields.Date()),
|
|
('datetime', fields.Datetime()),
|
|
('text', fields.Text()),
|
|
('selection', fields.Selection([('1', "Foo"), ('2', "Bar"), ('3', "Qux"), ('4', '')])),
|
|
('selection.function', fields.Selection(selection_fn)),
|
|
# just relate to an integer
|
|
('many2one', fields.Many2one('export.integer')),
|
|
('one2many', fields.One2many('export.one2many.child', 'parent_id')),
|
|
('many2many', fields.Many2many('export.many2many.other')),
|
|
('function', fields.Integer(compute=compute_fn, inverse=inverse_fn)),
|
|
# related: specialization of fields.function, should work the same way
|
|
# TODO: reference
|
|
]
|
|
|
|
for name, field in MODELS:
|
|
class NewModel(models.Model):
|
|
_name = 'export.%s' % name
|
|
_description = 'Export: %s' % name
|
|
_rec_name = 'value'
|
|
const = fields.Integer(default=4)
|
|
value = field
|
|
|
|
def name_get(self):
|
|
return [(record.id, "%s:%s" % (self._name, record.value)) for record in self]
|
|
|
|
@api.model
|
|
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
|
|
if isinstance(name, str) and name.split(':')[0] == self._name:
|
|
return self._search([('value', operator, int(name.split(':')[1]))], access_rights_uid=name_get_uid)
|
|
else:
|
|
return []
|
|
|
|
class One2ManyChild(models.Model):
|
|
_name = 'export.one2many.child'
|
|
_description = 'Export One to Many Child'
|
|
# FIXME: orm.py:1161, fix to name_get on m2o field
|
|
_rec_name = 'value'
|
|
|
|
parent_id = fields.Many2one('export.one2many')
|
|
str = fields.Char()
|
|
m2o = fields.Many2one('export.integer')
|
|
value = fields.Integer()
|
|
|
|
def name_get(self):
|
|
return [(record.id, "%s:%s" % (self._name, record.value)) for record in self]
|
|
|
|
@api.model
|
|
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
|
|
if isinstance(name, str) and name.split(':')[0] == self._name:
|
|
return self._search([('value', operator, int(name.split(':')[1]))], access_rights_uid=name_get_uid)
|
|
else:
|
|
return []
|
|
|
|
|
|
class One2ManyMultiple(models.Model):
|
|
_name = 'export.one2many.multiple'
|
|
_description = 'Export One To Many Multiple'
|
|
_rec_name = 'parent_id'
|
|
|
|
parent_id = fields.Many2one('export.one2many.recursive')
|
|
const = fields.Integer(default=36)
|
|
child1 = fields.One2many('export.one2many.child.1', 'parent_id')
|
|
child2 = fields.One2many('export.one2many.child.2', 'parent_id')
|
|
|
|
|
|
class One2ManyChildMultiple(models.Model):
|
|
_name = 'export.one2many.multiple.child'
|
|
# FIXME: orm.py:1161, fix to name_get on m2o field
|
|
_rec_name = 'value'
|
|
_description = 'Export One To Many Multiple Child'
|
|
|
|
parent_id = fields.Many2one('export.one2many.multiple')
|
|
str = fields.Char()
|
|
value = fields.Integer()
|
|
|
|
def name_get(self):
|
|
return [(record.id, "%s:%s" % (self._name, record.value)) for record in self]
|
|
|
|
|
|
class One2ManyChild1(models.Model):
|
|
_name = 'export.one2many.child.1'
|
|
_inherit = 'export.one2many.multiple.child'
|
|
_description = 'Export One to Many Child 1'
|
|
|
|
|
|
class One2ManyChild2(models.Model):
|
|
_name = 'export.one2many.child.2'
|
|
_inherit = 'export.one2many.multiple.child'
|
|
_description = 'Export One To Many Child 2'
|
|
|
|
|
|
class Many2ManyChild(models.Model):
|
|
_name = 'export.many2many.other'
|
|
_description = 'Export Many to Many Other'
|
|
# FIXME: orm.py:1161, fix to name_get on m2o field
|
|
_rec_name = 'value'
|
|
|
|
str = fields.Char()
|
|
value = fields.Integer()
|
|
|
|
def name_get(self):
|
|
return [(record.id, "%s:%s" % (self._name, record.value)) for record in self]
|
|
|
|
@api.model
|
|
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
|
|
if isinstance(name, str) and name.split(':')[0] == self._name:
|
|
return self._search([('value', operator, int(name.split(':')[1]))], access_rights_uid=name_get_uid)
|
|
else:
|
|
return []
|
|
|
|
|
|
class SelectionWithDefault(models.Model):
|
|
_name = 'export.selection.withdefault'
|
|
_description = 'Export Selection With Default'
|
|
|
|
const = fields.Integer(default=4)
|
|
value = fields.Selection([('1', "Foo"), ('2', "Bar")], default='2')
|
|
|
|
|
|
class RecO2M(models.Model):
|
|
_name = 'export.one2many.recursive'
|
|
_description = 'Export One To Many Recursive'
|
|
_rec_name = 'value'
|
|
|
|
value = fields.Integer()
|
|
child = fields.One2many('export.one2many.multiple', 'parent_id')
|
|
|
|
|
|
class OnlyOne(models.Model):
|
|
_name = 'export.unique'
|
|
_description = 'Export Unique'
|
|
|
|
value = fields.Integer()
|
|
value2 = fields.Integer()
|
|
value3 = fields.Integer()
|
|
|
|
_sql_constraints = [
|
|
('value_unique', 'unique (value)', "The value must be unique"),
|
|
('pair_unique', 'unique (value2, value3)', "The values must be unique"),
|
|
]
|
|
|
|
class InheritsParent(models.Model):
|
|
_name = _description = 'export.inherits.parent'
|
|
|
|
value_parent = fields.Integer()
|
|
|
|
class InheritsChild(models.Model):
|
|
_name = _description = 'export.inherits.child'
|
|
_inherits = {'export.inherits.parent': 'parent_id'}
|
|
|
|
parent_id = fields.Many2one('export.inherits.parent', required=True, ondelete='cascade')
|
|
value = fields.Integer()
|
|
|
|
class Many2String(models.Model):
|
|
_name = _description = 'export.m2o.str'
|
|
|
|
child_id = fields.Many2one('export.m2o.str.child')
|
|
|
|
class ChidToString(models.Model):
|
|
_name = _description = 'export.m2o.str.child'
|
|
|
|
name = fields.Char()
|