changeset 65e6c8a59986 in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset&node=65e6c8a59986
description:
        Include model, field and column in import data error message

        issue11132
        review381701002
diffstat:

 CHANGELOG                        |    1 +
 trytond/ir/message.xml           |   11 +-
 trytond/model/modelstorage.py    |  210 +++++++-----
 trytond/tests/import_data.py     |   21 -
 trytond/tests/test_importdata.py |  628 ++++++++++++++++----------------------
 5 files changed, 393 insertions(+), 478 deletions(-)

diffs (1245 lines):

diff -r 3b5056ac4f06 -r 65e6c8a59986 CHANGELOG
--- a/CHANGELOG Sun Jan 30 01:42:23 2022 +0100
+++ b/CHANGELOG Sun Jan 30 12:58:51 2022 +0100
@@ -1,3 +1,4 @@
+* Include model, field and column in import data error message
 * Support limit and offset to ModelSQL count search and search_count
 * Apply view inheritance to board
 * Add RPC view_get method to View
diff -r 3b5056ac4f06 -r 65e6c8a59986 trytond/ir/message.xml
--- a/trytond/ir/message.xml    Sun Jan 30 01:42:23 2022 +0100
+++ b/trytond/ir/message.xml    Sun Jan 30 12:58:51 2022 +0100
@@ -146,16 +146,19 @@
             <field name="text">This record is part of the base 
configuration.</field>
         </record>
         <record model="ir.message" id="msg_relation_not_found">
-            <field name="text">Relation not found: "%(value)r" in 
"%(model)s".</field>
+            <field name="text">Relation not found: %(value)r in "%(model)s" 
(%(column)s).</field>
         </record>
         <record model="ir.message" id="msg_too_many_relations_found">
-            <field name="text">Too many relations found: "%(value)r" in 
"%(model)s".</field>
+            <field name="text">Too many relations found: %(value)r in 
"%(model)s" (%(column)s).</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>
         </record>
         <record model="ir.message" id="msg_reference_syntax_error">
-            <field name="text">Syntax error for reference: "%(value)r" in 
"%(field)s".</field>
+            <field name="text">Syntax error for reference: %(value)r in 
"%(field)s" of "%(model)s" (%(column)s).</field>
         </record>
         <record model="ir.message" id="msg_xml_id_syntax_error">
-            <field name="text">Syntax error for XML id: "%(value)r" in 
"%(field)s".</field>
+            <field name="text">Syntax error for XML id: %(value)r in 
"%(field)s" of "%(model)s" (%(column)s).</field>
         </record>
         <record model="ir.message" id="msg_domain_validation_record">
             <field name="text">The value for field "%(field)s" in "%(model)s" 
is not valid according to its domain.</field>
diff -r 3b5056ac4f06 -r 65e6c8a59986 trytond/model/modelstorage.py
--- a/trytond/model/modelstorage.py     Sun Jan 30 01:42:23 2022 +0100
+++ b/trytond/model/modelstorage.py     Sun Jan 30 12:58:51 2022 +0100
@@ -4,6 +4,7 @@
 import base64
 import csv
 import datetime
+import decimal
 import random
 import time
 from collections import defaultdict
@@ -764,7 +765,7 @@
         pool = Pool()
 
         @lru_cache(maxsize=1000)
-        def get_many2one(relation, value):
+        def get_many2one(relation, value, column):
             if not value:
                 return None
             Relation = pool.get(relation)
@@ -775,18 +776,19 @@
                 raise ImportDataError(gettext(
                         'ir.msg_relation_not_found',
                         value=value,
-                        model=relation))
+                        **Relation.__names__()))
             elif len(res) > 1:
                 raise ImportDataError(
                     gettext('ir.msg_too_many_relations_found',
                         value=value,
-                        model=relation))
+                        column=column,
+                        **Relation.__names__()))
             else:
                 res = res[0].id
             return res
 
         @lru_cache(maxsize=1000)
-        def get_many2many(relation, value):
+        def get_many2many(relation, value, column):
             if not value:
                 return None
             res = []
@@ -800,33 +802,36 @@
                     raise ImportDataError(
                         gettext('ir.msg_relation_not_found',
                             value=word,
-                            model=relation))
+                            column=column,
+                            **Relation.__names__()))
                 elif len(res2) > 1:
                     raise ImportDataError(
                         gettext('ir.msg_too_many_relations_found',
                             value=word,
-                            model=relation))
+                            column=column,
+                            **Relation.__names__()))
                 else:
                     res.extend(res2)
             if len(res):
                 res = [('add', [x.id for x in res])]
             return res
 
-        def get_one2one(relation, value):
-            return ('add', get_many2one(relation, value))
+        def get_one2one(relation, value, column):
+            return ('add', get_many2one(relation, value, column))
 
         @lru_cache(maxsize=1000)
-        def get_reference(value, field):
+        def get_reference(value, field, klass, column):
             if not value:
                 return None
             try:
                 relation, value = value.split(',', 1)
-            except Exception:
+                Relation = pool.get(relation)
+            except (ValueError, KeyError) as e:
                 raise ImportDataError(
                     gettext('ir.msg_reference_syntax_error',
                         value=value,
-                        field=field))
-            Relation = pool.get(relation)
+                        column=column,
+                        **klass.__names__(field))) from e
             res = Relation.search([
                 ('rec_name', '=', value),
                 ], limit=2)
@@ -834,18 +839,19 @@
                 raise ImportDataError(gettext(
                         'ir.msg_relation_not_found',
                         value=value,
-                        model=relation))
+                        **Relation.__names__()))
             elif len(res) > 1:
                 raise ImportDataError(
                     gettext('ir.msg_too_many_relations_found',
                         value=value,
-                        model=relation))
+                        column=column,
+                        **Relation.__names__()))
             else:
                 res = '%s,%s' % (relation, res[0].id)
             return res
 
         @lru_cache(maxsize=1000)
