[IMP] models: sanitize error messages in import

Revert 83282f2d for a cleaner sanitizing earlier in the generation of the error
message.

If the import is failing, the error message contains the value that is
problematic. Escape this value in case it contains '%'
This commit is contained in:
Martin Trigaux 2015-07-01 12:36:17 +02:00
parent 988baeb533
commit 2758aaa6f8
3 changed files with 73 additions and 38 deletions

View File

@ -37,6 +37,19 @@ class ConversionNotFound(ValueError): pass
class ir_fields_converter(models.Model): class ir_fields_converter(models.Model):
_name = 'ir.fields.converter' _name = 'ir.fields.converter'
@api.model
def _format_import_error(self, error_type, error_msg, error_params=(), error_args=None):
# sanitize error params for later formatting by the import system
sanitize = lambda p: p.replace('%', '%%') if isinstance(p, basestring) else p
if error_params:
if isinstance(error_params, basestring):
error_params = sanitize(error_params)
elif isinstance(error_params, dict):
error_params = dict((k, sanitize(v)) for k, v in error_params.iteritems())
elif isinstance(error_params, tuple):
error_params = tuple(map(sanitize, error_params))
return error_type(error_msg % error_params, error_args)
@api.model @api.model
def for_model(self, model, fromtype=str): def for_model(self, model, fromtype=str):
""" Returns a converter object for the model. A converter is a """ Returns a converter object for the model. A converter is a
@ -148,29 +161,34 @@ class ir_fields_converter(models.Model):
if value.lower() in falses: if value.lower() in falses:
return False, [] return False, []
return True, [ImportWarning( return True, [self._format_import_error(
_(u"Unknown value '%s' for boolean field '%%(field)s', assuming '%s'") ImportWarning,
% (value, yes), { _(u"Unknown value '%s' for boolean field '%%(field)s', assuming '%s'"),
'moreinfo': _(u"Use '1' for yes and '0' for no") (value, yes),
})] {'moreinfo': _(u"Use '1' for yes and '0' for no")}
)]
@api.model @api.model
def _str_to_integer(self, model, field, value): def _str_to_integer(self, model, field, value):
try: try:
return int(value), [] return int(value), []
except ValueError: except ValueError:
raise ValueError( raise self._format_import_error(
_(u"'%s' does not seem to be an integer for field '%%(field)s'") ValueError,
% value) _(u"'%s' does not seem to be an integer for field '%%(field)s'"),
value
)
@api.model @api.model
def _str_to_float(self, model, field, value): def _str_to_float(self, model, field, value):
try: try:
return float(value), [] return float(value), []
except ValueError: except ValueError:
raise ValueError( raise self._format_import_error(
_(u"'%s' does not seem to be a number for field '%%(field)s'") ValueError,
% value) _(u"'%s' does not seem to be a number for field '%%(field)s'"),
value
)
@api.model @api.model
def _str_id(self, model, field, value): def _str_id(self, model, field, value):
@ -184,10 +202,12 @@ class ir_fields_converter(models.Model):
time.strptime(value, DEFAULT_SERVER_DATE_FORMAT) time.strptime(value, DEFAULT_SERVER_DATE_FORMAT)
return value, [] return value, []
except ValueError: except ValueError:
raise ValueError( raise self._format_import_error(
_(u"'%s' does not seem to be a valid date for field '%%(field)s'") % value, { ValueError,
'moreinfo': _(u"Use the format '%s'") % u"2012-12-31" _(u"'%s' does not seem to be a valid date for field '%%(field)s'"),
}) value,
{'moreinfo': _(u"Use the format '%s'") % u"2012-12-31"}
)
@api.model @api.model
def _input_tz(self): def _input_tz(self):
@ -215,10 +235,12 @@ class ir_fields_converter(models.Model):
parsed_value = datetime.datetime.strptime( parsed_value = datetime.datetime.strptime(
value, DEFAULT_SERVER_DATETIME_FORMAT) value, DEFAULT_SERVER_DATETIME_FORMAT)
except ValueError: except ValueError:
raise ValueError( raise self._format_import_error(
_(u"'%s' does not seem to be a valid datetime for field '%%(field)s'") % value, { ValueError,
'moreinfo': _(u"Use the format '%s'") % u"2012-12-31 23:59:59" _(u"'%s' does not seem to be a valid datetime for field '%%(field)s'"),
}) value,
{'moreinfo': _(u"Use the format '%s'") % u"2012-12-31 23:59:59"}
)
input_tz = self._input_tz()# Apply input tz to the parsed naive datetime input_tz = self._input_tz()# Apply input tz to the parsed naive datetime
dt = input_tz.localize(parsed_value, is_dst=False) dt = input_tz.localize(parsed_value, is_dst=False)
@ -251,12 +273,12 @@ class ir_fields_converter(models.Model):
if value == unicode(item) or value in labels: if value == unicode(item) or value in labels:
return item, [] return item, []
raise ValueError( raise self._format_import_error(
_(u"Value '%s' not found in selection field '%%(field)s'") % ( ValueError,
value), { _(u"Value '%s' not found in selection field '%%(field)s'"),
'moreinfo': [_label or unicode(item) for item, _label in selection value,
if _label or item] {'moreinfo': [_label or unicode(item) for item, _label in selection if _label or item]}
}) )
@api.model @api.model
def db_id_for(self, model, field, subfield, value): def db_id_for(self, model, field, subfield, value):
@ -298,8 +320,10 @@ class ir_fields_converter(models.Model):
id = tentative_id id = tentative_id
except psycopg2.DataError: except psycopg2.DataError:
# type error # type error
raise ValueError( raise self._format_import_error(
_(u"Invalid database id '%s' for the field '%%(field)s'") % value, ValueError,
_(u"Invalid database id '%s' for the field '%%(field)s'"),
value,
{'moreinfo': action}) {'moreinfo': action})
elif subfield == 'id': elif subfield == 'id':
field_type = _(u"external id") field_type = _(u"external id")
@ -321,12 +345,17 @@ class ir_fields_converter(models.Model):
% (len(ids)))) % (len(ids))))
id, _name = ids[0] id, _name = ids[0]
else: else:
raise Exception(_(u"Unknown sub-field '%s'") % subfield) raise self._format_import_error(
Exception,
_(u"Unknown sub-field '%s'"),
subfield
)
if id is None: if id is None:
raise ValueError( raise self._format_import_error(
_(u"No matching record found for %(field_type)s '%(value)s' in field '%%(field)s'") ValueError,
% {'field_type': field_type, 'value': value}, _(u"No matching record found for %(field_type)s '%(value)s' in field '%%(field)s'"),
{'field_type': field_type, 'value': value},
{'moreinfo': action}) {'moreinfo': action})
return id, field_type, warnings return id, field_type, warnings

