[IMP] allow converters to add data to import messages, formalize message keys

bzr revid: xmo@openerp.com-20120924150417-c2y7g7vdsfz66363
This commit is contained in:
Xavier Morel 2012-09-24 17:04:17 +02:00
parent a9c2cfcdb9
commit f143902d1a
3 changed files with 94 additions and 41 deletions

View File

@ -38,6 +38,26 @@ class ir_fields_converter(orm.Model):
By default, tries to get a method on itself with a name matching the
pattern ``_$fromtype_$column._type`` and returns it.
Converter callables can either return a value to their caller or raise
``ValueError``, which will be interpreted as a validation & conversion
failure.
ValueError can have either one or two parameters. The first parameter
is mandatory, **must** be a unicode string and will be used as the
user-visible message for the error (it should be translatable and
translated). It can contain a ``field`` named format placeholder so the
caller can inject the field's translated, user-facing name (@string).
The second parameter is optional and, if provided, must be a mapping.
This mapping will be merged into the error dictionary returned to the
client.
If a converter can perform its function but has to make assumptions
about the data, it can send a warning to the user through signalling
an :class:`~openerp.osv.orm.ImportWarning` (via ``warnings.warn``). The
handling of a warning at the upper levels is the same as
``ValueError`` above.
:param cr: openerp cursor
:param uid: ID of user calling the converter
:param column: column object to generate a value for
@ -74,10 +94,9 @@ class ir_fields_converter(orm.Model):
))
if value.lower() in falses: return False
warnings.warn(
warnings.warn(orm.ImportWarning(
_(u"Unknown value '%s' for boolean field '%%(field)s', assuming '%s'")
% (value, yes),
orm.ImportWarning)
% (value, yes)))
return True
def _str_to_integer(self, cr, uid, model, column, value, context=None):
@ -124,7 +143,9 @@ class ir_fields_converter(orm.Model):
return item
raise ValueError(
_(u"Value '%s' not found in selection field '%%(field)s'") % (
value))
value), {
'moreinfo': map(operator.itemgetter(1), selection)
})
def db_id_for(self, cr, uid, model, column, subfield, value, context=None):
@ -174,9 +195,9 @@ class ir_fields_converter(orm.Model):
cr, uid, name=value, operator='=', context=context)
if ids:
if len(ids) > 1:
warnings.warn(
warnings.warn(orm.ImportWarning(
_(u"Found multiple matches for field '%%(field)s' (%d matches)")
% (len(ids)), orm.ImportWarning)
% (len(ids))))
id, _name = ids[0]
else:
raise Exception(u"Unknown sub-field '%s'" % subfield)

View File

@ -1444,6 +1444,40 @@ class BaseModel(object):
def load(self, cr, uid, fields, data, context=None):
"""
Attempts to load the data matrix, and returns a list of ids (or
``False`` if there was an error and no id could be generated) and a
list of messages.
Each message is a dictionary with the following keys:
``type``
the type of message, either ``warning`` or ``error``. Any ``error``
message indicates the import failed and was rolled back.
``message``
the message's actual text, which should be translated and can be
shown to the user directly
``rows``
a dict with 2 keys ``from`` and ``to``, indicates the range of rows
in ``data`` which generated the message
``record``
a single integer, for warnings the index of the record which
generated the message (can be obtained from a non-false ``ids``
result)
``field``
the name of the (logical) OpenERP field for which the error or
warning was generated
``moreinfo`` (optional)
A string, a list or a dict, leading to more information about the
warning.
* If ``moreinfo`` is a string, it is a supplementary warnings
message which should be hidden by default
* If ``moreinfo`` is a list, it provides a number of possible or
alternative values for the string
* If ``moreinfo`` is a dict, it is an OpenERP action descriptor
which can be executed to get more information about the issues
with the field. If present, the ``help`` key serves as a label
for the action (e.g. the text of the link).
:param cr: cursor for the request
:param int uid: ID of the user attempting the data import
@ -1598,6 +1632,14 @@ class BaseModel(object):
(k, Converter.to_field(cr, uid, self, column, context=context))
for k, column in columns.iteritems())
def _log(base, field, exception):
type = 'warning' if isinstance(exception, Warning) else 'error'
record = dict(base, field=field, type=type,
message=unicode(exception.args[0]) % base)
if len(exception.args) > 1 and exception.args[1]:
record.update(exception.args[1])
log(record)
stream = CountingStream(records)
for record, extras in stream:
dbid = False
@ -1630,21 +1672,23 @@ class BaseModel(object):
# field name
message_base = dict(
extras, record=stream.index, field=field_names[field])
with warnings.catch_warnings(record=True) as w:
try:
try:
with warnings.catch_warnings(record=True) as ws:
converted[field] = converters[field](strvalue)
# In warning and error returned, use logical field name
# as field so client can reverse
for warning in w:
log(dict(message_base, type='warning', field=field,
message=unicode(warning.message) % message_base))
except ValueError, e:
log(dict(message_base,
type='error',
field=field,
message=unicode(e) % message_base,
))
for warning in ws:
# bubble non-import warnings upward
if warning.category != ImportWarning:
warnings.warn(warning.message, warning.category)
continue
w = warning.message
if isinstance(w, basestring):
# wrap warning string in an ImportWarning for
# uniform handling
w = ImportWarning(w)
_log(message_base, field, w)
except ValueError, e:
_log(message_base, field, e)
yield dbid, xid, converted, dict(extras, record=stream.index)

View File

@ -5,14 +5,10 @@ import openerp
from openerp.tests import common
from openerp.tools.misc import mute_logger
def message(msg, type='error', from_=0, to_=0, record=0, field='value'):
return {
'type': type,
'rows': {'from': from_, 'to': to_},
'record': record,
'field': field,
'message': msg
}
def message(msg, type='error', from_=0, to_=0, record=0, field='value', **kwargs):
return dict(kwargs,
type=type, rows={'from': from_, 'to': to_}, record=record,
field=field, message=msg)
def error(row, message, record=None, **kwargs):
""" Failed import of the record ``record`` at line ``row``, with the error
@ -455,23 +451,15 @@ class test_selection(ImporterCase):
def test_invalid(self):
ids, messages = self.import_(['value'], [['Baz']])
self.assertIs(ids, False)
self.assertEqual(messages, [{
'type': 'error',
'rows': {'from': 0, 'to': 0},
'record': 0,
'field': 'value',
'message': "Value 'Baz' not found in selection field 'unknown'",
}])
self.assertEqual(messages, [message(
u"Value 'Baz' not found in selection field 'unknown'",
moreinfo="Foo Bar Qux".split())])
ids, messages = self.import_(['value'], [[42]])
self.assertIs(ids, False)
self.assertEqual(messages, [{
'type': 'error',
'rows': {'from': 0, 'to': 0},
'record': 0,
'field': 'value',
'message': "Value '42' not found in selection field 'unknown'",
}])
self.assertEqual(messages, [message(
u"Value '42' not found in selection field 'unknown'",
moreinfo="Foo Bar Qux".split())])
class test_selection_function(ImporterCase):
model_name = 'export.selection.function'