-        def get_by_id(value, field, ftype):
+        def get_by_id(value, ftype, field, klass, column):
             if not value:
                 return None
             relation = None
@@ -855,11 +861,12 @@
             elif ftype == 'reference':
                 try:
                     relation, value = value.split(',', 1)
-                except Exception:
+                except ValueError as e:
                     raise ImportDataError(
                         gettext('ir.msg_reference_syntax_error',
                             value=value,
-                            field=field))
+                            column=column,
+                            **klass.__names__(field))) from e
                 value = [value]
             else:
                 value = [value]
@@ -867,12 +874,13 @@
             for word in value:
                 try:
                     module, xml_id = word.rsplit('.', 1)
-                except Exception:
+                    db_id = ModelData.get_id(module, xml_id)
+                except (ValueError, KeyError) as e:
                     raise ImportDataError(
                         gettext('ir.msg_xml_id_syntax_error',
                             value=word,
-                            field=field))
-                db_id = ModelData.get_id(module, xml_id)
+                            column=column,
+                            **klass.__names__(field))) from e
                 res_ids.append(db_id)
             if ftype == 'many2many' and res_ids:
                 return [('add', res_ids)]
@@ -889,6 +897,78 @@
                 create.append(row)
             return id_
 
+        def convert(value, ftype, field, klass, column):
+            def convert_boolean(value):
+                if value.lower() == 'true':
+                    return True
+                elif value.lower() == 'false':
+                    return False
+                elif not value:
+                    return False
+                else:
+                    return bool(int(value))
+
+            def convert_integer(value):
+                if isinstance(value, int):
+                    return value
+                elif value:
+                    return int(value)
+
+            def convert_float(value):
+                if isinstance(value, float):
+                    return value
+                elif value:
+                    return float(value)
+
+            def convert_numeric(value):
+                if isinstance(value, Decimal):
+                    return value
+                elif value:
+                    return Decimal(value)
+
+            def convert_date(value):
+                if isinstance(value, datetime.date):
+                    return value
+                elif value:
+                    return datetime.datetime.strptime(value, '%Y-%m-%d').date()
+
+            def convert_datetime(value):
+                if isinstance(value, datetime.datetime):
+                    return value
+                elif value:
+                    return datetime.datetime.strptime(
+                        value, '%Y-%m-%d %H:%M:%S')
+
+            def convert_timedelta(value):
+                if isinstance(value, datetime.timedelta):
+                    return value
+                elif value:
+                    try:
+                        return float(value)
+                    except ValueError:
+                        hours, minutes, seconds = (
+                            value.split(':') + ['00'])[:3]
+                        return datetime.timedelta(
+                            hours=int(hours), minutes=int(minutes),
+                            seconds=float(seconds))
+
+            def convert_binary(value):
+                if not isinstance(value, bytes):
+                    return base64.b64decode(value)
+                elif value:
+                    return value
+
+            try:
+                return locals()['convert_%s' % ftype](value)
+            except KeyError:
+                return 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
+
         def process_lines(data, prefix, fields_def, position=0, klass=cls):
             line = data[position]
             row = {}
@@ -903,95 +983,39 @@
                         % len(fields_names))
                 is_prefix_len = (len(field) == (prefix_len + 1))
                 value = line[i]
+                column = '/'.join(field)
                 if is_prefix_len and field[-1].endswith(':id'):
-                    ftype = fields_def[field[-1][:-3]]['type']
+                    field_name = field[-1][:-3]
+                    ftype = fields_def[field_name]['type']
                     row[field[0][:-3]] = get_by_id(
-                        value, '/'.join(field), ftype)
+                        value, ftype, field_name, klass, column)
                 elif is_prefix_len and ':lang=' in field[-1]:
                     field_name, lang = field[-1].split(':lang=')
                     translate.setdefault(lang, {})[field_name] = value or False
                 elif is_prefix_len and prefix == field[:-1]:
-                    this_field_def = fields_def[field[-1]]
+                    field_name = field[-1]
+                    this_field_def = fields_def[field_name]
                     field_type = this_field_def['type']
                     res = None
-                    if field[-1] == 'id':
+                    if field_name == 'id':
                         try:
                             res = int(value)
                         except ValueError:
-                            res = get_many2one(klass.__name__, value)
-                    elif field_type == 'boolean':
-                        if value.lower() == 'true':
-                            res = True
-                        elif value.lower() == 'false':
-                            res = False
-                        elif not value:
-                            res = False
-                        else:
-                            res = bool(int(value))
-                    elif field_type == 'integer':
-                        if isinstance(value, int):
-                            res = value
-                        elif value:
-                            res = int(value)
-                        else:
-                            res = None
-                    elif field_type == 'float':
-                        if isinstance(value, float):
-                            res = value
-                        elif value:
-                            res = float(value)
-                        else:
-                            res = None
-                    elif field_type == 'numeric':
-                        if isinstance(value, Decimal):
-                            res = value
-                        elif value:
-                            res = Decimal(value)
-                        else:
-                            res = None
-                    elif field_type == 'date':
-                        if isinstance(value, datetime.date):
-                            res = value
-                        elif value:
-                            res = datetime.datetime.strptime(
-                                value, '%Y-%m-%d').date()
-                        else:
-                            res = None
-                    elif field_type == 'datetime':
-                        if isinstance(value, datetime.datetime):
-                            res = value
-                        elif value:
-                            res = datetime.datetime.strptime(
-                                value, '%Y-%m-%d %H:%M:%S')
-                        else:
-                            res = None
-                    elif field_type == 'timedelta':
-                        if isinstance(value, datetime.timedelta):
-                            res = value
-                        elif value:
-                            try:
-                                res = float(value)
-                            except ValueError:
-                                hours, minutes, seconds = (
-                                    value.split(':') + ['00'])[:3]
-                                res = datetime.timedelta(
-                                    hours=int(hours), minutes=int(minutes),
-                                    seconds=float(seconds))
-                        else:
-                            res = None
+                            res = get_many2one(klass.__name__, value, column)
                     elif field_type == 'many2one':
