[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:
parent
988baeb533
commit
2758aaa6f8
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue