Include row and column in error message when importing data
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
* Include row and column in error message when importing data
|
||||
* Remove support for ``:id`` suffix in import data
|
||||
* Add export translations option to trytond-admin
|
||||
* Update TinyMCE to version 7
|
||||
|
||||
@@ -152,22 +152,22 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="text">This record is part of the base configuration.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_import_data_missing_column">
|
||||
<field name="text">Missing values for last column.</field>
|
||||
<field name="text">Missing values for last column at row %(row)d.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_import_data_missing_columns">
|
||||
<field name="text">Missing values for %(n)s last columns.</field>
|
||||
<field name="text">Missing values for %(n)s last columns at row %(row)d.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_relation_not_found">
|
||||
<field name="text">Relation not found: %(value)r in "%(model)s" (%(column)s).</field>
|
||||
<field name="text">Could not find relation for %(value)r in "%(model)s" at row %(row)d and column %(column)d.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_too_many_relations_found">
|
||||
<field name="text">Too many relations found: %(value)r in "%(model)s" (%(column)s).</field>
|
||||
<field name="text">Found too many relation for %(value)r in "%(model)s" at row %(row)d and column %(column)d.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_value_syntax_error">
|
||||
<field name="text">Syntax error for value: %(value)r in "%(field)s" of "%(model)s" (%(column)s).</field>
|
||||
<field name="text">Syntax error for %(value)r in "%(field)s" of "%(model)s" at row %(row)d and column %(column)d.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_reference_syntax_error">
|
||||
<field name="text">Syntax error for reference: %(value)r in "%(field)s" of "%(model)s" (%(column)s).</field>
|
||||
<field name="text">Syntax error for %(value)r in "%(field)s" of "%(model)s" at row %(row)d and column %(column)d.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_domain_validation_record">
|
||||
<field name="text">The value "%(value)s" for field "%(field)s" in record "%(record)s" of "%(model)s" is not valid according to its domain.</field>
|
||||
|
||||
@@ -838,8 +838,27 @@ class ModelStorage(Model):
|
||||
'''
|
||||
pool = Pool()
|
||||
|
||||
row_index = data.index
|
||||
|
||||
class _Error(Exception):
|
||||
def __init__(self, msg_id, **variables):
|
||||
self.msg_id = msg_id
|
||||
self.variables = variables
|
||||
|
||||
def error(func):
|
||||
def wrapper(line, index, *args, **kwargs):
|
||||
try:
|
||||
return func(line[index], *args, **kwargs)
|
||||
except _Error as e:
|
||||
raise ImportDataError(gettext(
|
||||
e.msg_id, **e.variables,
|
||||
row=row_index(line) + 1,
|
||||
column=index + 1)) from e
|
||||
return wrapper
|
||||
|
||||
@error
|
||||
@lru_cache(maxsize=1000)
|
||||
def get_many2one(relation, value, column):
|
||||
def get_many2one(value, relation):
|
||||
if not value:
|
||||
return None
|
||||
Relation = pool.get(relation)
|
||||
@@ -847,23 +866,22 @@ class ModelStorage(Model):
|
||||
('rec_name', '=', value),
|
||||
], limit=2)
|
||||
if len(res) < 1:
|
||||
raise ImportDataError(gettext(
|
||||
'ir.msg_relation_not_found',
|
||||
value=value,
|
||||
column=column,
|
||||
**Relation.__names__()))
|
||||
raise _Error(
|
||||
'ir.msg_relation_not_found',
|
||||
value=value,
|
||||
**Relation.__names__())
|
||||
elif len(res) > 1:
|
||||
raise ImportDataError(
|
||||
gettext('ir.msg_too_many_relations_found',
|
||||
value=value,
|
||||
column=column,
|
||||
**Relation.__names__()))
|
||||
raise _Error(
|
||||
'ir.msg_too_many_relations_found',
|
||||
value=value,
|
||||
**Relation.__names__())
|
||||
else:
|
||||
res = res[0].id
|
||||
return res
|
||||
|
||||
@error
|
||||
@lru_cache(maxsize=1000)
|
||||
def get_many2many(relation, value, column):
|
||||
def get_many2many(value, relation):
|
||||
if not value:
|
||||
return None
|
||||
res = []
|
||||
@@ -874,52 +892,48 @@ class ModelStorage(Model):
|
||||
('rec_name', '=', word),
|
||||
], limit=2)
|
||||
if len(res2) < 1:
|
||||
raise ImportDataError(
|
||||
gettext('ir.msg_relation_not_found',
|
||||
value=word,
|
||||
column=column,
|
||||
**Relation.__names__()))
|
||||
raise _Error(
|
||||
'ir.msg_relation_not_found',
|
||||
value=word,
|
||||
**Relation.__names__())
|
||||
elif len(res2) > 1:
|
||||
raise ImportDataError(
|
||||
gettext('ir.msg_too_many_relations_found',
|
||||
value=word,
|
||||
column=column,
|
||||
**Relation.__names__()))
|
||||
raise _Error(
|
||||
'ir.msg_too_many_relations_found',
|
||||
value=word,
|
||||
**Relation.__names__())
|
||||
else:
|
||||
res.append(res2[0].id)
|
||||
return res
|
||||
|
||||
def get_one2one(relation, value, column):
|
||||
return ('add', get_many2one(relation, value, column))
|
||||
def get_one2one(*args, **kwargs):
|
||||
return ('add', get_many2one(*args, **kwargs))
|
||||
|
||||
@error
|
||||
@lru_cache(maxsize=1000)
|
||||
def get_reference(value, field, klass, column):
|
||||
def get_reference(value, field, klass):
|
||||
if not value:
|
||||
return None
|
||||
try:
|
||||
relation, value = value.split(',', 1)
|
||||
Relation = pool.get(relation)
|
||||
except (ValueError, KeyError) as e:
|
||||
raise ImportDataError(
|
||||
gettext('ir.msg_reference_syntax_error',
|
||||
value=value,
|
||||
column=column,
|
||||
**klass.__names__(field))) from e
|
||||
raise _Error(
|
||||
'ir.msg_reference_syntax_error',
|
||||
value=value,
|
||||
**klass.__names__(field)) from e
|
||||
res = Relation.search([
|
||||
('rec_name', '=', value),
|
||||
], limit=2)
|
||||
if len(res) < 1:
|
||||
raise ImportDataError(gettext(
|
||||
'ir.msg_relation_not_found',
|
||||
value=value,
|
||||
column=column,
|
||||
**Relation.__names__()))
|
||||
raise _Error(
|
||||
'ir.msg_relation_not_found',
|
||||
value=value,
|
||||
**Relation.__names__())
|
||||
elif len(res) > 1:
|
||||
raise ImportDataError(
|
||||
gettext('ir.msg_too_many_relations_found',
|
||||
value=value,
|
||||
column=column,
|
||||
**Relation.__names__()))
|
||||
raise _Error(
|
||||
'ir.msg_too_many_relations_found',
|
||||
value=value,
|
||||
**Relation.__names__())
|
||||
else:
|
||||
res = '%s,%s' % (relation, res[0].id)
|
||||
return res
|
||||
@@ -933,7 +947,8 @@ class ModelStorage(Model):
|
||||
create.append(row)
|
||||
return id_
|
||||
|
||||
def convert(value, ftype, field, klass, column):
|
||||
@error
|
||||
def convert(value, ftype, field, klass):
|
||||
def convert_boolean(value):
|
||||
if value.lower() == 'true':
|
||||
return True
|
||||
@@ -995,15 +1010,16 @@ class ModelStorage(Model):
|
||||
return value
|
||||
|
||||
try:
|
||||
return locals()['convert_%s' % ftype](value)
|
||||
func = locals()['convert_%s' % ftype]
|
||||
except KeyError:
|
||||
return value
|
||||
try:
|
||||
return func(value)
|
||||
except (ValueError, TypeError, decimal.InvalidOperation) as e:
|
||||
raise ImportDataError(
|
||||
gettext('ir.msg_value_syntax_error',
|
||||
value=value,
|
||||
column=column,
|
||||
**klass.__names__(field))) from e
|
||||
raise _Error(
|
||||
'ir.msg_value_syntax_error',
|
||||
value=value,
|
||||
**klass.__names__(field)) from e
|
||||
|
||||
def process_lines(data, prefix, fields_def, position=0, klass=cls):
|
||||
line = data[position]
|
||||
@@ -1016,18 +1032,19 @@ class ModelStorage(Model):
|
||||
n = len(fields_names) - len(line)
|
||||
if n > 1:
|
||||
raise ImportDataError(gettext(
|
||||
'ir.msg_import_data_missing_columns', n=n))
|
||||
'ir.msg_import_data_missing_columns',
|
||||
n=n,
|
||||
row=row_index(line) + 1))
|
||||
else:
|
||||
raise ImportDataError(gettext(
|
||||
'ir.msg_import_data_missing_column'))
|
||||
'ir.msg_import_data_missing_column',
|
||||
row=row_index(line) + 1))
|
||||
# Import normal fields_names
|
||||
for i, field in enumerate(fields_names):
|
||||
is_prefix_len = (len(field) == (prefix_len + 1))
|
||||
value = line[i]
|
||||
column = '/'.join(field)
|
||||
if is_prefix_len and ':lang=' in field[-1]:
|
||||
field_name, lang = field[-1].split(':lang=')
|
||||
translate.setdefault(lang, {})[field_name] = value
|
||||
translate.setdefault(lang, {})[field_name] = line[i]
|
||||
elif is_prefix_len and prefix == field[:-1]:
|
||||
field_name = field[-1]
|
||||
this_field_def = fields_def[field_name]
|
||||
@@ -1035,25 +1052,22 @@ class ModelStorage(Model):
|
||||
res = None
|
||||
if field_name == 'id':
|
||||
try:
|
||||
res = int(value)
|
||||
res = int(line[i])
|
||||
except ValueError:
|
||||
res = get_many2one(klass.__name__, value, column)
|
||||
res = get_many2one(line, i, klass.__name__)
|
||||
elif field_type == 'many2one':
|
||||
res = get_many2one(
|
||||
this_field_def['relation'], value, column)
|
||||
res = get_many2one(line, i, this_field_def['relation'])
|
||||
elif field_type == 'many2many':
|
||||
many2many.add(field_name)
|
||||
res = get_many2many(
|
||||
this_field_def['relation'], value, column)
|
||||
line, i, this_field_def['relation'])
|
||||
res = [('add', res)] if res else []
|
||||
elif field_type == 'one2one':
|
||||
res = get_one2one(
|
||||
this_field_def['relation'], value, column)
|
||||
res = get_one2one(line, i, this_field_def['relation'])
|
||||
elif field_type == 'reference':
|
||||
res = get_reference(value, field_name, klass, column)
|
||||
res = get_reference(line, i, field_name, klass)
|
||||
else:
|
||||
res = convert(
|
||||
value, field_type, field_name, klass, column)
|
||||
res = convert(line, i, field_type, field_name, klass)
|
||||
row[field_name] = res
|
||||
elif prefix == field[0:prefix_len]:
|
||||
todo.add(field[prefix_len])
|
||||
@@ -1117,7 +1131,8 @@ class ModelStorage(Model):
|
||||
to_create, to_create_translations = [], []
|
||||
to_write, to_write_translations = [], []
|
||||
languages = set()
|
||||
while len(data):
|
||||
data = data.copy()
|
||||
while data:
|
||||
(row, _, translate) = \
|
||||
process_lines(data, [], fields_def)
|
||||
if dispatch(to_create, to_write, row):
|
||||
|
||||
@@ -24,10 +24,12 @@ class ImportDataTestCase(unittest.TestCase):
|
||||
pool = Pool()
|
||||
Model = pool.get('test.import_data')
|
||||
|
||||
with self.assertRaisesRegex(ImportDataError, "for last column"):
|
||||
with self.assertRaisesRegex(
|
||||
ImportDataError, "for last column at row 2"):
|
||||
Model.import_data(['name'], [["Test"], []])
|
||||
|
||||
with self.assertRaisesRegex(ImportDataError, "for 2 last column"):
|
||||
with self.assertRaisesRegex(
|
||||
ImportDataError, "for 2 last columns at row 2"):
|
||||
Model.import_data(['name', 'value'], [["Test", 1], []])
|
||||
|
||||
@with_transaction()
|
||||
@@ -56,7 +58,8 @@ class ImportDataTestCase(unittest.TestCase):
|
||||
pool = Pool()
|
||||
Boolean = pool.get('test.import_data.boolean')
|
||||
|
||||
with self.assertRaises(ImportDataError):
|
||||
with self.assertRaisesRegex(
|
||||
ImportDataError, "at row 1 and column 1"):
|
||||
Boolean.import_data(['boolean'], [['foo']])
|
||||
|
||||
@with_transaction()
|
||||
@@ -87,7 +90,8 @@ class ImportDataTestCase(unittest.TestCase):
|
||||
|
||||
for value in ['1.1', '-1.1', 'foo']:
|
||||
with self.subTest(value=value):
|
||||
with self.assertRaises(ImportDataError):
|
||||
with self.assertRaisesRegex(
|
||||
ImportDataError, "at row 1 and column 1"):
|
||||
Integer.import_data(['integer'], [[value]])
|
||||
|
||||
@with_transaction()
|
||||
@@ -108,7 +112,8 @@ class ImportDataTestCase(unittest.TestCase):
|
||||
pool = Pool()
|
||||
Float = pool.get('test.import_data.float')
|
||||
|
||||
with self.assertRaises(ImportDataError):
|
||||
with self.assertRaisesRegex(
|
||||
ImportDataError, "'foo' .* at row 1 and column 1"):
|
||||
Float.import_data(['float'], [['foo']])
|
||||
|
||||
@with_transaction()
|
||||
@@ -129,7 +134,8 @@ class ImportDataTestCase(unittest.TestCase):
|
||||
pool = Pool()
|
||||
Numeric = pool.get('test.import_data.numeric')
|
||||
|
||||
with self.assertRaises(ImportDataError):
|
||||
with self.assertRaisesRegex(
|
||||
ImportDataError, "'foo' .* at row 1 and column 1"):
|
||||
Numeric.import_data(['numeric'], [['foo']])
|
||||
|
||||
@with_transaction()
|
||||
@@ -220,7 +226,8 @@ class ImportDataTestCase(unittest.TestCase):
|
||||
pool = Pool()
|
||||
Date = pool.get('test.import_data.date')
|
||||
|
||||
with self.assertRaises(ImportDataError):
|
||||
with self.assertRaisesRegex(
|
||||
ImportDataError, "'foo' .* at row 1 and column 1"):
|
||||
Date.import_data(['date'], [['foo']])
|
||||
|
||||
@with_transaction()
|
||||
@@ -252,7 +259,8 @@ class ImportDataTestCase(unittest.TestCase):
|
||||
pool = Pool()
|
||||
Datetime = pool.get('test.import_data.datetime')
|
||||
|
||||
with self.assertRaises(ImportDataError):
|
||||
with self.assertRaisesRegex(
|
||||
ImportDataError, "'foo' .* at row 1 and column 1"):
|
||||
Datetime.import_data(['datetime'], [['foo']])
|
||||
|
||||
@with_transaction()
|
||||
@@ -276,7 +284,8 @@ class ImportDataTestCase(unittest.TestCase):
|
||||
pool = Pool()
|
||||
Timedelta = pool.get('test.import_data.timedelta')
|
||||
|
||||
with self.assertRaises(ImportDataError):
|
||||
with self.assertRaisesRegex(
|
||||
ImportDataError, "'foo' .* at row 1 and column 1"):
|
||||
Timedelta.import_data(['timedelta'], [['foo']])
|
||||
|
||||
@with_transaction()
|
||||
@@ -334,7 +343,8 @@ class ImportDataTestCase(unittest.TestCase):
|
||||
|
||||
for value in ['foo', 'Duplicate']:
|
||||
with self.subTest(value=value):
|
||||
with self.assertRaises(ImportDataError):
|
||||
with self.assertRaisesRegex(
|
||||
ImportDataError, "at row 1 and column 1"):
|
||||
Many2one.import_data(['many2one'], [[value]])
|
||||
|
||||
@with_transaction()
|
||||
@@ -381,7 +391,8 @@ class ImportDataTestCase(unittest.TestCase):
|
||||
|
||||
for value in ['foo', 'Test 1,foo', 'Duplicate', 'Test 1,Duplicate']:
|
||||
with self.subTest(value=value):
|
||||
with self.assertRaises(ImportDataError):
|
||||
with self.assertRaisesRegex(
|
||||
ImportDataError, "at row 1 and column 1"):
|
||||
Many2many.import_data(['many2many'], [[value]])
|
||||
|
||||
@with_transaction()
|
||||
@@ -518,7 +529,8 @@ class ImportDataTestCase(unittest.TestCase):
|
||||
'test.import_data.reference.selection,Duplicate',
|
||||
'test.import_data.reference.selection,test.foo']:
|
||||
with self.subTest(value=value):
|
||||
with self.assertRaises(ImportDataError):
|
||||
with self.assertRaisesRegex(
|
||||
ImportDataError, "at row 1 and column 1"):
|
||||
Reference.import_data(['reference'], [[value]])
|
||||
|
||||
@with_transaction()
|
||||
|
||||
Reference in New Issue
Block a user