-                        res = get_many2one(this_field_def['relation'], value)
+                        res = get_many2one(
+                            this_field_def['relation'], value, column)
                     elif field_type == 'many2many':
-                        res = get_many2many(this_field_def['relation'], value)
+                        res = get_many2many(
+                            this_field_def['relation'], value, column)
                     elif field_type == 'one2one':
-                        res = get_one2one(this_field_def['relation'], value)
+                        res = get_one2one(
+                            this_field_def['relation'], value, column)
                     elif field_type == 'reference':
-                        res = get_reference(value, '/'.join(field))
-                    elif (field_type == 'binary'
-                            and not isinstance(value, bytes)):
-                        res = base64.b64decode(value)
+                        res = get_reference(value, field_name, klass, column)
                     else:
-                        res = value or None
+                        res = convert(
+                            value, field_type, field_name, klass, column)
                     row[field[-1]] = res
                 elif prefix == field[0:prefix_len]:
                     todo.add(field[prefix_len])
diff -r 3b5056ac4f06 -r 65e6c8a59986 trytond/tests/import_data.py
--- a/trytond/tests/import_data.py      Sun Jan 30 01:42:23 2022 +0100
+++ b/trytond/tests/import_data.py      Sun Jan 30 12:58:51 2022 +0100
@@ -17,36 +17,18 @@
     integer = fields.Integer('Integer')
 
 
-class ImportDataIntegerRequired(ModelSQL):
-    "Import Data Integer Required"
-    __name__ = 'test.import_data.integer_required'
-    integer = fields.Integer('Integer', required=True)
-
-
 class ImportDataFloat(ModelSQL):
     "Import Data Float"
     __name__ = 'test.import_data.float'
     float = fields.Float('Float')
 
 
-class ImportDataFloatRequired(ModelSQL):
-    "Import Data Float Required"
-    __name__ = 'test.import_data.float_required'
-    float = fields.Float('Float', required=True)
-
-
 class ImportDataNumeric(ModelSQL):
     "Import Data Numeric"
     __name__ = 'test.import_data.numeric'
     numeric = fields.Numeric('Numeric')
 
 
-class ImportDataNumericRequired(ModelSQL):
-    "Import Data Numeric Required"
-    __name__ = 'test.import_data.numeric_required'
-    numeric = fields.Numeric('Numeric', required=True)
-
-
 class ImportDataChar(ModelSQL):
     "Import Data Char"
     __name__ = 'test.import_data.char'
