[MERGE] from trunk
[odoo/odoo.git] / openerp / tests / addons / test_impex / tests / test_import.py
1 # -*- coding: utf-8 -*-
2 import openerp.modules.registry
3 import openerp
4
5 from openerp.tests import common
6 from openerp.tools.misc import mute_logger
7
8 def ok(n):
9     """ Successful import of ``n`` records
10
11     :param int n: number of records which should have been imported
12     """
13     return n, 0, 0, 0
14
15 def error(row, message, record=None, **kwargs):
16     """ Failed import of the record ``record`` at line ``row``, with the error
17     message ``message``
18
19     :param str message:
20     :param dict record:
21     """
22     return (
23         -1, dict(record or {}, **kwargs),
24         "Line %d : %s" % (row, message),
25         '')
26
27 def values(seq, field='value'):
28     return [item[field] for item in seq]
29
30 class ImporterCase(common.TransactionCase):
31     model_name = False
32
33     def __init__(self, *args, **kwargs):
34         super(ImporterCase, self).__init__(*args, **kwargs)
35         self.model = None
36
37     def setUp(self):
38         super(ImporterCase, self).setUp()
39         self.model = self.registry(self.model_name)
40
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),
53             context=context)
54
55     def xid(self, record):
56         ModelData = self.registry('ir.model.data')
57
58         ids = ModelData.search(
59             self.cr, openerp.SUPERUSER_ID,
60             [('model', '=', record._table_name), ('res_id', '=', record.id)])
61         if ids:
62             d = ModelData.read(
63                 self.cr, openerp.SUPERUSER_ID, ids, ['name', 'module'])[0]
64             if d['module']:
65                 return '%s.%s' % (d['module'], d['name'])
66             return d['name']
67
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, {
72             'name': name,
73             'model': record._table_name,
74             'res_id': record.id,
75             'module': '__test__'
76         })
77         return '__test__.' + name
78
79 class test_ids_stuff(ImporterCase):
80     model_name = 'export.integer'
81
82     def test_create_with_id(self):
83         self.assertEqual(
84             self.import_(['.id', 'value'], [['42', '36']]),
85             error(1, u"Unknown database identifier '42'"))
86     def test_create_with_xid(self):
87         self.assertEqual(
88             self.import_(['id', 'value'], [['somexmlid', '42']]),
89             ok(1))
90         self.assertEqual(
91             'somexmlid',
92             self.xid(self.browse()[0]))
93
94     def test_update_with_id(self):
95         id = self.model.create(self.cr, openerp.SUPERUSER_ID, {'value': 36})
96         self.assertEqual(
97             36,
98             self.model.browse(self.cr, openerp.SUPERUSER_ID, id).value)
99
100         self.assertEqual(
101             self.import_(['.id', 'value'], [[str(id), '42']]),
102             ok(1))
103         self.assertEqual(
104             [42], # updated value to imported
105             values(self.read()))
106
107     def test_update_with_xid(self):
108         self.import_(['id', 'value'], [['somexmlid', '36']])
109         self.assertEqual([36], values(self.read()))
110
111         self.import_(['id', 'value'], [['somexmlid', '1234567']])
112         self.assertEqual([1234567], values(self.read()))
113
114 class test_boolean_field(ImporterCase):
115     model_name = 'export.boolean'
116
117     def test_empty(self):
118         self.assertEqual(
119             self.import_(['value'], []),
120             ok(0))
121
122     def test_exported(self):
123         self.assertEqual(
124             self.import_(['value'], [
125                 ['False'],
126                 ['True'],
127             ]),
128             ok(2))
129         records = self.read()
130         self.assertEqual([
131             False,
132             True,
133         ], values(records))
134
135     def test_falses(self):
136         self.assertEqual(
137             self.import_(['value'], [
138                 [u'0'],
139                 [u'no'],
140                 [u'false'],
141                 [u'FALSE'],
142                 [u''],
143             ]),
144             ok(5))
145         self.assertEqual([
146                 False,
147                 False,
148                 False,
149                 False,
150                 False,
151             ],
152             values(self.read()))
153
154     def test_trues(self):
155         self.assertEqual(
156             self.import_(['value'], [
157                 ['off'],
158                 ['None'],
159                 ['nil'],
160                 ['()'],
161                 ['f'],
162                 ['#f'],
163                 # Problem: OpenOffice (and probably excel) output localized booleans
164                 ['VRAI'],
165                 [u'OFF'],
166             ]),
167             ok(8))
168         self.assertEqual(
169             [True] * 8,
170             values(self.read()))
171
172 class test_integer_field(ImporterCase):
173     model_name = 'export.integer'
174
175     def test_none(self):
176         self.assertEqual(
177             self.import_(['value'], []),
178             ok(0))
179
180     def test_empty(self):
181         self.assertEqual(
182             self.import_(['value'], [['']]),
183             ok(1))
184         self.assertEqual(
185             [False],
186             values(self.read()))
187
188     def test_zero(self):
189         self.assertEqual(
190             self.import_(['value'], [['0']]),
191             ok(1))
192         self.assertEqual(
193             self.import_(['value'], [['-0']]),
194             ok(1))
195         self.assertEqual([False, False], values(self.read()))
196
197     def test_positives(self):
198         self.assertEqual(
199             self.import_(['value'], [
200                 ['1'],
201                 ['42'],
202                 [str(2**31-1)],
203                 ['12345678']
204             ]),
205             ok(4))
206         self.assertEqual([
207             1, 42, 2**31-1, 12345678
208         ], values(self.read()))
209
210     def test_negatives(self):
211         self.assertEqual(
212             self.import_(['value'], [
213                 ['-1'],
214                 ['-42'],
215                 [str(-(2**31 - 1))],
216                 [str(-(2**31))],
217                 ['-12345678']
218             ]),
219             ok(5))
220         self.assertEqual([
221             -1, -42, -(2**31 - 1), -(2**31), -12345678
222         ], values(self.read()))
223
224     @mute_logger('openerp.sql_db')
225     def test_out_of_range(self):
226         self.assertEqual(
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 "!"
232         self.cr.rollback()
233         self.assertEqual(
234             self.import_(['value'], [[str(-2**32)]]),
235             error(1, "integer out of range\n"))
236
237
238     def test_nonsense(self):
239         self.assertEqual(
240             self.import_(['value'], [['zorglub']]),
241             error(1, u"'zorglub' does not seem to be an integer for field 'unknown'"))
242
243 class test_float_field(ImporterCase):
244     model_name = 'export.float'
245     def test_none(self):
246         self.assertEqual(
247             self.import_(['value'], []),
248             ok(0))
249
250     def test_empty(self):
251         self.assertEqual(
252             self.import_(['value'], [['']]),
253             ok(1))
254         self.assertEqual(
255             [False],
256             values(self.read()))
257
258     def test_zero(self):
259         self.assertEqual(
260             self.import_(['value'], [['0']]),
261             ok(1))
262         self.assertEqual(
263             self.import_(['value'], [['-0']]),
264             ok(1))
265         self.assertEqual([False, False], values(self.read()))
266
267     def test_positives(self):
268         self.assertEqual(
269             self.import_(['value'], [
270                 ['1'],
271                 ['42'],
272                 [str(2**31-1)],
273                 ['12345678'],
274                 [str(2**33)],
275                 ['0.000001'],
276             ]),
277             ok(6))
278         self.assertEqual([
279             1, 42, 2**31-1, 12345678, 2.0**33, .000001
280         ], values(self.read()))
281
282     def test_negatives(self):
283         self.assertEqual(
284             self.import_(['value'], [
285                 ['-1'],
286                 ['-42'],
287                 [str(-2**31 + 1)],
288                 [str(-2**31)],
289                 ['-12345678'],
290                 [str(-2**33)],
291                 ['-0.000001'],
292             ]),
293             ok(7))
294         self.assertEqual([
295             -1, -42, -(2**31 - 1), -(2**31), -12345678, -2.0**33, -.000001
296         ], values(self.read()))
297
298     def test_nonsense(self):
299         self.assertEqual(
300             self.import_(['value'], [['foobar']]),
301             error(1, u"'foobar' does not seem to be a number for field 'unknown'"))
302
303 class test_string_field(ImporterCase):
304     model_name = 'export.string.bounded'
305
306     def test_empty(self):
307         self.assertEqual(
308             self.import_(['value'], [['']]),
309             ok(1))
310         self.assertEqual([False], values(self.read()))
311
312     def test_imported(self):
313         self.assertEqual(
314             self.import_(['value'], [
315                 [u'foobar'],
316                 [u'foobarbaz'],
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 "
320                  u"surprise him."]
321             ]),
322             ok(4))
323         self.assertEqual([
324             u"foobar",
325             u"foobarbaz",
326             u"Með suð Ã­ eyrum ",
327             u"People 'get' typ",
328         ], values(self.read()))
329
330 class test_unbound_string_field(ImporterCase):
331     model_name = 'export.string'
332
333     def test_imported(self):
334         self.assertEqual(
335             self.import_(['value'], [
336                 [u'í dag viðrar vel til loftárása'],
337                 # ackbar.jpg
338                 [u"If they ask you about fun, you tell them â€“ fun is a filthy"
339                  u" parasite"]
340             ]),
341             ok(2))
342         self.assertEqual([
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()))
346
347 class test_text(ImporterCase):
348     model_name = 'export.text'
349
350     def test_empty(self):
351         self.assertEqual(
352             self.import_(['value'], [['']]),
353             ok(1))
354         self.assertEqual([False], values(self.read()))
355
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.")
363         self.assertEqual(
364             self.import_(['value'], [[s]]),
365             ok(1))
366         self.assertEqual([s], values(self.read()))
367
368 class test_selection(ImporterCase):
369     model_name = 'export.selection'
370     translations_fr = [
371         ("Qux", "toto"),
372         ("Bar", "titi"),
373         ("Foo", "tete"),
374     ]
375
376     def test_imported(self):
377         self.assertEqual(
378             self.import_(['value'], [
379                 ['Qux'],
380                 ['Bar'],
381                 ['Foo'],
382                 ['2'],
383             ]),
384             ok(4))
385         self.assertEqual([3, 2, 1, 2], values(self.read()))
386
387     def test_imported_translated(self):
388         self.registry('res.lang').create(self.cr, openerp.SUPERUSER_ID, {
389             'name': u'Français',
390             'code': 'fr_FR',
391             'translatable': True,
392             'date_format': '%d.%m.%Y',
393             'decimal_point': ',',
394             'thousands_sep': ' ',
395         })
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',
400                 'lang': 'fr_FR',
401                 'type': 'selection',
402                 'src': source,
403                 'value': value
404             })
405
406         self.assertEqual(
407             self.import_(['value'], [
408                 ['toto'],
409                 ['tete'],
410                 ['titi'],
411             ], context={'lang': 'fr_FR'}),
412             ok(3))
413         self.assertEqual([3, 1, 2], values(self.read()))
414         self.assertEqual(
415             self.import_(['value'], [['Foo']], context={'lang': 'fr_FR'}),
416             ok(1))
417
418     def test_invalid(self):
419         self.assertEqual(
420             self.import_(['value'], [['Baz']]),
421             error(1, u"Value 'Baz' not found in selection field 'unknown'"))
422         self.cr.rollback()
423         self.assertEqual(
424             self.import_(['value'], [[42]]),
425             error(1, u"Value '42' not found in selection field 'unknown'"))
426
427 class test_selection_function(ImporterCase):
428     model_name = 'export.selection.function'
429     translations_fr = [
430         ("Corge", "toto"),
431         ("Grault", "titi"),
432         ("Wheee", "tete"),
433         ("Moog", "tutu"),
434     ]
435
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
440         """
441         # NOTE: conflict between a value and a label => ?
442         self.assertEqual(
443             self.import_(['value'], [
444                 ['3'],
445                 ["Grault"],
446             ]),
447             ok(2))
448         self.assertEqual(
449             ['3', '1'],
450             values(self.read()))
451
452     def test_translated(self):
453         """ Expects output of selection function returns translated labels
454         """
455         self.registry('res.lang').create(self.cr, openerp.SUPERUSER_ID, {
456             'name': u'Français',
457             'code': 'fr_FR',
458             'translatable': True,
459             'date_format': '%d.%m.%Y',
460             'decimal_point': ',',
461             'thousands_sep': ' ',
462         })
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',
467                 'lang': 'fr_FR',
468                 'type': 'selection',
469                 'src': source,
470                 'value': value
471             })
472         self.assertEqual(
473             self.import_(['value'], [
474                 ['toto'],
475                 ['tete'],
476             ], context={'lang': 'fr_FR'}),
477             ok(2))
478         self.assertEqual(
479             self.import_(['value'], [['Wheee']], context={'lang': 'fr_FR'}),
480             ok(1))
481
482 class test_m2o(ImporterCase):
483     model_name = 'export.many2one'
484
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})
491         # get its name
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]
496
497         self.assertEqual(
498             self.import_(['value'], [
499                 # import by name_get
500                 [name1],
501                 [name1],
502                 [name2],
503             ]),
504             ok(3))
505         # correct ids assigned to corresponding records
506         self.assertEqual([
507             (integer_id1, name1),
508             (integer_id1, name1),
509             (integer_id2, name2),],
510             values(self.read()))
511
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])
518
519         self.assertEqual(
520             self.import_(['value/id'], [[xid]]),
521             ok(1))
522         b = self.browse()
523         self.assertEqual(42, b[0].value.value)
524
525     def test_by_id(self):
526         integer_id = self.registry('export.integer').create(
527             self.cr, openerp.SUPERUSER_ID, {'value': 42})
528         self.assertEqual(
529             self.import_(['value/.id'], [[integer_id]]),
530             ok(1))
531         b = self.browse()
532         self.assertEqual(42, b[0].value.value)
533
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)
545
546         self.assertEqual(
547             self.import_(['value'], [[name2]]),
548             ok(1))
549         self.assertEqual([
550             (integer_id1, name1)
551         ], values(self.read()))
552
553     def test_fail_by_implicit_id(self):
554         """ Can't implicitly import records by id
555         """
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})
561
562         self.assertEqual(
563             self.import_(['value'], [
564                 # import by id, without specifying it
565                 [integer_id1],
566                 [integer_id2],
567                 [integer_id1],
568             ]),
569             error(1, u"No matching record found for name '%s' in field 'unknown'" % integer_id1))
570
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)...
574         """
575         self.assertEqual(
576             self.import_(['value/value'], [['42']]),
577             error(1, u"Can not create Many-To-One records indirectly, import the field separately"))
578
579     def test_fail_noids(self):
580         self.assertEqual(
581             self.import_(['value'], [['nameisnoexist:3']]),
582             error(1, u"No matching record found for name 'nameisnoexist:3' in field 'unknown'"))
583         self.cr.rollback()
584         self.assertEqual(
585             self.import_(['value/id'], [['noxidhere']]),
586             error(1, u"No matching record found for external id 'noxidhere' in field 'unknown'"))
587         self.cr.rollback()
588         self.assertEqual(
589             self.import_(['value/.id'], [[66]]),
590             error(1, u"No matching record found for database id '66' in field 'unknown'"))
591
592 class test_m2m(ImporterCase):
593     model_name = 'export.many2many'
594
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]
598     def test_ids(self):
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'})
609
610         self.assertEqual(
611             self.import_(['value/.id'], [
612                 ['%d,%d' % (id1, id2)],
613                 ['%d,%d,%d' % (id1, id3, id4)],
614                 ['%d,%d,%d' % (id1, id2, id3)],
615                 ['%d' % id5]
616             ]),
617             ok(4))
618         ids = lambda records: [record.id for record in records]
619
620         b = self.browse()
621         self.assertEqual(ids(b[0].value), [id1, id2])
622         self.assertEqual(values(b[0].value), [3, 44])
623
624         self.assertEqual(ids(b[2].value), [id1, id2, id3])
625         self.assertEqual(values(b[2].value), [3, 44, 84])
626
627     def test_noids(self):
628         self.assertEqual(
629             self.import_(['value/.id'], [['42']]),
630             error(1, u"No matching record found for database id '42' in field 'unknown'"))
631
632     def test_xids(self):
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])
639
640         self.assertEqual(
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]))],
645             ]),
646             ok(3))
647
648         b = self.browse()
649         self.assertEqual(values(b[0].value), [3, 44])
650         self.assertEqual(values(b[2].value), [44, 84])
651     def test_noxids(self):
652         self.assertEqual(
653             self.import_(['value/id'], [['noxidforthat']]),
654             error(1, u"No matching record found for external id 'noxidforthat' in field 'unknown'"))
655
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])
663
664         name = lambda record: dict(record.name_get())[record.id]
665
666         self.assertEqual(
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]))],
671             ]),
672             ok(3))
673
674         b = self.browse()
675         self.assertEqual(values(b[1].value), [3, 44, 84])
676         self.assertEqual(values(b[2].value), [3, 9])
677
678     def test_nonames(self):
679         self.assertEqual(
680             self.import_(['value'], [['wherethem2mhavenonames']]),
681             error(1, u"No matching record found for name 'wherethem2mhavenonames' in field 'unknown'"))
682
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'})
689
690         xid = 'myxid'
691         self.assertEqual(
692             self.import_(['id', 'value/.id'], [[xid, '%d,%d' % (id1, id2)]]),
693             ok(1))
694         self.assertEqual(
695             self.import_(['id', 'value/.id'], [[xid, '%d,%d' % (id3, id4)]]),
696             ok(1))
697
698         b = self.browse()
699         self.assertEqual(len(b), 1)
700         # TODO: replacement of existing m2m values is correct?
701         self.assertEqual(values(b[0].value), [84, 9])
702
703 class test_o2m(ImporterCase):
704     model_name = 'export.one2many'
705
706     def test_name_get(self):
707         s = u'Java is a DSL for taking large XML files and converting them to' \
708             u' stack traces'
709         self.assertEqual(
710             self.import_(
711                 ['const', 'value'],
712                 [['5', s]]),
713             error(1, u"No matching record found for name '%s' in field 'unknown'" % s))
714
715     def test_single(self):
716         self.assertEqual(
717             self.import_(['const', 'value/value'], [
718                 ['5', '63']
719             ]),
720             ok(1))
721
722         (b,) = self.browse()
723         self.assertEqual(b.const, 5)
724         self.assertEqual(values(b.value), [63])
725
726     def test_multicore(self):
727         self.assertEqual(
728             self.import_(['const', 'value/value'], [
729                 ['5', '63'],
730                 ['6', '64'],
731             ]),
732             ok(2))
733
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])
739
740     def test_multisub(self):
741         self.assertEqual(
742             self.import_(['const', 'value/value'], [
743                 ['5', '63'],
744                 ['', '64'],
745                 ['', '65'],
746                 ['', '66'],
747             ]),
748             ok(4))
749
750         (b,) = self.browse()
751         self.assertEqual(values(b.value), [63, 64, 65, 66])
752
753     def test_multi_subfields(self):
754         self.assertEqual(
755             self.import_(['value/str', 'const', 'value/value'], [
756                 ['this', '5', '63'],
757                 ['is', '', '64'],
758                 ['the', '', '65'],
759                 ['rhythm', '', '66'],
760             ]),
761             ok(4))
762
763         (b,) = self.browse()
764         self.assertEqual(values(b.value), [63, 64, 65, 66])
765         self.assertEqual(
766             values(b.value, 'str'),
767             'this is the rhythm'.split())
768
769     def test_link_inline(self):
770         id1 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
771             'str': 'Bf', 'value': 109
772         })
773         id2 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
774             'str': 'Me', 'value': 262
775         })
776
777         try:
778             self.import_(['const', 'value/.id'], [
779                 ['42', '%d,%d' % (id1, id2)]
780             ])
781         except ValueError, e:
782             # should be Exception(Database ID doesn't exist: export.one2many.child : $id1,$id2)
783             self.assertIs(type(e), ValueError)
784             self.assertEqual(
785                 e.args[0],
786                 "invalid literal for int() with base 10: '%d,%d'" % (id1, id2))
787
788     def test_link(self):
789         id1 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
790             'str': 'Bf', 'value': 109
791         })
792         id2 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
793             'str': 'Me', 'value': 262
794         })
795
796         self.assertEqual(
797             self.import_(['const', 'value/.id'], [
798                 ['42', str(id1)],
799                 ['', str(id2)],
800             ]),
801             ok(2))
802
803         [b] = self.browse()
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])
808
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
813         })
814         id2 = O2M_c.create(self.cr, openerp.SUPERUSER_ID, {
815             'str': 'Me', 'value': 262
816         })
817
818         self.assertEqual(
819             self.import_(['const', 'value/.id', 'value/value'], [
820                 ['42', str(id1), '1'],
821                 ['', str(id2), '2'],
822             ]),
823             ok(2))
824
825         [b] = self.browse()
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])
829
830 class test_o2m_multiple(ImporterCase):
831     model_name = 'export.one2many.multiple'
832
833     def test_multi_mixed(self):
834         self.assertEqual(
835             self.import_(['const', 'child1/value', 'child2/value'], [
836                 ['5', '11', '21'],
837                 ['', '12', '22'],
838                 ['', '13', '23'],
839                 ['', '14', ''],
840             ]),
841             ok(4))
842
843         [b] = self.browse()
844         self.assertEqual(values(b.child1), [11, 12, 13, 14])
845         self.assertEqual(values(b.child2), [21, 22, 23])
846
847     def test_multi(self):
848         self.assertEqual(
849             self.import_(['const', 'child1/value', 'child2/value'], [
850                 ['5', '11', '21'],
851                 ['', '12', ''],
852                 ['', '13', ''],
853                 ['', '14', ''],
854                 ['', '', '22'],
855                 ['', '', '23'],
856             ]),
857             ok(6))
858
859         [b] = self.browse()
860         self.assertEqual(values(b.child1), [11, 12, 13, 14])
861         self.assertEqual(values(b.child2), [21, 22, 23])
862
863     def test_multi_fullsplit(self):
864         self.assertEqual(
865             self.import_(['const', 'child1/value', 'child2/value'], [
866                 ['5', '11', ''],
867                 ['', '12', ''],
868                 ['', '13', ''],
869                 ['', '14', ''],
870                 ['', '', '21'],
871                 ['', '', '22'],
872                 ['', '', '23'],
873             ]),
874             ok(7))
875
876         [b] = self.browse()
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])
880
881 # function, related, reference: written to db as-is...
882 # => function uses @type for value coercion/conversion