View File

@ -111,6 +111,12 @@ class test_ids_stuff(ImporterCase):
self.import_(['id', 'value'], [['somexmlid', '1234567']]) self.import_(['id', 'value'], [['somexmlid', '1234567']])
self.assertEqual([1234567], values(self.read())) self.assertEqual([1234567], values(self.read()))
def test_wrong_format(self):
self.assertEqual(
self.import_(['value'], [['50%']]),
error(1, u"'50%' does not seem to be an integer for field 'unknown'"))
class test_boolean_field(ImporterCase): class test_boolean_field(ImporterCase):
model_name = 'export.boolean' model_name = 'export.boolean'
@ -163,10 +169,13 @@ class test_boolean_field(ImporterCase):
# Problem: OpenOffice (and probably excel) output localized booleans # Problem: OpenOffice (and probably excel) output localized booleans
['VRAI'], ['VRAI'],
[u'OFF'], [u'OFF'],
[u'是的'],
['!&%#${}'],
['%(field)s'],
]), ]),
ok(8)) ok(11))
self.assertEqual( self.assertEqual(
[True] * 8, [True] * 11,
values(self.read())) values(self.read()))
class test_integer_field(ImporterCase): class test_integer_field(ImporterCase):

View File

@ -1199,11 +1199,8 @@ class BaseModel(object):
type = 'warning' if isinstance(exception, Warning) else 'error' type = 'warning' if isinstance(exception, Warning) else 'error'
# logs the logical (not human-readable) field name for automated # logs the logical (not human-readable) field name for automated
# processing of response, but injects human readable in message # processing of response, but injects human readable in message
# Before applying string substitution, we need to escape all '%' record = dict(base, type=type, field=field,
# characters that are not part of substitutable reference. message=unicode(exception.args[0]) % base)
regex = '%%(?!\((%s)\)s)' % '|'.join(base.keys())
message = unicode(re.sub(regex, '%%', exception.args[0])) % base
record = dict(base, type=type, field=field, message=message)
if len(exception.args) > 1 and exception.args[1]: if len(exception.args) > 1 and exception.args[1]:
record.update(exception.args[1]) record.update(exception.args[1])
log(record) log(record)