@@ -166,11 +148,8 @@
     Pool.register(
         ImportDataBoolean,
         ImportDataInteger,
-        ImportDataIntegerRequired,
         ImportDataFloat,
-        ImportDataFloatRequired,
         ImportDataNumeric,
-        ImportDataNumericRequired,
         ImportDataChar,
         ImportDataText,
         ImportDataDate,
diff -r 3b5056ac4f06 -r 65e6c8a59986 trytond/tests/test_importdata.py
--- a/trytond/tests/test_importdata.py  Sun Jan 30 01:42:23 2022 +0100
+++ b/trytond/tests/test_importdata.py  Sun Jan 30 12:58:51 2022 +0100
@@ -1,15 +1,13 @@
 # -*- coding: utf-8 -*-
 # This file is part of Tryton.  The COPYRIGHT file at the top level of
 # this repository contains the full copyright notices and license terms.
-import datetime
+import datetime as dt
 import unittest
-from decimal import Decimal, InvalidOperation
+from decimal import Decimal
 
-from trytond.model.exceptions import (
-    ImportDataError, RequiredValidationError, SelectionValidationError)
+from trytond.model.exceptions import ImportDataError
 from trytond.pool import Pool
 from trytond.tests.test_tryton import activate_module, with_transaction
-from trytond.transaction import Transaction
 
 
 class ImportDataTestCase(unittest.TestCase):
@@ -25,26 +23,28 @@
         pool = Pool()
         Boolean = pool.get('test.import_data.boolean')
 
-        self.assertEqual(Boolean.import_data(['boolean'],
-            [['True']]), 1)
+        for value in ['True', '1', 'False', '0', '']:
+            with self.subTest(value=value):
+                self.assertEqual(
+                    Boolean.import_data(['boolean'], [[value]]), 1)
 
-        self.assertEqual(Boolean.import_data(['boolean'],
-            [['1']]), 1)
-
-        self.assertEqual(Boolean.import_data(['boolean'],
-            [['False']]), 1)
+    @with_transaction()
+    def test_boolean_many_rows(self):
+        "Test boolean many rows"
+        pool = Pool()
+        Boolean = pool.get('test.import_data.boolean')
 
-        self.assertEqual(Boolean.import_data(['boolean'],
-            [['0']]), 1)
-
-        self.assertEqual(Boolean.import_data(['boolean'],
-            [['']]), 1)
+        self.assertEqual(
+            Boolean.import_data(['boolean'], [['True'], ['False']]), 2)
 
-        self.assertEqual(Boolean.import_data(['boolean'],
-            [['True'], ['False']]), 2)
+    @with_transaction()
+    def test_boolean_invalid(self):
+        "Test boolean invalid value"
+        pool = Pool()
+        Boolean = pool.get('test.import_data.boolean')
 
-        self.assertRaises(ValueError, Boolean.import_data,
-            ['boolean'], [['foo']])
+        with self.assertRaises(ImportDataError):
+            Boolean.import_data(['boolean'], [['foo']])
 
     @with_transaction()
     def test_integer(self):
@@ -52,77 +52,30 @@
         pool = Pool()
         Integer = pool.get('test.import_data.integer')
 
-        self.assertEqual(Integer.import_data(['integer'],
-            [['1']]), 1)
-
-        self.assertEqual(Integer.import_data(['integer'],
-            [[0]]), 1)
-
-        self.assertEqual(Integer.import_data(['integer'],
-            [[1]]), 1)
-
-        self.assertEqual(Integer.import_data(['integer'],
-            [['-1']]), 1)
-
-        self.assertEqual(Integer.import_data(['integer'],
-            [['']]), 1)
-
-        self.assertEqual(Integer.import_data(['integer'],
-            [['1'], ['2']]), 2)
-
-        self.assertRaises(ValueError, Integer.import_data,
-            ['integer'], [['1.1']])
-
-        self.assertRaises(ValueError, Integer.import_data,
-            ['integer'], [['-1.1']])
-
-        self.assertRaises(ValueError, Integer.import_data,
-            ['integer'], [['foo']])
-
-        self.assertEqual(Integer.import_data(['integer'],
-            [['0']]), 1)
-
-        self.assertEqual(Integer.import_data(['integer'],
-            [[None]]), 1)
+        for value in ['1', '0', 0, 1, '-1', '', None]:
+            with self.subTest(value=value):
+                self.assertEqual(
+                    Integer.import_data(['integer'], [[value]]), 1)
 
     @with_transaction()
-    def test_integer_required(self):
-        'Test required integer'
+    def test_integer_many_rows(self):
+        "Test integer many rows"
         pool = Pool()
-        IntegerRequired = pool.get('test.import_data.integer_required')
-        transaction = Transaction()
-
-        self.assertEqual(IntegerRequired.import_data(['integer'],
-            [['1']]), 1)
+        Integer = pool.get('test.import_data.integer')
 
-        self.assertEqual(IntegerRequired.import_data(['integer'],
-            [['-1']]), 1)
-
-        with self.assertRaises(RequiredValidationError):
-            IntegerRequired.import_data(['integer'], [['']])
-        transaction.rollback()
-
-        self.assertEqual(IntegerRequired.import_data(['integer'],
-            [['1'], ['2']]), 2)
+        self.assertEqual(
+            Integer.import_data(['integer'], [['1'], ['2']]), 2)
 
-        self.assertRaises(ValueError, IntegerRequired.import_data,
-            ['integer'], [['1.1']])
-
-        self.assertRaises(ValueError, IntegerRequired.import_data,
-            ['integer'], [['-1.1']])
-
-        self.assertRaises(ValueError, IntegerRequired.import_data,
-            ['integer'], [['foo']])
+    @with_transaction()
+    def test_integer_invalid(self):
+        "Test integer invalid value"
+        pool = Pool()
+        Integer = pool.get('test.import_data.integer')
 
-        self.assertEqual(IntegerRequired.import_data(['integer'],
-            [['0']]), 1)
-
-        self.assertEqual(IntegerRequired.import_data(['integer'],
-            [[0]]), 1)
-
-        with self.assertRaises(RequiredValidationError):
-            IntegerRequired.import_data(['integer'], [[None]])
-        transaction.rollback()
+        for value in ['1.1', '-1.1', 'foo']:
+            with self.subTest(value=value):
+                with self.assertRaises(ImportDataError):
+                    Integer.import_data(['integer'], [[value]])
 
     @with_transaction()
     def test_float(self):
@@ -130,77 +83,20 @@
         pool = Pool()
         Float = pool.get('test.import_data.float')
 
-        self.assertEqual(Float.import_data(['float'],
-            [['1.1']]), 1)
-
-        self.assertEqual(Float.import_data(['float'],
-            [[0.0]]), 1)
-
-        self.assertEqual(Float.import_data(['float'],
-            [[1.1]]), 1)
-
-        self.assertEqual(Float.import_data(['float'],
-            [['-1.1']]), 1)
-
-        self.assertEqual(Float.import_data(['float'],
-            [['1']]), 1)
-
-        self.assertEqual(Float.import_data(['float'],
-            [['']]), 1)
-
-        self.assertEqual(Float.import_data(['float'],
-            [['1.1'], ['2.2']]), 2)
-
-        self.assertRaises(ValueError, Float.import_data,
-            ['float'], [['foo']])
-
-        self.assertEqual(Float.import_data(['float'],
-            [['0']]), 1)
-
-        self.assertEqual(Float.import_data(['float'],
-            [['0.0']]), 1)
-
-        self.assertEqual(Float.import_data(['float'],
-            [[None]]), 1)
+        for value in [
+                '1.1', 0.0, 1.1, '-1.1', '1', '', '1.1', '0', '0.0', None]:
+            with self.subTest(value=value):
+                self.assertEqual(
+                    Float.import_data(['float'], [[value]]), 1)
 
     @with_transaction()
-    def test_float_required(self):
-        'Test required float'
+    def test_float_invalid(self):
+        "Test float invalid value"
         pool = Pool()
-        FloatRequired = pool.get('test.import_data.float_required')
-        transaction = Transaction()
-
-        self.assertEqual(FloatRequired.import_data(['float'],
-            [['1.1']]), 1)
-
-        self.assertEqual(FloatRequired.import_data(['float'],
-            [['-1.1']]), 1)
-
-        self.assertEqual(FloatRequired.import_data(['float'],
-            [['1']]), 1)
-
-        with self.assertRaises(RequiredValidationError):
-            FloatRequired.import_data(['float'], [['']])
-        transaction.rollback()
+        Float = pool.get('test.import_data.float')
 
-        self.assertEqual(FloatRequired.import_data(['float'],
-            [['1.1'], ['2.2']]), 2)
-
-        self.assertRaises(ValueError, FloatRequired.import_data,
-            ['float'], [['foo']])
-
-        self.assertEqual(FloatRequired.import_data(['float'],
-            [['0']]), 1)
-
-        self.assertEqual(FloatRequired.import_data(['float'],
-            [['0.0']]), 1)
-
-        self.assertEqual(FloatRequired.import_data(['float'],
-            [[0.0]]), 1)
-
-        with self.assertRaises(RequiredValidationError):
-            FloatRequired.import_data(['float'], [[None]])
-        transaction.rollback()
+        with self.assertRaises(ImportDataError):
+            Float.import_data(['float'], [['foo']])
 
     @with_transaction()
     def test_numeric(self):
@@ -208,77 +104,20 @@
         pool = Pool()
         Numeric = pool.get('test.import_data.numeric')
 
-        self.assertEqual(Numeric.import_data(['numeric'],
-            [['1.1']]), 1)
-
-        self.assertEqual(Numeric.import_data(['numeric'],
-            [[Decimal('0.0')]]), 1)
-
-        self.assertEqual(Numeric.import_data(['numeric'],
-            [[Decimal('1.1')]]), 1)
-
-        self.assertEqual(Numeric.import_data(['numeric'],
-            [['-1.1']]), 1)
-
-        self.assertEqual(Numeric.import_data(['numeric'],
-            [['1']]), 1)
-
-        self.assertEqual(Numeric.import_data(['numeric'],
-            [['']]), 1)
-
-        self.assertEqual(Numeric.import_data(['numeric'],
-            [['1.1'], ['2.2']]), 2)
-
-        self.assertRaises(InvalidOperation, Numeric.import_data,
-            ['numeric'], [['foo']])
-
-        self.assertEqual(Numeric.import_data(['numeric'],
-            [['0']]), 1)
-
-        self.assertEqual(Numeric.import_data(['numeric'],
-            [['0.0']]), 1)
-
-        self.assertEqual(Numeric.import_data(['numeric'],
-            [[None]]), 1)
+        for value in [
+                '1.1', Decimal('1.1'), '-1.1', '1',
+                Decimal('0.0'), '0', '0.0', '', None]:
+            with self.subTest(value=value):
+                self.assertEqual(
+                    Numeric.import_data(['numeric'], [[value]]), 1)
 
     @with_transaction()
-    def test_numeric_required(self):
-        'Test required numeric'
+    def test_numeric_invalid(self):
         pool = Pool()
-        NumericRequired = pool.get('test.import_data.numeric_required')
-        transaction = Transaction()
-
-        self.assertEqual(NumericRequired.import_data(['numeric'],
-            [['1.1']]), 1)
-
-        self.assertEqual(NumericRequired.import_data(['numeric'],
-            [['-1.1']]), 1)
-
-        self.assertEqual(NumericRequired.import_data(['numeric'],
-            [['1']]), 1)
-
-        with self.assertRaises(RequiredValidationError):
-            NumericRequired.import_data(['numeric'], [['']])
-        transaction.rollback()
+        Numeric = pool.get('test.import_data.numeric')
 
-        self.assertEqual(NumericRequired.import_data(['numeric'],
-            [['1.1'], ['2.2']]), 2)
-
-        self.assertRaises(InvalidOperation,
-            NumericRequired.import_data, ['numeric'], [['foo']])
-
-        self.assertEqual(NumericRequired.import_data(['numeric'],
-            [['0']]), 1)
-
-        self.assertEqual(NumericRequired.import_data(['numeric'],
-            [['0.0']]), 1)
-
-        self.assertEqual(NumericRequired.import_data(['numeric'],
-            [[Decimal('0.0')]]), 1)
-
-        with self.assertRaises(RequiredValidationError):
-            NumericRequired.import_data(['numeric'], [[None]])
-        transaction.rollback()
+        with self.assertRaises(ImportDataError):
+            Numeric.import_data(['numeric'], [['foo']])
 
     @with_transaction()
     def test_char(self):
@@ -286,14 +125,19 @@
         pool = Pool()
         Char = pool.get('test.import_data.char')
 
-        self.assertEqual(Char.import_data(['char'],
-            [['test']]), 1)
+        for value in ['test', '']:
+            with self.subTest(value=value):
+                self.assertEqual(
+                    Char.import_data(['char'], [[value]]), 1)
 
-        self.assertEqual(Char.import_data(['char'],
-            [['']]), 1)
+    @with_transaction()
+    def test_char_many_rows(self):
+        "Test char many rows"
+        pool = Pool()
+        Char = pool.get('test.import_data.char')
 
-        self.assertEqual(Char.import_data(['char'],
-            [['test'], ['foo'], ['bar']]), 3)
+        self.assertEqual(
+            Char.import_data(['char'], [['test'], ['foo'], ['bar']]), 3)
 
     @with_transaction()
     def test_text(self):
@@ -301,14 +145,19 @@
         pool = Pool()
         Text = pool.get('test.import_data.text')
 
-        self.assertEqual(Text.import_data(['text'],
-            [['test']]), 1)
+        for value in ['test', '']:
+            with self.subTest(value=value):
+                self.assertEqual(
+                    Text.import_data(['text'], [[value]]), 1)
 
-        self.assertEqual(Text.import_data(['text'],
-            [['']]), 1)
+    @with_transaction()
+    def test_text_many_rows(self):
+        "Test text many rows"
+        pool = Pool()
+        Text = pool.get('test.import_data.text')
 
-        self.assertEqual(Text.import_data(['text'],
-            [['test'], ['foo'], ['bar']]), 3)
+        self.assertEqual(
+            Text.import_data(['text'], [['test'], ['foo'], ['bar']]), 3)
 
     @with_transaction()
     def test_date(self):
@@ -316,20 +165,28 @@
         pool = Pool()
         Date = pool.get('test.import_data.date')
 
-        self.assertEqual(Date.import_data(['date'],
-            [['2010-01-01']]), 1)
+        for value in ['2010-01-01', dt.date(2019, 3, 13), '']:
+            with self.subTest(value=value):
+                self.assertEqual(
+                    Date.import_data(['date'], [[value]]), 1)
 
-        self.assertEqual(Date.import_data(['date'],
-            [[datetime.date(2019, 3, 13)]]), 1)
+    @with_transaction()
+    def test_date_many_rows(self):
+        "Test date many rows"
+        pool = Pool()
+        Date = pool.get('test.import_data.date')
 
-        self.assertEqual(Date.import_data(['date'],
-            [['']]), 1)
+        self.assertEqual(
+            Date.import_data(['date'], [['2010-01-01'], ['2010-02-01']]), 2)
 
-        self.assertEqual(Date.import_data(['date'],
-            [['2010-01-01'], ['2010-02-01']]), 2)
+    @with_transaction()
+    def test_date_invalid(self):
+        "Test date invalid value"
+        pool = Pool()
+        Date = pool.get('test.import_data.date')
 
-        self.assertRaises(ValueError, Date.import_data,
-            ['date'], [['foo']])
+        with self.assertRaises(ImportDataError):
+            Date.import_data(['date'], [['foo']])
 
     @with_transaction()
     def test_datetime(self):
@@ -337,20 +194,31 @@
         pool = Pool()
         Datetime = pool.get('test.import_data.datetime')
 
-        self.assertEqual(Datetime.import_data(['datetime'],
-            [['2010-01-01 12:00:00']]), 1)
+        for value in [
+                '2010-01-01 12:00:00', dt.datetime(2019, 3, 13, 12, 0), '']:
+            with self.subTest(value=value):
+                self.assertEqual(
+                    Datetime.import_data(['datetime'], [[value]]), 1)
 
-        self.assertEqual(Datetime.import_data(['datetime'],
-            [[datetime.datetime(2019, 3, 13, 12, 0)]]), 1)
+    @with_transaction()
+    def test_datetime_many_rows(self):
+        "Test datetime many rows"
+        pool = Pool()
+        Datetime = pool.get('test.import_data.datetime')
 
-        self.assertEqual(Datetime.import_data(['datetime'],
-            [['']]), 1)
+        self.assertEqual(
+            Datetime.import_data(
+                ['datetime'], [
+                    ['2010-01-01 12:00:00'], ['2010-01-01 13:30:00']]), 2)
 
-        self.assertEqual(Datetime.import_data(['datetime'],
-            [['2010-01-01 12:00:00'], ['2010-01-01 13:30:00']]), 2)
+    @with_transaction()
+    def test_datetime_invalid(self):
+        "Test datetime invalid value"
+        pool = Pool()
+        Datetime = pool.get('test.import_data.datetime')
 
-        self.assertRaises(ValueError, Datetime.import_data,
-            ['datetime'], [['foo']])
+        with self.assertRaises(ImportDataError):
+            Datetime.import_data(['datetime'], [['foo']])
 
     @with_transaction()
     def test_timedelta(self):
@@ -358,30 +226,23 @@
         pool = Pool()
         Timedelta = pool.get('test.import_data.timedelta')
 
-        self.assertEqual(Timedelta.import_data(['timedelta'],
-            [['00:00']]), 1)
-
-        self.assertEqual(Timedelta.import_data(['timedelta'],
-            [['0:00:00']]), 1)
-
-        self.assertEqual(Timedelta.import_data(['timedelta'],
-            [['01:00:00']]), 1)
-
-        self.assertEqual(Timedelta.import_data(['timedelta'],
-            [['36:00:00']]), 1)
+        for value in [
+                '00:00', '0:00:00', '01:00:00', '36:00:00', '0:00:00.0001',
+                dt.timedelta(
+                    weeks=2, days=3, hours=8, minutes=50, seconds=30.45),
+                30.45]:
+            with self.subTest(value=value):
+                self.assertEqual(
+                    Timedelta.import_data(['timedelta'], [[value]]), 1)
 
-        self.assertEqual(Timedelta.import_data(['timedelta'],
-            [['0:00:00.0001']]), 1)
+    @with_transaction()
+    def test_timedelta_invalid(self):
+        'Test timedelta'
+        pool = Pool()
+        Timedelta = pool.get('test.import_data.timedelta')
 
-        self.assertEqual(Timedelta.import_data(['timedelta'],
-            [[datetime.timedelta(
-                weeks=2, days=3, hours=8, minutes=50, seconds=30.45)]]), 1)
-
-        self.assertEqual(Timedelta.import_data(['timedelta'],
-            [[30.45]]), 1)
-
-        self.assertRaises(ValueError, Timedelta.import_data,
-            ['timedelta'], [['foo']])
+        with self.assertRaises(ImportDataError):
+            Timedelta.import_data(['timedelta'], [['foo']])
 
     @with_transaction()
     def test_selection(self):
@@ -389,100 +250,120 @@
         pool = Pool()
         Selection = pool.get('test.import_data.selection')
 
-        self.assertEqual(Selection.import_data(['selection'],
-            [['select1']]), 1)
-
-        self.assertEqual(Selection.import_data(['selection'],
-            [['']]), 1)
+        for value in ['select1', '']:
+            with self.subTest(value=value):
+                self.assertEqual(
+                    Selection.import_data(['selection'], [[value]]), 1)
 
-        self.assertEqual(Selection.import_data(['selection'],
-            [['select1'], ['select2']]), 2)
+    @with_transaction()
+    def test_selection_many_rows(self):
+        'Test selection many rows'
+        pool = Pool()
+        Selection = pool.get('test.import_data.selection')
 
-        with self.assertRaises(SelectionValidationError):
-            Selection.import_data(['selection'], [['foo']])
+        self.assertEqual(
+            Selection.import_data(['selection'], [['select1'], ['select2']]),
+            2)
 
     @with_transaction()
     def test_many2one(self):
         'Test many2one'
         pool = Pool()
         Many2one = pool.get('test.import_data.many2one')
-        transaction = Transaction()
 
-        self.assertEqual(Many2one.import_data(['many2one'],
-            [['Test']]), 1)
+        for value in ['Test', '']:
+            with self.subTest(value=value):
+                self.assertEqual(
+                    Many2one.import_data(['many2one'], [[value]]), 1)
 
-        self.assertEqual(Many2one.import_data(['many2one:id'],
-            [['tests.import_data_many2one_target_test']]), 1)
+    @with_transaction()
+    def test_many2one_id(self):
+        "Test many2one with id"
+        pool = Pool()
+        Many2one = pool.get('test.import_data.many2one')
 
-        self.assertEqual(Many2one.import_data(['many2one'],
-            [['']]), 1)
+        self.assertEqual(
+            Many2one.import_data(
+                ['many2one:id'], [['tests.import_data_many2one_target_test']]),
+            1)
 
-        self.assertEqual(Many2one.import_data(['many2one'],
-            [['Test'], ['Test']]), 2)
+    @with_transaction()
+    def test_many2one_many_rows(self):
+        "Test many2one many rows"
+        pool = Pool()
+        Many2one = pool.get('test.import_data.many2one')
 
-        with self.assertRaises(ImportDataError):
-            Many2one.import_data(['many2one'], [['foo']])
-        transaction.rollback()
+        self.assertEqual(
+            Many2one.import_data(['many2one'], [['Test'], ['Test']]), 2)
 
-        with self.assertRaises(ImportDataError):
-            Many2one.import_data(['many2one'], [['Duplicate']])
-        transaction.rollback()
+    @with_transaction()
+    def test_many2one_invalid(self):
+        "Test many2one invalid value"
+        pool = Pool()
+        Many2one = pool.get('test.import_data.many2one')
 
-        with self.assertRaises(ImportDataError):
-            Many2one.import_data(['many2one:id'], [['foo']])
-        transaction.rollback()
+        for value in ['foo', 'Duplicate']:
+            with self.subTest(value=value):
+                with self.assertRaises(ImportDataError):
+                    Many2one.import_data(['many2one'], [[value]])
 
-        with self.assertRaises(KeyError):
-            Many2one.import_data(['many2one:id'], [['tests.foo']])
-        transaction.rollback()
+    @with_transaction()
+    def test_many2one_id_invalid(self):
+        "Test many2one invalid id"
+        pool = Pool()
+        Many2one = pool.get('test.import_data.many2one')
+
+        for value in ['foo', 'tests.foo']:
+            with self.subTest(value=value):
+                with self.assertRaises(ImportDataError):
+                    Many2one.import_data(['many2one:id'], [[value]])
 
     @with_transaction()
     def test_many2many(self):
         'Test many2many'
         pool = Pool()
         Many2many = pool.get('test.import_data.many2many')
-        transaction = Transaction()
-
-        self.assertEqual(Many2many.import_data(['many2many'],
-            [['Test 1']]), 1)
-
-        self.assertEqual(Many2many.import_data(['many2many:id'],
-            [['tests.import_data_many2many_target_test1']]), 1)
 
-        self.assertEqual(Many2many.import_data(['many2many'],
-            [['Test 1,Test 2']]), 1)
+        for value in [
+                'Test 1', 'Test\\, comma', 'Test\\, comma,Test 1',
+                'Test 1,Test 2', '']:
+            with self.subTest(value=value):
+                self.assertEqual(
+                    Many2many.import_data(['many2many'], [[value]]), 1)
 
-        self.assertEqual(Many2many.import_data(['many2many:id'],
-            [['tests.import_data_many2many_target_test1,'
-                'tests.import_data_many2many_target_test2']]), 1)
-
-        self.assertEqual(Many2many.import_data(['many2many'],
-            [['Test\\, comma']]), 1)
-
-        self.assertEqual(Many2many.import_data(['many2many'],
-            [['Test\\, comma,Test 1']]), 1)
+    @with_transaction()
+    def test_many2many_id(self):
+        "Test many2many with id"
+        pool = Pool()
+        Many2many = pool.get('test.import_data.many2many')
 
-        self.assertEqual(Many2many.import_data(['many2many'],
-            [['']]), 1)
+        for value in [
+                'tests.import_data_many2many_target_test1',
+                'tests.import_data_many2many_target_test1,'
+                'tests.import_data_many2many_target_test2']:
+            with self.subTest(value=value):
+                self.assertEqual(
+                    Many2many.import_data(['many2many:id'], [[value]]), 1)
 
-        self.assertEqual(Many2many.import_data(['many2many'],
-            [['Test 1'], ['Test 2']]), 2)
-
-        with self.assertRaises(ImportDataError):
-            Many2many.import_data(['many2many'], [['foo']])
-        transaction.rollback()
+    @with_transaction()
+    def test_many2many_many_rows(self):
+        "Test many2many many rows"
+        pool = Pool()
+        Many2many = pool.get('test.import_data.many2many')
 
-        with self.assertRaises(ImportDataError):
-            Many2many.import_data(['many2many'], [['Test 1,foo']])
-        transaction.rollback()
+        self.assertEqual(
+            Many2many.import_data(['many2many'], [['Test 1'], ['Test 2']]), 2)
 
-        with self.assertRaises(ImportDataError):
-            Many2many.import_data(['many2many'], [['Duplicate']])
-        transaction.rollback()
+    @with_transaction()
+    def test_many2many_invalid(self):
+        "Test many2many invalid value"
+        pool = Pool()
+        Many2many = pool.get('test.import_data.many2many')
 
-        with self.assertRaises(ImportDataError):
-            Many2many.import_data(['many2many'], [['Test 1,Duplicate']])
-        transaction.rollback()
+        for value in ['foo', 'Test 1,foo', 'Duplicate', 'Test 1,Duplicate']:
+            with self.subTest(value=value):
+                with self.assertRaises(ImportDataError):
+                    Many2many.import_data(['many2many'], [[value]])
 
     @with_transaction()
     def test_one2many(self):
@@ -493,10 +374,22 @@
         self.assertEqual(One2many.import_data(
                 ['name', 'one2many/name'], [['Test', 'Test 1']]), 1)
 
+    @with_transaction()
+    def test_one2many_many_targets(self):
+        "Test one2many with many targets"
+        pool = Pool()
+        One2many = pool.get('test.import_data.one2many')
+
         self.assertEqual(One2many.import_data(
                 ['name', 'one2many/name'],
                 [['Test', 'Test 1'], ['', 'Test 2']]), 1)
 
+    @with_transaction()
+    def test_one2many_many_rows(self):
+        "Test one2many many rows"
+        pool = Pool()
+        One2many = pool.get('test.import_data.one2many')
+
         self.assertEqual(One2many.import_data(
                 ['name', 'one2many/name'],
                 [
@@ -509,14 +402,18 @@
         'Test reference'
         pool = Pool()
         Reference = pool.get('test.import_data.reference')
-        transaction = Transaction()
 
         self.assertEqual(Reference.import_data(['reference'],
             [['test.import_data.reference.selection,Test']]), 1)
         reference, = Reference.search([])
         self.assertEqual(reference.reference.__name__,
             'test.import_data.reference.selection')
-        transaction.rollback()
+
+    @with_transaction()
+    def test_reference_id(self):
+        "Test reference with id"
+        pool = Pool()
+        Reference = pool.get('test.import_data.reference')
 
         self.assertEqual(Reference.import_data(['reference:id'],
             [['test.import_data.reference.selection,'
@@ -524,13 +421,23 @@
         reference, = Reference.search([])
         self.assertEqual(reference.reference.__name__,
             'test.import_data.reference.selection')
-        transaction.rollback()
+
+    @with_transaction()
+    def test_reference_empty(self):
+        "Test reference empty"
+        pool = Pool()
+        Reference = pool.get('test.import_data.reference')
 
         self.assertEqual(Reference.import_data(['reference'],
             [['']]), 1)
         reference, = Reference.search([])
         self.assertEqual(reference.reference, None)
-        transaction.rollback()
+
+    @with_transaction()
+    def test_reference_many_rows(self):
+        "Test reference many rows"
+        pool = Pool()
+        Reference = pool.get('test.import_data.reference')
 
         self.assertEqual(Reference.import_data(['reference'],
             [['test.import_data.reference.selection,Test'],
@@ -538,30 +445,31 @@
         for reference in Reference.search([]):
             self.assertEqual(reference.reference.__name__,
                 'test.import_data.reference.selection')
-        transaction.rollback()
+
+    @with_transaction()
+    def test_reference_invalid(self):
+        "Test reference invalid value"
+        pool = Pool()
+        Reference = pool.get('test.import_data.reference')
 
-        with self.assertRaises(ImportDataError):
-            Reference.import_data(
-                ['reference'], [['test.import_data.reference.selection,foo']])
-        transaction.rollback()
+        for value in [
+                'test.import_data.reference.selection,foo',
+                'test.import_data.reference.selection,Duplicate',
+                'test.import_data.reference.selection,test.foo']:
+            with self.subTest(value=value):
+                with self.assertRaises(ImportDataError):
+                    Reference.import_data(['reference'], [[value]])
 
-        with self.assertRaises(ImportDataError):
-            Reference.import_data(
-                ['reference'],
-                [['test.import_data.reference.selection,Duplicate']])
-            transaction.rollback()
+    @with_transaction()
+    def test_reference_id_invalid(self):
+        "Test reference invalid id"
+        pool = Pool()
+        Reference = pool.get('test.import_data.reference')
 
         with self.assertRaises(ImportDataError):
             Reference.import_data(
                 ['reference:id'],
                 [['test.import_data.reference.selection,foo']])
-        transaction.rollback()
-
-        with self.assertRaises(KeyError):
-            Reference.import_data(
-                ['reference:id'],
-                [['test.import_data.reference.selection,test.foo']])
-        transaction.rollback()
 
     @with_transaction()
     def test_binary_bytes(self):

Reply via email to