1 # -*- coding: utf-8 -*-
2 import openerp.modules.registry
5 from openerp.tests import common
6 from openerp.tools.misc import mute_logger
9 """ Successful import of ``n`` records
11 :param int n: number of records which should have been imported
15 def error(row, message, record=None, **kwargs):
16 """ Failed import of the record ``record`` at line ``row``, with the error
23 -1, dict(record or {}, **kwargs),
24 "Line %d : %s" % (row, message),
27 def values(seq, field='value'):
28 return [item[field] for item in seq]
30 class ImporterCase(common.TransactionCase):
33 def __init__(self, *args, **kwargs):
34 super(ImporterCase, self).__init__(*args, **kwargs)
38 super(ImporterCase, self).setUp()
39 self.model = self.registry(self.model_name)
41 def import_(self, fields, rows, context=None):
42 return self.model.import_data(
43 self.cr, openerp.SUPERUSER_ID, fields, rows, context=context)
44 def read(self, fields=('value',), domain=(), context=None):
45 return self.model.read(
46 self.cr, openerp.SUPERUSER_ID,
47 self.model.search(self.cr, openerp.SUPERUSER_ID, domain, context=context),
48 fields=fields, context=context)
49 def browse(self, domain=(), context=None):
50 return self.model.browse(
51 self.cr, openerp.SUPERUSER_ID,
52 self.model.search(self.cr, openerp.SUPERUSER_ID, domain, context=context),
55 def xid(self, record):
56 ModelData = self.registry('ir.model.data')
58 ids = ModelData.search(
59 self.cr, openerp.SUPERUSER_ID,
60 [('model', '=', record._table_name), ('res_id', '=', record.id)])
63 self.cr, openerp.SUPERUSER_ID, ids, ['name', 'module'])[0]
65 return '%s.%s' % (d['module'], d['name'])
68 name = dict(record.name_get())[record.id]
69 # fix dotted name_get results, otherwise xid lookups blow up
70 name = name.replace('.', '-')
71 ModelData.create(self.cr, openerp.SUPERUSER_ID, {
73 'model': record._table_name,
77 return '__test__.' + name
79 class test_ids_stuff(ImporterCase):
80 model_name = 'export.integer'
82 def test_create_with_id(self):
84 self.import_(['.id', 'value'], [['42', '36']]),
85 error(1, u"Unknown database identifier '42'"))
86 def test_create_with_xid(self):
88 self.import_(['id', 'value'], [['somexmlid', '42']]),
92 self.xid(self.browse()[0]))
94 def test_update_with_id(self):
95 id = self.model.create(self.cr, openerp.SUPERUSER_ID, {'value': 36})
98 self.model.browse(self.cr, openerp.SUPERUSER_ID, id).value)
101 self.import_(['.id', 'value'], [[str(id), '42']]),
104 [42], # updated value to imported
107 def test_update_with_xid(self):
108 self.import_(['id', 'value'], [['somexmlid', '36']])
109 self.assertEqual([36], values(self.read()))
111 self.import_(['id', 'value'], [['somexmlid', '1234567']])
112 self.assertEqual([1234567], values(self.read()))
114 class test_boolean_field(ImporterCase):
115 model_name = 'export.boolean'
117 def test_empty(self):
119 self.import_(['value'], []),
122 def test_exported(self):
124 self.import_(['value'], [
129 records = self.read()
135 def test_falses(self):
137 self.import_(['value'], [
154 def test_trues(self):
156 self.import_(['value'], [
163 # Problem: OpenOffice (and probably excel) output localized booleans
172 class test_integer_field(ImporterCase):
173 model_name = 'export.integer'
177 self.import_(['value'], []),
180 def test_empty(self):
182 self.import_(['value'], [['']]),
190 self.import_(['value'], [['0']]),
193 self.import_(['value'], [['-0']]),
195 self.assertEqual([False, False], values(self.read()))
197 def test_positives(self):
199 self.import_(['value'], [
207 1, 42, 2**31-1, 12345678
208 ], values(self.read()))
210 def test_negatives(self):
212 self.import_(['value'], [
221 -1, -42, -(2**31 - 1), -(2**31), -12345678
222 ], values(self.read()))
224 @mute_logger('openerp.sql_db')
225 def test_out_of_range(self):
227 self.import_(['value'], [[str(2**31)]]),
228 error(1, "integer out of range\n"))
229 # auto-rollbacks if error is in process_liness, but not during
230 # ir.model.data write. Can differentiate because former ends lines
231 # error lines with "!"
234 self.import_(['value'], [[str(-2**32)]]),
235 error(1, "integer out of range\n"))
238 def test_nonsense(self):
240 self.import_(['value'], [['zorglub']]),
241 error(1, u"'zorglub' does not seem to be an integer for field 'unknown'"))
243 class test_float_field(ImporterCase):
244 model_name = 'export.float'
247 self.import_(['value'], []),
250 def test_empty(self):
252 self.import_(['value'], [['']]),
260 self.import_(['value'], [['0']]),
263 self.import_(['value'], [['-0']]),
265 self.assertEqual([False, False], values(self.read()))
267 def test_positives(self):
269 self.import_(['value'], [
279 1, 42, 2**31-1, 12345678, 2.0**33, .000001
280 ], values(self.read()))
282 def test_negatives(self):
284 self.import_(['value'], [
295 -1, -42, -(2**31 - 1), -(2**31), -12345678, -2.0**33, -.000001
296 ], values(self.read()))
298 def test_nonsense(self):
300 self.import_(['value'], [['foobar']]),
301 error(1, u"'foobar' does not seem to be a number for field 'unknown'"))
303 class test_string_field(ImporterCase):
304 model_name = 'export.string.bounded'
306 def test_empty(self):
308 self.import_(['value'], [['']]),
310 self.assertEqual([False], values(self.read()))
312 def test_imported(self):
314 self.import_(['value'], [
317 [u'Með suð à eyrum við spilum endalaust'],
318 [u"People 'get' types. They use them all the time. Telling "
319 u"someone he can't pound a nail with a banana doesn't much "
326 u"Með suð à eyrum ",
328 ], values(self.read()))
330 class test_unbound_string_field(ImporterCase):
331 model_name = 'export.string'
333 def test_imported(self):
335 self.import_(['value'], [
336 [u'à dag viðrar vel til loftárása'],
338 [u"If they ask you about fun, you tell them – fun is a filthy"
343 u"à dag viðrar vel til loftárása",
344 u"If they ask you about fun, you tell them – fun is a filthy parasite"
345 ], values(self.read()))
347 class test_text(ImporterCase):
348 model_name = 'export.text'
350 def test_empty(self):
352 self.import_(['value'], [['']]),
354 self.assertEqual([False], values(self.read()))
356 def test_imported(self):
357 s = (u"BreiðskÃfa er notað um útgefna hljómplötu sem inniheldur "
358 u"stúdÃóupptökur frá einum flytjanda. BreiðskÃfur eru oftast "
359 u"milli 25-80 mÃnútur og er lengd þeirra oft miðuð við 33â…“ "
360 u"snúninga 12 tommu vÃnylplötur (sem geta verið allt að 30 mÃn "
361 u"hvor hlið).\n\nBreiðskÃfur eru stundum tvöfaldar og eru þær þá"
362 u" gefnar út á tveimur geisladiskum eða tveimur vÃnylplötum.")
364 self.import_(['value'], [[s]]),
366 self.assertEqual([s], values(self.read()))
368 class test_selection(ImporterCase):
369 model_name = 'export.selection'
376 def test_imported(self):
378 self.import_(['value'], [
385 self.assertEqual([3, 2, 1, 2], values(self.read()))
387 def test_imported_translated(self):
388 self.registry('res.lang').create(self.cr, openerp.SUPERUSER_ID, {
389 'name': u'Français',
391 'translatable': True,
392 'date_format': '%d.%m.%Y',
393 'decimal_point': ',',
394 'thousands_sep': ' ',
396 Translations = self.registry('ir.translation')
397 for source, value in self.translations_fr:
398 Translations.create(self.cr, openerp.SUPERUSER_ID, {
399 'name': 'export.selection,value',
407 self.import_(['value'], [
411 ], context={'lang': 'fr_FR'}),
413 self.assertEqual([3, 1, 2], values(self.read()))
415 self.import_(['value'], [['Foo']], context={'lang': 'fr_FR'}),
418 def test_invalid(self):
420 self.import_(['value'], [['Baz']]),
421 error(1, u"Value 'Baz' not found in selection field 'unknown'"))
424 self.import_(['value'], [[42]]),
425 error(1, u"Value '42' not found in selection field 'unknown'"))
427 class test_selection_function(ImporterCase):
428 model_name = 'export.selection.function'
436 def test_imported(self):
437 """ import uses fields_get, so translates import label (may or may not
438 be good news) *and* serializes the selection function to reverse it:
439 import does not actually know that the selection field uses a function
441 # NOTE: conflict between a value and a label => ?
443 self.import_(['value'], [
452 def test_translated(self):
453 """ Expects output of selection function returns translated labels
455 self.registry('res.lang').create(self.cr, openerp.SUPERUSER_ID, {
456 'name': u'Français',
458 'translatable': True,
459 'date_format': '%d.%m.%Y',
460 'decimal_point': ',',
461 'thousands_sep': ' ',
463 Translations = self.registry('ir.translation')
464 for source, value in self.translations_fr:
465 Translations.create(self.cr, openerp.SUPERUSER_ID, {
466 'name': 'export.selection,value',
473 self.import_(['value'], [
476 ], context={'lang': 'fr_FR'}),
479 self.import_(['value'], [['Wheee']], context={'lang': 'fr_FR'}),
482 class test_m2o(ImporterCase):
483 model_name = 'export.many2one'
485 def test_by_name(self):
486 # create integer objects
487 integer_id1 = self.registry('export.integer').create(
488 self.cr, openerp.SUPERUSER_ID, {'value': 42})
489 integer_id2 = self.registry('export.integer').create(
490 self.cr, openerp.SUPERUSER_ID, {'value': 36})
492 name1 = dict(self.registry('export.integer').name_get(
493 self.cr, openerp.SUPERUSER_ID,[integer_id1]))[integer_id1]
494 name2 = dict(self.registry('export.integer').name_get(
495 self.cr, openerp.SUPERUSER_ID,[integer_id2]))[integer_id2]
498 self.import_(['value'], [
505 # correct ids assigned to corresponding records
507 (integer_id1, name1),
508 (integer_id1, name1),
509 (integer_id2, name2),],
512 def test_by_xid(self):
513 ExportInteger = self.registry('export.integer')
514 integer_id = ExportInteger.create(
515 self.cr, openerp.SUPERUSER_ID, {'value': 42})
516 xid = self.xid(ExportInteger.browse(
517 self.cr, openerp.SUPERUSER_ID, [integer_id])[0])
520 self.import_(['value/id'], [[xid]]),
523 self.assertEqual(42, b[0].value.value)
525 def test_by_id(self):
526 integer_id = self.registry('export.integer').create(
527 self.cr, openerp.SUPERUSER_ID, {'value': 42})
529 self.import_(['value/.id'], [[integer_id]]),
532 self.assertEqual(42, b[0].value.value)
534 def test_by_names(self):
535 integer_id1 = self.registry('export.integer').create(
536 self.cr, openerp.SUPERUSER_ID, {'value': 42})
537 integer_id2 = self.registry('export.integer').create(
538 self.cr, openerp.SUPERUSER_ID, {'value': 42})
539 name1 = dict(self.registry('export.integer').name_get(
540 self.cr, openerp.SUPERUSER_ID,[integer_id1]))[integer_id1]
541 name2 = dict(self.registry('export.integer').name_get(
542 self.cr, openerp.SUPERUSER_ID,[integer_id2]))[integer_id2]
543 # names should be the same
544 self.assertEqual(name1, name2)
547 self.import_(['value'], [[name2]]),
551 ], values(self.read()))
553 def test_fail_by_implicit_id(self):
554 """ Can't implicitly import records by id
556 # create integer objects
557 integer_id1 = self.registry('export.integer').create(
558 self.cr, openerp.SUPERUSER_ID, {'value': 42})
559 integer_id2 = self.registry('export.integer').create(
560 self.cr, openerp.SUPERUSER_ID, {'value': 36})
563 self.import_(['value'], [
564 # import by id, without specifying it
569 error(1, u"No matching record found for name '%s' in field 'unknown'" % integer_id1))
571 def test_sub_field(self):
572 """ Does not implicitly create the record, does not warn that you can't
573 import m2o subfields (at all)...
576 self.import_(['value/value'], [['42']]),
577 error(1, u"Can not create Many-To-One records indirectly, import the field separately"))
579 def test_fail_noids(self):
581 self.import_(['value'], [['nameisnoexist:3']]),
582 error(1, u"No matching record found for name 'nameisnoexist:3' in field 'unknown'"))
585 self.import_(['value/id'], [['noxidhere']]),
586 error(1, u"No matching record found for external id 'noxidhere' in field 'unknown'"))
589 self.import_(['value/.id'], [[66]]),
590 error(1, u"No matching record found for database id '66' in field 'unknown'"))
592 class test_m2m(ImporterCase):
593 model_name = 'export.many2many'
595 # apparently, one and only thing which works is a
596 # csv_internal_sep-separated list of ids, xids, or names (depending if
597 # m2m/.id, m2m/id or m2m[/anythingelse]
599 id1 = self.registry('export.many2many.other').create(
600 self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'})
601 id2 = self.registry('export.many2many.other').create(
602 self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'})
603 id3 = self.registry('export.many2many.other').create(
604 self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'})
605 id4 = self.registry('export.many2many.other').create(
606 self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'})
607 id5 = self.registry('export.many2many.other').create(
608 self.cr, openerp.SUPERUSER_ID, {'value': 99, 'str': 'record4'})
611 self.import_(['value/.id'], [
612 ['%d,%d' % (id1, id2)],
613 ['%d,%d,%d' % (id1, id3, id4)],
614 ['%d,%d,%d' % (id1, id2, id3)],
618 ids = lambda records: [record.id for record in records]
621 self.assertEqual(ids(b[0].value), [id1, id2])
622 self.assertEqual(values(b[0].value), [3, 44])
624 self.assertEqual(ids(b[2].value), [id1, id2, id3])
625 self.assertEqual(values(b[2].value), [3, 44, 84])
627 def test_noids(self):
629 self.import_(['value/.id'], [['42']]),
630 error(1, u"No matching record found for database id '42' in field 'unknown'"))
633 M2O_o = self.registry('export.many2many.other')
634 id1 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'})
635 id2 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'})
636 id3 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'})
637 id4 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'})
638 records = M2O_o.browse(self.cr, openerp.SUPERUSER_ID, [id1, id2, id3, id4])
641 self.import_(['value/id'], [
642 ['%s,%s' % (self.xid(records[0]), self.xid(records[1]))],
643 ['%s' % self.xid(records[3])],
644 ['%s,%s' % (self.xid(records[2]), self.xid(records[1]))],
649 self.assertEqual(values(b[0].value), [3, 44])
650 self.assertEqual(values(b[2].value), [44, 84])
651 def test_noxids(self):
653 self.import_(['value/id'], [['noxidforthat']]),
654 error(1, u"No matching record found for external id 'noxidforthat' in field 'unknown'"))
656 def test_names(self):
657 M2O_o = self.registry('export.many2many.other')
658 id1 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'})
659 id2 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'})
660 id3 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'})
661 id4 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'})
662 records = M2O_o.browse(self.cr, openerp.SUPERUSER_ID, [id1, id2, id3, id4])
664 name = lambda record: dict(record.name_get())[record.id]
667 self.import_(['value'], [
668 ['%s,%s' % (name(records[1]), name(records[2]))],
669 ['%s,%s,%s' % (name(records[0]), name(records[1]), name(records[2]))],
670 ['%s,%s' % (name(records[0]), name(records[3]))],
675 self.assertEqual(values(b[1].value), [3, 44, 84])
676 self.assertEqual(values(b[2].value), [3, 9])
678 def test_nonames(self):
680 self.import_(['value'], [['wherethem2mhavenonames']]),
681 error(1, u"No matching record found for name 'wherethem2mhavenonames' in field 'unknown'"))
683 def test_import_to_existing(self):
684 M2O_o = self.registry('export.many2many.other')
685 id1 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'})
686 id2 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'})
687 id3 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'})
688 id4 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'})
692 self.import_(['id', 'value/.id'], [[xid, '%d,%d' % (id1, id2)]]),
695 self.import_(['id', 'value/.id'], [[xid, '%d,%d' % (id3, id4)]]),
699 self.assertEqual(len(b), 1)
700 # TODO: replacement of existing m2m values is correct?
701 self.assertEqual(values(b[0].value), [84, 9])
703 class test_o2m(ImporterCase):
704 model_name = 'export.one2many'
706 def test_name_get(self):
707 s = u'Java is a DSL for taking large XML files and converting them to' \
713 error(1, u"No matching record found for name '%s' in field 'unknown'" % s))
715 def test_single(self):
717 self.import_(['const', 'value/value'], [
723 self.assertEqual(b.const, 5)
724 self.assertEqual(values(b.value), [63])
726 def test_multicore(self):
728 self.import_(['const', 'value/value'], [
734 b1, b2 = self.browse()
735 self.assertEqual(b1.const, 5)
736 self.assertEqual(values(b1.value), [63])
737 self.assertEqual(b2.const, 6)
738 self.assertEqual(values(b2.value), [64])
740 def test_multisub(self):
742 self.import_(['const', 'value/value'], [
751 self.assertEqual(values(b.value), [63, 64, 65, 66])
753 def test_multi_subfields(self):
755 self.import_(['value/str', 'const', 'value/value'], [
759 ['rhythm', '', '66'],
764 self.assertEqual(values(b.value), [63, 64, 65, 66])
766 values(b.value, 'str'),
767 'this is the rhythm'.split())
769 def test_link_inline(self):
770 id1 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
771 'str': 'Bf', 'value': 109
773 id2 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
774 'str': 'Me', 'value': 262
778 self.import_(['const', 'value/.id'], [
779 ['42', '%d,%d' % (id1, id2)]
781 except ValueError, e:
782 # should be Exception(Database ID doesn't exist: export.one2many.child : $id1,$id2)
783 self.assertIs(type(e), ValueError)
786 "invalid literal for int() with base 10: '%d,%d'" % (id1, id2))
789 id1 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
790 'str': 'Bf', 'value': 109
792 id2 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
793 'str': 'Me', 'value': 262
797 self.import_(['const', 'value/.id'], [
804 self.assertEqual(b.const, 42)
805 # automatically forces link between core record and o2ms
806 self.assertEqual(values(b.value), [109, 262])
807 self.assertEqual(values(b.value, field='parent_id'), [b, b])
809 def test_link_2(self):
810 O2M_c = self.registry('export.one2many.child')
811 id1 = O2M_c.create(self.cr, openerp.SUPERUSER_ID, {
812 'str': 'Bf', 'value': 109
814 id2 = O2M_c.create(self.cr, openerp.SUPERUSER_ID, {
815 'str': 'Me', 'value': 262
819 self.import_(['const', 'value/.id', 'value/value'], [
820 ['42', str(id1), '1'],
826 self.assertEqual(b.const, 42)
827 self.assertEqual(values(b.value), [1, 2])
828 self.assertEqual(values(b.value, field='parent_id'), [b, b])
830 class test_o2m_multiple(ImporterCase):
831 model_name = 'export.one2many.multiple'
833 def test_multi_mixed(self):
835 self.import_(['const', 'child1/value', 'child2/value'], [
844 self.assertEqual(values(b.child1), [11, 12, 13, 14])
845 self.assertEqual(values(b.child2), [21, 22, 23])
847 def test_multi(self):
849 self.import_(['const', 'child1/value', 'child2/value'], [
860 self.assertEqual(values(b.child1), [11, 12, 13, 14])
861 self.assertEqual(values(b.child2), [21, 22, 23])
863 def test_multi_fullsplit(self):
865 self.import_(['const', 'child1/value', 'child2/value'], [
877 self.assertEqual(b.const, 5)
878 self.assertEqual(values(b.child1), [11, 12, 13, 14])
879 self.assertEqual(values(b.child2), [21, 22, 23])
881 # function, related, reference: written to db as-is...
882 # => function uses @type for value coercion/conversion