diff --git a/addons/base_import/models.py b/addons/base_import/models.py index 754467ed1c2..07a3439419d 100644 --- a/addons/base_import/models.py +++ b/addons/base_import/models.py @@ -24,6 +24,7 @@ class ir_import(orm.TransientModel): 'res_model': fields.char('Model', size=64), 'file': fields.binary('File'), 'file_name': fields.char('File Name', size=None), + 'file_mime': fields.char('File Type', size=None), } def get_fields(self, cr, uid, model, context=None, @@ -174,19 +175,20 @@ class ir_import(orm.TransientModel): Will consume the first line of the ``rows`` iterator. - Returns either None (no title) or a dict mapping cell indices + Returns a pair of (None, None) if headers were not requested + or the list of headers and a dict mapping cell indices to key paths in the ``fields`` tree :param Iterator rows: :param dict fields: :param dict options: - :rtype: None | dict(int: list(str)) + :rtype: (None, None) | (list(str), dict(int: list(str))) """ if not options.get('headers'): - return None + return None, None headers = next(rows) - return dict( + return headers, dict( (index, [field['name'] for field in self._match_header(header, fields, options)] or None) for index, header in enumerate(headers) ) @@ -196,13 +198,16 @@ class ir_import(orm.TransientModel): fields-matching between the import's file data and the model's columns. + If the headers are not requested (not options.headers), + ``matches`` and ``headers`` are both ``False``. + :param id: identifier of the import :param int count: number of preview lines to generate :param options: format-specific options. CSV: {encoding, quote, separator, headers} :type options: {str, str, str, bool} - :returns: {fields, matches, preview} | {error, preview} - :rtype: {dict(str: dict(...)), dict(int, list(str)), list(list(str))} | {str, str} + :returns: {fields, matches, headers, preview} | {error, preview} + :rtype: {dict(str: dict(...)), dict(int, list(str)), list(str), list(list(str))} | {str, str} """ (record,) = self.browse(cr, uid, [id], context=context) fields = self.get_fields(cr, uid, record.res_model, context=context) @@ -210,13 +215,14 @@ class ir_import(orm.TransientModel): try: rows = self._read_csv(record, options) - match = self._match_headers(rows, fields, options) + headers, matches = self._match_headers(rows, fields, options) # Match should have consumed the first row (iif headers), get # the ``count`` next rows for preview preview = itertools.islice(rows, count) return { 'fields': fields, - 'matches': match, + 'matches': matches or False, + 'headers': headers or False, 'preview': list(preview), } except (TypeError, UnicodeDecodeError), e: diff --git a/addons/base_import/tests/test_cases.py b/addons/base_import/tests/test_cases.py index 7cd7cbc5d00..00230f9a3c0 100644 --- a/addons/base_import/tests/test_cases.py +++ b/addons/base_import/tests/test_cases.py @@ -127,9 +127,11 @@ class test_match_headers_single(TransactionCase): class test_match_headers_multiple(TransactionCase): def test_noheaders(self): - self.assertIsNone( + self.assertEqual( self.registry('base_import.import')._match_headers( - [], [], {})) + [], [], {}), + (None, None) + ) def test_nomatch(self): self.assertEqual( self.registry('base_import.import')._match_headers( @@ -139,7 +141,11 @@ class test_match_headers_multiple(TransactionCase): ]), [], {'headers': True}), - dict((index, None) for index in range(4))) + ( + ['foo', 'bar', 'baz', 'qux'], + dict.fromkeys(range(4)) + ) + ) def test_mixed(self): self.assertEqual( @@ -153,12 +159,13 @@ class test_match_headers_multiple(TransactionCase): ]} ], {'headers': True}), - { + (['foo', 'bar', 'baz', 'qux/corge'], { 0: None, 1: ['bar'], 2: ['bob'], 3: ['qux', 'corge'], }) + ) import base64, csv class test_preview(TransactionCase): @@ -216,27 +223,22 @@ class test_preview(TransactionCase): 'headers': True, }) - p = { - 'matches': {0: ['name'], 1: ['somevalue'], 2: None}, - 'fields': [ - {'id': 'id', 'name': 'id', 'string': 'External ID', 'required':False, 'fields': []}, - {'id': 'name', 'name': 'name', 'string': 'Name', 'required':False, 'fields': []}, - {'id': 'somevalue', 'name': 'somevalue', 'string': 'Some Value', 'required':True, 'fields': []}, - {'id': 'othervalue', 'name': 'othervalue', 'string': 'Other Variable', 'required':False, 'fields': []}, - ], - 'preview': [ - ['foo', '1', '2'], - ['bar', '3', '4'], - ['qux', '5', '6'], - ] - } - - self.assertEqual(result['matches'], p['matches']) + self.assertEqual(result['matches'], {0: ['name'], 1: ['somevalue'], 2: None}) + self.assertEqual(result['headers'], ['name', 'Some Value', 'Counter']) # Order depends on iteration order of fields_get - self.assertItemsEqual(result['fields'], p['fields']) - self.assertEqual(result['preview'], p['preview']) + self.assertItemsEqual(result['fields'], [ + {'id': 'id', 'name': 'id', 'string': 'External ID', 'required':False, 'fields': []}, + {'id': 'name', 'name': 'name', 'string': 'Name', 'required':False, 'fields': []}, + {'id': 'somevalue', 'name': 'somevalue', 'string': 'Some Value', 'required':True, 'fields': []}, + {'id': 'othervalue', 'name': 'othervalue', 'string': 'Other Variable', 'required':False, 'fields': []}, + ]) + self.assertEqual(result['preview'], [ + ['foo', '1', '2'], + ['bar', '3', '4'], + ['qux', '5', '6'], + ]) # Ensure we only have the response fields we expect - self.assertItemsEqual(result.keys(), ['matches', 'fields', 'preview']) + self.assertItemsEqual(result.keys(), ['matches', 'headers', 'fields', 'preview']) class test_convert_import_data(TransactionCase): """ Tests conversion of base_import.import input into data which