[ADD] big bit on new import: pretty much everything but o2m
[odoo/odoo.git] / openerp / tests / addons / test_impex / tests / test_load.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 message(msg, type='error', from_=0, to_=0, record=0, field='value'):
9     return {
10         'type': type,
11         'rows': {'from': from_, 'to': to_},
12         'record': record,
13         'field': field,
14         'message': msg
15     }
16
17 def error(row, message, record=None, **kwargs):
18     """ Failed import of the record ``record`` at line ``row``, with the error
19     message ``message``
20
21     :param str message:
22     :param dict record:
23     """
24     return (
25         -1, dict(record or {}, **kwargs),
26         "Line %d : %s" % (row, message),
27         '')
28
29 def values(seq, field='value'):
30     return [item[field] for item in seq]
31
32 class ImporterCase(common.TransactionCase):
33     model_name = False
34
35     def __init__(self, *args, **kwargs):
36         super(ImporterCase, self).__init__(*args, **kwargs)
37         self.model = None
38
39     def setUp(self):
40         super(ImporterCase, self).setUp()
41         self.model = self.registry(self.model_name)
42         self.registry('ir.model.data').clear_caches()
43
44     def import_(self, fields, rows, context=None):
45         return self.model.load(
46             self.cr, openerp.SUPERUSER_ID, fields, rows, context=context)
47     def read(self, fields=('value',), domain=(), context=None):
48         return self.model.read(
49             self.cr, openerp.SUPERUSER_ID,
50             self.model.search(self.cr, openerp.SUPERUSER_ID, domain, context=context),
51             fields=fields, context=context)
52     def browse(self, domain=(), context=None):
53         return self.model.browse(
54             self.cr, openerp.SUPERUSER_ID,
55             self.model.search(self.cr, openerp.SUPERUSER_ID, domain, context=context),
56             context=context)
57
58     def xid(self, record):
59         ModelData = self.registry('ir.model.data')
60
61         ids = ModelData.search(
62             self.cr, openerp.SUPERUSER_ID,
63             [('model', '=', record._table_name), ('res_id', '=', record.id)])
64         if ids:
65             d = ModelData.read(
66                 self.cr, openerp.SUPERUSER_ID, ids, ['name', 'module'])[0]
67             if d['module']:
68                 return '%s.%s' % (d['module'], d['name'])
69             return d['name']
70
71         name = dict(record.name_get())[record.id]
72         # fix dotted name_get results, otherwise xid lookups blow up
73         name = name.replace('.', '-')
74         ModelData.create(self.cr, openerp.SUPERUSER_ID, {
75             'name': name,
76             'model': record._table_name,
77             'res_id': record.id,
78             'module': '__test__'
79         })
80         return '__test__.' + name
81
82 class test_ids_stuff(ImporterCase):
83     model_name = 'export.integer'
84
85     def test_create_with_id(self):
86         ids, messages = self.import_(['.id', 'value'], [['42', '36']])
87         self.assertIs(ids, False)
88         self.assertEqual(messages, [{
89             'type': 'error',
90             'rows': {'from': 0, 'to': 0},
91             'record': 0,
92             'field': '.id',
93             'message': u"Unknown database identifier '42'",
94         }])
95     def test_create_with_xid(self):
96         ids, messages = self.import_(['id', 'value'], [['somexmlid', '42']])
97         self.assertEqual(len(ids), 1)
98         self.assertFalse(messages)
99         self.assertEqual(
100             'somexmlid',
101             self.xid(self.browse()[0]))
102
103     def test_update_with_id(self):
104         id = self.model.create(self.cr, openerp.SUPERUSER_ID, {'value': 36})
105         self.assertEqual(
106             36,
107             self.model.browse(self.cr, openerp.SUPERUSER_ID, id).value)
108
109         ids, messages = self.import_(['.id', 'value'], [[str(id), '42']])
110         self.assertEqual(len(ids), 1)
111         self.assertFalse(messages)
112         self.assertEqual(
113             [42], # updated value to imported
114             values(self.read()))
115
116     def test_update_with_xid(self):
117         self.import_(['id', 'value'], [['somexmlid', '36']])
118         self.assertEqual([36], values(self.read()))
119
120         self.import_(['id', 'value'], [['somexmlid', '1234567']])
121         self.assertEqual([1234567], values(self.read()))
122
123 class test_boolean_field(ImporterCase):
124     model_name = 'export.boolean'
125
126     def test_empty(self):
127         self.assertEqual(
128             self.import_(['value'], []),
129             ([], []))
130
131     def test_exported(self):
132         ids, messages = self.import_(['value'], [['False'], ['True'], ])
133         self.assertEqual(len(ids), 2)
134         self.assertFalse(messages)
135         records = self.read()
136         self.assertEqual([
137             False,
138             True,
139         ], values(records))
140
141     def test_falses(self):
142         ids, messages = self.import_(
143             ['value'],
144             [[u'0'], [u'off'],
145              [u'false'], [u'FALSE'],
146              [u'OFF'], [u''],
147         ])
148         self.assertEqual(len(ids), 6)
149         self.assertFalse(messages)
150         self.assertEqual([
151                 False,
152                 False,
153                 False,
154                 False,
155                 False,
156                 False,
157             ],
158             values(self.read()))
159
160     def test_trues(self):
161         ids, messages = self.import_(
162             ['value'],
163             [['no'],
164              ['None'],
165              ['nil'],
166              ['()'],
167              ['f'],
168              ['#f'],
169              # Problem: OpenOffice (and probably excel) output localized booleans
170              ['VRAI'],
171         ])
172         self.assertEqual(len(ids), 7)
173         # FIXME: should warn for values which are not "true", "yes" or "1"
174         self.assertFalse(messages)
175         self.assertEqual(
176             [True] * 7,
177             values(self.read()))
178
179 class test_integer_field(ImporterCase):
180     model_name = 'export.integer'
181
182     def test_none(self):
183         self.assertEqual(
184             self.import_(['value'], []),
185             ([], []))
186
187     def test_empty(self):
188         ids, messages = self.import_(['value'], [['']])
189         self.assertEqual(len(ids), 1)
190         self.assertFalse(messages)
191         self.assertEqual(
192             [False],
193             values(self.read()))
194
195     def test_zero(self):
196         ids, messages = self.import_(['value'], [['0']])
197         self.assertEqual(len(ids), 1)
198         self.assertFalse(messages)
199
200         ids, messages = self.import_(['value'], [['-0']])
201         self.assertEqual(len(ids), 1)
202         self.assertFalse(messages)
203
204         self.assertEqual([False, False], values(self.read()))
205
206     def test_positives(self):
207         ids, messages = self.import_(['value'], [
208             ['1'],
209             ['42'],
210             [str(2**31-1)],
211             ['12345678']
212         ])
213         self.assertEqual(len(ids), 4)
214         self.assertFalse(messages)
215
216         self.assertEqual([
217             1, 42, 2**31-1, 12345678
218         ], values(self.read()))
219
220     def test_negatives(self):
221         ids, messages = self.import_(['value'], [
222             ['-1'],
223             ['-42'],
224             [str(-(2**31 - 1))],
225             [str(-(2**31))],
226             ['-12345678']
227         ])
228         self.assertEqual(len(ids), 5)
229         self.assertFalse(messages)
230         self.assertEqual([
231             -1, -42, -(2**31 - 1), -(2**31), -12345678
232         ], values(self.read()))
233
234     @mute_logger('openerp.sql_db')
235     def test_out_of_range(self):
236         ids, messages = self.import_(['value'], [[str(2**31)]])
237         self.assertIs(ids, False)
238         self.assertEqual(messages, [{
239             'type': 'error',
240             'rows': {'from': 0, 'to': 0},
241             'record': 0,
242             'message': "integer out of range\n"
243         }])
244
245         ids, messages = self.import_(['value'], [[str(-2**32)]])
246         self.assertIs(ids, False)
247         self.assertEqual(messages, [{
248             'type': 'error',
249             'rows': {'from': 0, 'to': 0},
250             'record': 0,
251             'message': "integer out of range\n"
252         }])
253
254     def test_nonsense(self):
255         ids, messages = self.import_(['value'], [['zorglub']])
256         self.assertIs(ids, False)
257         self.assertEqual(messages, [{
258             'type': 'error',
259             'rows': {'from': 0, 'to': 0},
260             'record': 0,
261             'field': 'value',
262             'message': u"invalid literal for int() with base 10: 'zorglub'",
263         }])
264
265 class test_float_field(ImporterCase):
266     model_name = 'export.float'
267     def test_none(self):
268         self.assertEqual(
269             self.import_(['value'], []),
270             ([], []))
271
272     def test_empty(self):
273         ids, messages = self.import_(['value'], [['']])
274         self.assertEqual(len(ids), 1)
275         self.assertFalse(messages)
276         self.assertEqual(
277             [False],
278             values(self.read()))
279
280     def test_zero(self):
281         ids, messages = self.import_(['value'], [['0']])
282         self.assertEqual(len(ids), 1)
283         self.assertFalse(messages)
284
285         ids, messages = self.import_(['value'], [['-0']])
286         self.assertEqual(len(ids), 1)
287         self.assertFalse(messages)
288
289         self.assertEqual([False, False], values(self.read()))
290
291     def test_positives(self):
292         ids, messages = self.import_(['value'], [
293             ['1'],
294             ['42'],
295             [str(2**31-1)],
296             ['12345678'],
297             [str(2**33)],
298             ['0.000001'],
299         ])
300         self.assertEqual(len(ids), 6)
301         self.assertFalse(messages)
302
303         self.assertEqual([
304             1, 42, 2**31-1, 12345678, 2.0**33, .000001
305         ], values(self.read()))
306
307     def test_negatives(self):
308         ids, messages = self.import_(['value'], [
309             ['-1'],
310             ['-42'],
311             [str(-2**31 + 1)],
312             [str(-2**31)],
313             ['-12345678'],
314             [str(-2**33)],
315             ['-0.000001'],
316         ])
317         self.assertEqual(len(ids), 7)
318         self.assertFalse(messages)
319         self.assertEqual([
320             -1, -42, -(2**31 - 1), -(2**31), -12345678, -2.0**33, -.000001
321         ], values(self.read()))
322
323     def test_nonsense(self):
324         ids, messages = self.import_(['value'], [['foobar']])
325         self.assertIs(ids, False)
326         self.assertEqual(messages, [{
327             'type': 'error',
328             'rows': {'from': 0, 'to': 0},
329             'record': 0,
330             'field': 'value',
331             'message': u"invalid literal for float(): foobar",
332         }])
333
334 class test_string_field(ImporterCase):
335     model_name = 'export.string.bounded'
336
337     def test_empty(self):
338         ids, messages = self.import_(['value'], [['']])
339         self.assertEqual(len(ids), 1)
340         self.assertFalse(messages)
341         self.assertEqual([False], values(self.read()))
342
343     def test_imported(self):
344         ids, messages = self.import_(['value'], [
345             [u'foobar'],
346             [u'foobarbaz'],
347             [u'Með suð Ã­ eyrum við spilum endalaust'],
348             [u"People 'get' types. They use them all the time. Telling "
349              u"someone he can't pound a nail with a banana doesn't much "
350              u"surprise him."]
351         ])
352         self.assertEqual(len(ids), 4)
353         self.assertFalse(messages)
354         self.assertEqual([
355             u"foobar",
356             u"foobarbaz",
357             u"Með suð Ã­ eyrum ",
358             u"People 'get' typ",
359         ], values(self.read()))
360
361 class test_unbound_string_field(ImporterCase):
362     model_name = 'export.string'
363
364     def test_imported(self):
365         ids, messages = self.import_(['value'], [
366             [u'í dag viðrar vel til loftárása'],
367             # ackbar.jpg
368             [u"If they ask you about fun, you tell them â€“ fun is a filthy"
369              u" parasite"]
370         ])
371         self.assertEqual(len(ids), 2)
372         self.assertFalse(messages)
373         self.assertEqual([
374             u"í dag viðrar vel til loftárása",
375             u"If they ask you about fun, you tell them â€“ fun is a filthy parasite"
376         ], values(self.read()))
377
378 class test_text(ImporterCase):
379     model_name = 'export.text'
380
381     def test_empty(self):
382         ids, messages = self.import_(['value'], [['']])
383         self.assertEqual(len(ids), 1)
384         self.assertFalse(messages)
385         self.assertEqual([False], values(self.read()))
386
387     def test_imported(self):
388         s = (u"Breiðskífa er notað um Ãºtgefna hljómplötu sem inniheldur "
389              u"stúdíóupptökur frá einum flytjanda. Breiðskífur eru oftast "
390              u"milli 25-80 mínútur og er lengd Ã¾eirra oft miðuð við 33â…“ "
391              u"snúninga 12 tommu vínylplötur (sem geta verið allt að 30 mín "
392              u"hvor hlið).\n\nBreiðskífur eru stundum tvöfaldar og eru Ã¾Ã¦r Ã¾Ã¡"
393              u" gefnar Ãºt Ã¡ tveimur geisladiskum eða tveimur vínylplötum.")
394         ids, messages = self.import_(['value'], [[s]])
395         self.assertEqual(len(ids), 1)
396         self.assertFalse(messages)
397         self.assertEqual([s], values(self.read()))
398
399 class test_selection(ImporterCase):
400     model_name = 'export.selection'
401     translations_fr = [
402         ("Qux", "toto"),
403         ("Bar", "titi"),
404         ("Foo", "tete"),
405     ]
406
407     def test_imported(self):
408         ids, messages = self.import_(['value'], [
409             ['Qux'],
410             ['Bar'],
411             ['Foo'],
412             ['2'],
413         ])
414         self.assertEqual(len(ids), 4)
415         self.assertFalse(messages)
416         self.assertEqual([3, 2, 1, 2], values(self.read()))
417
418     def test_imported_translated(self):
419         self.registry('res.lang').create(self.cr, openerp.SUPERUSER_ID, {
420             'name': u'Français',
421             'code': 'fr_FR',
422             'translatable': True,
423             'date_format': '%d.%m.%Y',
424             'decimal_point': ',',
425             'thousand_sep': ' ',
426         })
427         Translations = self.registry('ir.translation')
428         for source, value in self.translations_fr:
429             Translations.create(self.cr, openerp.SUPERUSER_ID, {
430                 'name': 'export.selection,value',
431                 'lang': 'fr_FR',
432                 'type': 'selection',
433                 'src': source,
434                 'value': value
435             })
436
437         ids, messages = self.import_(['value'], [
438             ['toto'],
439             ['tete'],
440             ['titi'],
441         ], context={'lang': 'fr_FR'})
442         self.assertEqual(len(ids), 3)
443         self.assertFalse(messages)
444
445         self.assertEqual([3, 1, 2], values(self.read()))
446
447         ids, messages = self.import_(['value'], [['Foo']], context={'lang': 'fr_FR'})
448         self.assertEqual(len(ids), 1)
449         self.assertFalse(messages)
450
451     def test_invalid(self):
452         ids, messages = self.import_(['value'], [['Baz']])
453         self.assertIs(ids, False)
454         self.assertEqual(messages, [{
455             'type': 'error',
456             'rows': {'from': 0, 'to': 0},
457             'record': 0,
458             'field': 'value',
459             'message': "Value 'Baz' not found in selection field 'value'",
460         }])
461
462         ids, messages = self.import_(['value'], [[42]])
463         self.assertIs(ids, False)
464         self.assertEqual(messages, [{
465             'type': 'error',
466             'rows': {'from': 0, 'to': 0},
467             'record': 0,
468             'field': 'value',
469             'message': "Value '42' not found in selection field 'value'",
470         }])
471
472 class test_selection_function(ImporterCase):
473     model_name = 'export.selection.function'
474     translations_fr = [
475         ("Corge", "toto"),
476         ("Grault", "titi"),
477         ("Whee", "tete"),
478         ("Moog", "tutu"),
479     ]
480
481     def test_imported(self):
482         """ import uses fields_get, so translates import label (may or may not
483         be good news) *and* serializes the selection function to reverse it:
484         import does not actually know that the selection field uses a function
485         """
486         # NOTE: conflict between a value and a label => ?
487         ids, messages = self.import_(['value'], [
488             ['3'],
489             ["Grault"],
490         ])
491         self.assertEqual(len(ids), 2)
492         self.assertFalse(messages)
493         self.assertEqual(
494             ['3', '1'],
495             values(self.read()))
496
497     def test_translated(self):
498         """ Expects output of selection function returns translated labels
499         """
500         self.registry('res.lang').create(self.cr, openerp.SUPERUSER_ID, {
501             'name': u'Français',
502             'code': 'fr_FR',
503             'translatable': True,
504             'date_format': '%d.%m.%Y',
505             'decimal_point': ',',
506             'thousand_sep': ' ',
507         })
508         Translations = self.registry('ir.translation')
509         for source, value in self.translations_fr:
510             Translations.create(self.cr, openerp.SUPERUSER_ID, {
511                 'name': 'export.selection,value',
512                 'lang': 'fr_FR',
513                 'type': 'selection',
514                 'src': source,
515                 'value': value
516             })
517         ids, messages = self.import_(['value'], [
518             ['toto'],
519             ['tete'],
520         ], context={'lang': 'fr_FR'})
521         self.assertIs(ids, False)
522         self.assertEqual(messages, [{
523             'type': 'error',
524             'rows': {'from': 1, 'to': 1},
525             'record': 1,
526             'field': 'value',
527             'message': "Value 'tete' not found in selection field 'value'",
528         }])
529         ids, messages = self.import_(['value'], [['Wheee']], context={'lang': 'fr_FR'})
530         self.assertEqual(len(ids), 1)
531         self.assertFalse(messages)
532
533 class test_m2o(ImporterCase):
534     model_name = 'export.many2one'
535
536     def test_by_name(self):
537         # create integer objects
538         integer_id1 = self.registry('export.integer').create(
539             self.cr, openerp.SUPERUSER_ID, {'value': 42})
540         integer_id2 = self.registry('export.integer').create(
541             self.cr, openerp.SUPERUSER_ID, {'value': 36})
542         # get its name
543         name1 = dict(self.registry('export.integer').name_get(
544             self.cr, openerp.SUPERUSER_ID,[integer_id1]))[integer_id1]
545         name2 = dict(self.registry('export.integer').name_get(
546             self.cr, openerp.SUPERUSER_ID,[integer_id2]))[integer_id2]
547
548         ids , messages = self.import_(['value'], [
549             # import by name_get
550             [name1],
551             [name1],
552             [name2],
553         ])
554         self.assertFalse(messages)
555         self.assertEqual(len(ids), 3)
556         # correct ids assigned to corresponding records
557         self.assertEqual([
558             (integer_id1, name1),
559             (integer_id1, name1),
560             (integer_id2, name2),],
561             values(self.read()))
562
563     def test_by_xid(self):
564         ExportInteger = self.registry('export.integer')
565         integer_id = ExportInteger.create(
566             self.cr, openerp.SUPERUSER_ID, {'value': 42})
567         xid = self.xid(ExportInteger.browse(
568             self.cr, openerp.SUPERUSER_ID, [integer_id])[0])
569
570         ids, messages = self.import_(['value/id'], [[xid]])
571         self.assertFalse(messages)
572         self.assertEqual(len(ids), 1)
573         b = self.browse()
574         self.assertEqual(42, b[0].value.value)
575
576     def test_by_id(self):
577         integer_id = self.registry('export.integer').create(
578             self.cr, openerp.SUPERUSER_ID, {'value': 42})
579         ids, messages = self.import_(['value/.id'], [[integer_id]])
580         self.assertFalse(messages)
581         self.assertEqual(len(ids), 1)
582         b = self.browse()
583         self.assertEqual(42, b[0].value.value)
584
585     def test_by_names(self):
586         integer_id1 = self.registry('export.integer').create(
587             self.cr, openerp.SUPERUSER_ID, {'value': 42})
588         integer_id2 = self.registry('export.integer').create(
589             self.cr, openerp.SUPERUSER_ID, {'value': 42})
590         name1 = dict(self.registry('export.integer').name_get(
591             self.cr, openerp.SUPERUSER_ID,[integer_id1]))[integer_id1]
592         name2 = dict(self.registry('export.integer').name_get(
593             self.cr, openerp.SUPERUSER_ID,[integer_id2]))[integer_id2]
594         # names should be the same
595         self.assertEqual(name1, name2)
596
597         ids, messages = self.import_(['value'], [[name2]])
598         self.assertEqual(
599             messages,
600             [message(u"Found multiple matches for field 'value' (2 matches)",
601                      type='warning')])
602         self.assertEqual(len(ids), 1)
603         self.assertEqual([
604             (integer_id1, name1)
605         ], values(self.read()))
606
607     def test_fail_by_implicit_id(self):
608         """ Can't implicitly import records by id
609         """
610         # create integer objects
611         integer_id1 = self.registry('export.integer').create(
612             self.cr, openerp.SUPERUSER_ID, {'value': 42})
613         integer_id2 = self.registry('export.integer').create(
614             self.cr, openerp.SUPERUSER_ID, {'value': 36})
615
616         # Because name_search all the things. Fallback schmallback
617         ids, messages = self.import_(['value'], [
618                 # import by id, without specifying it
619                 [integer_id1],
620                 [integer_id2],
621                 [integer_id1],
622         ])
623         self.assertEqual(messages, [
624             message(u"No matching record found for name '%s' in field 'value'" % id,
625                     from_=index, to_=index, record=index)
626             for index, id in enumerate([integer_id1, integer_id2, integer_id1])])
627         self.assertIs(ids, False)
628
629     def test_sub_field(self):
630         """ Does not implicitly create the record, does not warn that you can't
631         import m2o subfields (at all)...
632         """
633         ids, messages = self.import_(['value/value'], [['42']])
634         self.assertEqual(messages, [
635             message(u"Can not create Many-To-One records indirectly, import "
636                     u"the field separately")])
637         self.assertIs(ids, False)
638
639     def test_fail_noids(self):
640         ids, messages = self.import_(['value'], [['nameisnoexist:3']])
641         self.assertEqual(messages, [message(
642             u"No matching record found for name 'nameisnoexist:3' "
643             u"in field 'value'")])
644         self.assertIs(ids, False)
645
646         ids, messages = self.import_(['value/id'], [['noxidhere']])
647         self.assertEqual(messages, [message(
648             u"No matching record found for external id 'noxidhere' "
649             u"in field 'value'")])
650         self.assertIs(ids, False)
651
652         ids, messages = self.import_(['value/.id'], [['66']])
653         self.assertEqual(messages, [message(
654             u"No matching record found for database id '66' "
655             u"in field 'value'")])
656         self.assertIs(ids, False)
657
658     def test_fail_multiple(self):
659         ids, messages = self.import_(
660             ['value', 'value/id'],
661             [['somename', 'somexid']])
662         self.assertEqual(messages, [message(
663             u"Ambiguous specification for field 'value', only provide one of "
664             u"name, external id or database id")])
665         self.assertIs(ids, False)
666
667 class test_m2m(ImporterCase):
668     model_name = 'export.many2many'
669
670     # apparently, one and only thing which works is a
671     # csv_internal_sep-separated list of ids, xids, or names (depending if
672     # m2m/.id, m2m/id or m2m[/anythingelse]
673     def test_ids(self):
674         id1 = self.registry('export.many2many.other').create(
675                 self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'})
676         id2 = self.registry('export.many2many.other').create(
677                 self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'})
678         id3 = self.registry('export.many2many.other').create(
679                 self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'})
680         id4 = self.registry('export.many2many.other').create(
681                 self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'})
682         id5 = self.registry('export.many2many.other').create(
683                 self.cr, openerp.SUPERUSER_ID, {'value': 99, 'str': 'record4'})
684
685         ids, messages = self.import_(['value/.id'], [
686             ['%d,%d' % (id1, id2)],
687             ['%d,%d,%d' % (id1, id3, id4)],
688             ['%d,%d,%d' % (id1, id2, id3)],
689             ['%d' % id5]
690         ])
691         self.assertFalse(messages)
692         self.assertEqual(len(ids), 4)
693
694         ids = lambda records: [record.id for record in records]
695
696         b = self.browse()
697         self.assertEqual(ids(b[0].value), [id1, id2])
698         self.assertEqual(values(b[0].value), [3, 44])
699
700         self.assertEqual(ids(b[2].value), [id1, id2, id3])
701         self.assertEqual(values(b[2].value), [3, 44, 84])
702
703     def test_noids(self):
704         ids, messages = self.import_(['value/.id'], [['42']])
705         self.assertEqual(messages, [message(
706             u"No matching record found for database id '42' in field "
707             u"'value'")])
708         self.assertIs(ids, False)
709
710     def test_xids(self):
711         M2O_o = self.registry('export.many2many.other')
712         id1 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'})
713         id2 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'})
714         id3 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'})
715         id4 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'})
716         records = M2O_o.browse(self.cr, openerp.SUPERUSER_ID, [id1, id2, id3, id4])
717
718         ids, messages = self.import_(['value/id'], [
719             ['%s,%s' % (self.xid(records[0]), self.xid(records[1]))],
720             ['%s' % self.xid(records[3])],
721             ['%s,%s' % (self.xid(records[2]), self.xid(records[1]))],
722         ])
723         self.assertFalse(messages)
724         self.assertEqual(len(ids), 3)
725
726         b = self.browse()
727         self.assertEqual(values(b[0].value), [3, 44])
728         self.assertEqual(values(b[2].value), [44, 84])
729     def test_noxids(self):
730         ids, messages = self.import_(['value/id'], [['noxidforthat']])
731         self.assertEqual(messages, [message(
732             u"No matching record found for external id 'noxidforthat' "
733             u"in field 'value'")])
734         self.assertIs(ids, False)
735
736     def test_names(self):
737         M2O_o = self.registry('export.many2many.other')
738         id1 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'})
739         id2 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'})
740         id3 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'})
741         id4 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'})
742         records = M2O_o.browse(self.cr, openerp.SUPERUSER_ID, [id1, id2, id3, id4])
743
744         name = lambda record: dict(record.name_get())[record.id]
745
746         ids, messages = self.import_(['value'], [
747             ['%s,%s' % (name(records[1]), name(records[2]))],
748             ['%s,%s,%s' % (name(records[0]), name(records[1]), name(records[2]))],
749             ['%s,%s' % (name(records[0]), name(records[3]))],
750         ])
751         self.assertFalse(messages)
752         self.assertEqual(len(ids), 3)
753
754         b = self.browse()
755         self.assertEqual(values(b[1].value), [3, 44, 84])
756         self.assertEqual(values(b[2].value), [3, 9])
757
758     def test_nonames(self):
759         ids, messages = self.import_(['value'], [['wherethem2mhavenonames']])
760         self.assertEqual(messages, [message(
761             u"No matching record found for name 'wherethem2mhavenonames' in "
762             u"field 'value'")])
763         self.assertIs(ids, False)
764
765     def test_import_to_existing(self):
766         M2O_o = self.registry('export.many2many.other')
767         id1 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'})
768         id2 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'})
769         id3 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'})
770         id4 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'})
771
772         xid = 'myxid'
773         ids, messages = self.import_(['id', 'value/.id'], [[xid, '%d,%d' % (id1, id2)]])
774         self.assertFalse(messages)
775         self.assertEqual(len(ids), 1)
776         ids, messages = self.import_(['id', 'value/.id'], [[xid, '%d,%d' % (id3, id4)]])
777         self.assertFalse(messages)
778         self.assertEqual(len(ids), 1)
779
780         b = self.browse()
781         self.assertEqual(len(b), 1)
782         # TODO: replacement of existing m2m values is correct?
783         self.assertEqual(values(b[0].value), [84, 9])
784
785 class test_o2m(ImporterCase):
786     model_name = 'export.one2many'
787
788     def test_name_get(self):
789         # FIXME: bloody hell why can't this just name_create the record?
790         self.assertRaises(
791             IndexError,
792             self.import_,
793             ['const', 'value'],
794             [['5', u'Java is a DSL for taking large XML files'
795                    u' and converting them to stack traces']])
796
797     def test_single(self):
798         ids, messages = self.import_(['const', 'value/value'], [
799             ['5', '63']
800         ])
801         self.assertEqual(len(ids), 1)
802         self.assertFalse(messages)
803
804         (b,) = self.browse()
805         self.assertEqual(b.const, 5)
806         self.assertEqual(values(b.value), [63])
807
808     def test_multicore(self):
809         ids, messages = self.import_(['const', 'value/value'], [
810             ['5', '63'],
811             ['6', '64'],
812         ])
813         self.assertEqual(len(ids), 2)
814         self.assertFalse(messages)
815
816         b1, b2 = self.browse()
817         self.assertEqual(b1.const, 5)
818         self.assertEqual(values(b1.value), [63])
819         self.assertEqual(b2.const, 6)
820         self.assertEqual(values(b2.value), [64])
821
822     def test_multisub(self):
823         ids, messages = self.import_(['const', 'value/value'], [
824             ['5', '63'],
825             ['', '64'],
826             ['', '65'],
827             ['', '66'],
828         ])
829         self.assertEqual(len(ids), 4)
830         self.assertFalse(messages)
831
832         (b,) = self.browse()
833         self.assertEqual(values(b.value), [63, 64, 65, 66])
834
835     def test_multi_subfields(self):
836         ids, messages = self.import_(['value/str', 'const', 'value/value'], [
837             ['this', '5', '63'],
838             ['is', '', '64'],
839             ['the', '', '65'],
840             ['rhythm', '', '66'],
841         ])
842         self.assertEqual(len(ids), 4)
843         self.assertFalse(messages)
844
845         (b,) = self.browse()
846         self.assertEqual(values(b.value), [63, 64, 65, 66])
847         self.assertEqual(
848             values(b.value, 'str'),
849             'this is the rhythm'.split())
850
851     def test_link_inline(self):
852         id1 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
853             'str': 'Bf', 'value': 109
854         })
855         id2 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
856             'str': 'Me', 'value': 262
857         })
858
859         try:
860             self.import_(['const', 'value/.id'], [
861                 ['42', '%d,%d' % (id1, id2)]
862             ])
863             self.fail("Should have raised a valueerror")
864         except ValueError, e:
865             # should be Exception(Database ID doesn't exist: export.one2many.child : $id1,$id2)
866             self.assertIs(type(e), ValueError)
867             self.assertEqual(
868                 e.args[0],
869                 "invalid literal for int() with base 10: '%d,%d'" % (id1, id2))
870
871     def test_link(self):
872         id1 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
873             'str': 'Bf', 'value': 109
874         })
875         id2 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, {
876             'str': 'Me', 'value': 262
877         })
878
879         ids, messages = self.import_(['const', 'value/.id'], [
880             ['42', str(id1)],
881             ['', str(id2)],
882         ])
883         self.assertEqual(len(ids), 2)
884         self.assertFalse(messages)
885
886         # No record values alongside id => o2m resolution skipped altogether,
887         # creates 2 records => remove/don't import columns sideshow columns,
888         # get completely different semantics
889         b, b1 = self.browse()
890         self.assertEqual(b.const, 42)
891         self.assertEqual(values(b.value), [])
892         self.assertEqual(b1.const, 4)
893         self.assertEqual(values(b1.value), [])
894
895     def test_link_2(self):
896         O2M_c = self.registry('export.one2many.child')
897         id1 = O2M_c.create(self.cr, openerp.SUPERUSER_ID, {
898             'str': 'Bf', 'value': 109
899         })
900         id2 = O2M_c.create(self.cr, openerp.SUPERUSER_ID, {
901             'str': 'Me', 'value': 262
902         })
903
904         ids, messages = self.import_(['const', 'value/.id', 'value/value'], [
905             ['42', str(id1), '1'],
906             ['', str(id2), '2'],
907         ])
908         self.assertEqual(len(ids), 2)
909         self.assertFalse(messages)
910
911         (b,) = self.browse()
912         # if an id (db or xid) is provided, expectations that objects are
913         # *already* linked and emits UPDATE (1, id, {}).
914         # Noid => CREATE (0, ?, {})
915         # TODO: xid ignored aside from getting corresponding db id?
916         self.assertEqual(b.const, 42)
917         self.assertEqual(values(b.value), [])
918
919         # FIXME: updates somebody else's records?
920         self.assertEqual(
921             O2M_c.read(self.cr, openerp.SUPERUSER_ID, id1),
922             {'id': id1, 'str': 'Bf', 'value': 1, 'parent_id': False})
923         self.assertEqual(
924             O2M_c.read(self.cr, openerp.SUPERUSER_ID, id2),
925             {'id': id2, 'str': 'Me', 'value': 2, 'parent_id': False})
926
927 class test_o2m_multiple(ImporterCase):
928     model_name = 'export.one2many.multiple'
929
930     def test_multi_mixed(self):
931         ids, messages = self.import_(['const', 'child1/value', 'child2/value'], [
932             ['5', '11', '21'],
933             ['', '12', '22'],
934             ['', '13', '23'],
935             ['', '14', ''],
936         ])
937         self.assertEqual(len(ids), 4)
938         self.assertFalse(messages)
939         # Oh yeah, that's the stuff
940         (b, b1, b2) = self.browse()
941         self.assertEqual(values(b.child1), [11])
942         self.assertEqual(values(b.child2), [21])
943
944         self.assertEqual(values(b1.child1), [12])
945         self.assertEqual(values(b1.child2), [22])
946
947         self.assertEqual(values(b2.child1), [13, 14])
948         self.assertEqual(values(b2.child2), [23])
949
950     def test_multi(self):
951         ids, messages = self.import_(['const', 'child1/value', 'child2/value'], [
952             ['5', '11', '21'],
953             ['', '12', ''],
954             ['', '13', ''],
955             ['', '14', ''],
956             ['', '', '22'],
957             ['', '', '23'],
958         ])
959         self.assertEqual(len(ids), 6)
960         self.assertFalse(messages)
961         # What the actual fuck?
962         (b, b1) = self.browse()
963         self.assertEqual(values(b.child1), [11, 12, 13, 14])
964         self.assertEqual(values(b.child2), [21])
965         self.assertEqual(values(b1.child2), [22, 23])
966
967     def test_multi_fullsplit(self):
968         ids, messages = self.import_(['const', 'child1/value', 'child2/value'], [
969             ['5', '11', ''],
970             ['', '12', ''],
971             ['', '13', ''],
972             ['', '14', ''],
973             ['', '', '21'],
974             ['', '', '22'],
975             ['', '', '23'],
976         ])
977         self.assertEqual(len(ids), 7)
978         self.assertFalse(messages)
979         # oh wow
980         (b, b1) = self.browse()
981         self.assertEqual(b.const, 5)
982         self.assertEqual(values(b.child1), [11, 12, 13, 14])
983         self.assertEqual(b1.const, 36)
984         self.assertEqual(values(b1.child2), [21, 22, 23])
985
986 # function, related, reference: written to db as-is...
987 # => function uses @type for value coercion/conversion