[FIX]Resolved traceback on moving to the previous record from serial numbers in Trace...
[odoo/odoo.git] / openerp / tests / test_orm.py
1 from openerp.tools import mute_logger
2 import common
3
4 UID = common.ADMIN_USER_ID
5 DB = common.DB
6
7
8 class TestORM(common.TransactionCase):
9     """ test special behaviors of ORM CRUD functions
10     
11         TODO: use real Exceptions types instead of Exception """
12
13     def setUp(self):
14         super(TestORM, self).setUp()
15         cr, uid = self.cr, self.uid
16         self.partner = self.registry('res.partner')
17         self.users = self.registry('res.users')
18         self.p1 = self.partner.name_create(cr, uid, 'W')[0]
19         self.p2 = self.partner.name_create(cr, uid, 'Y')[0]
20         self.ir_rule = self.registry('ir.rule')
21
22         # sample unprivileged user
23         employee_gid = self.ref('base.group_user')
24         self.uid2 = self.users.create(cr, uid, {'name': 'test user', 'login': 'test', 'groups_id': [4,employee_gid]})
25
26     @mute_logger('openerp.osv.orm')
27     def testAccessDeletedRecords(self):
28         """ Verify that accessing deleted records works as expected """
29         cr, uid, uid2, p1, p2 = self.cr, self.uid, self.uid2, self.p1, self.p2
30         self.partner.unlink(cr, uid, [p1])
31
32         # read() is expected to skip deleted records because our API is not
33         # transactional for a sequence of search()->read() performed from the
34         # client-side... a concurrent deletion could therefore cause spurious
35         # exceptions even when simply opening a list view!
36         # /!\ Using unprileged user to detect former side effects of ir.rules!
37         self.assertEqual([{'id': p2, 'name': 'Y'}], self.partner.read(cr, uid2, [p1,p2], ['name']), "read() should skip deleted records")
38         self.assertEqual([], self.partner.read(cr, uid2, [p1], ['name']), "read() should skip deleted records")
39
40         # Deleting an already deleted record should be simply ignored
41         self.assertTrue(self.partner.unlink(cr, uid, [p1]), "Re-deleting should be a no-op")
42
43         # Updating an already deleted record should raise, even as admin
44         with self.assertRaises(Exception):
45             self.partner.write(cr, uid, [p1], {'name': 'foo'})
46
47     @mute_logger('openerp.osv.orm')
48     def testAccessFilteredRecords(self):
49         """ Verify that accessing filtered records works as expected for non-admin user """
50         cr, uid, uid2, p1, p2 = self.cr, self.uid, self.uid2, self.p1, self.p2
51         partner_model = self.registry('ir.model').search(cr, uid, [('model','=','res.partner')])[0]
52         self.ir_rule.create(cr, uid, {'name': 'Y is invisible',
53                                       'domain_force': [('id', '!=', p1)],
54                                       'model_id': partner_model})
55         # search as unprivileged user
56         partners = self.partner.search(cr, uid2, [])
57         self.assertFalse(p1 in partners, "W should not be visible...")
58         self.assertTrue(p2 in partners, "... but Y should be visible")
59
60         # read as unprivileged user
61         with self.assertRaises(Exception):
62             self.partner.read(cr, uid2, [p1], ['name'])
63         # write as unprivileged user
64         with self.assertRaises(Exception):
65             self.partner.write(cr, uid2, [p1], {'name': 'foo'})
66         # unlink as unprivileged user
67         with self.assertRaises(Exception):
68             self.partner.unlink(cr, uid2, [p1])
69
70         # Prepare mixed case 
71         self.partner.unlink(cr, uid, [p2])
72         # read mixed records: some deleted and some filtered
73         with self.assertRaises(Exception):
74             self.partner.read(cr, uid2, [p1,p2], ['name'])
75         # delete mixed records: some deleted and some filtered
76         with self.assertRaises(Exception):
77             self.partner.unlink(cr, uid2, [p1,p2])
78
79
80 class TestInherits(common.TransactionCase):
81     """ test the behavior of the orm for models that use _inherits;
82         specifically: res.users, that inherits from res.partner
83     """
84
85     def setUp(self):
86         super(TestInherits, self).setUp()
87         self.partner = self.registry('res.partner')
88         self.user = self.registry('res.users')
89
90     def test_create(self):
91         """ creating a user should automatically create a new partner """
92         partners_before = self.partner.search(self.cr, UID, [])
93         foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
94         foo = self.user.browse(self.cr, UID, foo_id)
95
96         self.assertNotIn(foo.partner_id.id, partners_before)
97
98     def test_create_with_ancestor(self):
99         """ creating a user with a specific 'partner_id' should not create a new partner """
100         par_id = self.partner.create(self.cr, UID, {'name': 'Foo'})
101         partners_before = self.partner.search(self.cr, UID, [])
102         foo_id = self.user.create(self.cr, UID, {'partner_id': par_id, 'login': 'foo', 'password': 'foo'})
103         partners_after = self.partner.search(self.cr, UID, [])
104
105         self.assertEqual(set(partners_before), set(partners_after))
106
107         foo = self.user.browse(self.cr, UID, foo_id)
108         self.assertEqual(foo.name, 'Foo')
109         self.assertEqual(foo.partner_id.id, par_id)
110
111     @mute_logger('openerp.osv.orm')
112     def test_read(self):
113         """ inherited fields should be read without any indirection """
114         foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
115         foo_values, = self.user.read(self.cr, UID, [foo_id])
116         partner_id = foo_values['partner_id'][0]
117         partner_values, = self.partner.read(self.cr, UID, [partner_id])
118         self.assertEqual(foo_values['name'], partner_values['name'])
119
120         foo = self.user.browse(self.cr, UID, foo_id)
121         self.assertEqual(foo.name, foo.partner_id.name)
122
123     @mute_logger('openerp.osv.orm')
124     def test_copy(self):
125         """ copying a user should automatically copy its partner, too """
126         foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
127         foo_before, = self.user.read(self.cr, UID, [foo_id])
128         bar_id = self.user.copy(self.cr, UID, foo_id, {'login': 'bar', 'password': 'bar'})
129         foo_after, = self.user.read(self.cr, UID, [foo_id])
130
131         self.assertEqual(foo_before, foo_after)
132
133         foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id])
134         self.assertEqual(bar.login, 'bar')
135         self.assertNotEqual(foo.id, bar.id)
136         self.assertNotEqual(foo.partner_id.id, bar.partner_id.id)
137
138     @mute_logger('openerp.osv.orm')
139     def test_copy_with_ancestor(self):
140         """ copying a user with 'parent_id' in defaults should not duplicate the partner """
141         foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
142         par_id = self.partner.create(self.cr, UID, {'name': 'Bar'})
143
144         foo_before, = self.user.read(self.cr, UID, [foo_id])
145         partners_before = self.partner.search(self.cr, UID, [])
146         bar_id = self.user.copy(self.cr, UID, foo_id, {'partner_id': par_id, 'login': 'bar'})
147         foo_after, = self.user.read(self.cr, UID, [foo_id])
148         partners_after = self.partner.search(self.cr, UID, [])
149
150         self.assertEqual(foo_before, foo_after)
151         self.assertEqual(set(partners_before), set(partners_after))
152
153         foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id])
154         self.assertNotEqual(foo.id, bar.id)
155         self.assertEqual(bar.partner_id.id, par_id)
156         self.assertEqual(bar.login, 'bar', "login is given from copy parameters")
157         self.assertEqual(bar.password, foo.password, "password is given from original record")
158         self.assertEqual(bar.name, 'Bar', "name is given from specific partner")
159
160
161
162 CREATE = lambda values: (0, False, values)
163 UPDATE = lambda id, values: (1, id, values)
164 DELETE = lambda id: (2, id, False)
165 FORGET = lambda id: (3, id, False)
166 LINK_TO = lambda id: (4, id, False)
167 DELETE_ALL = lambda: (5, False, False)
168 REPLACE_WITH = lambda ids: (6, False, ids)
169
170 def sorted_by_id(list_of_dicts):
171     "sort dictionaries by their 'id' field; useful for comparisons"
172     return sorted(list_of_dicts, key=lambda d: d.get('id'))
173
174 class TestO2MSerialization(common.TransactionCase):
175     """ test the orm method 'write' on one2many fields """
176
177     def setUp(self):
178         super(TestO2MSerialization, self).setUp()
179         self.partner = self.registry('res.partner')
180
181     def test_no_command(self):
182         " empty list of commands yields an empty list of records "
183         results = self.partner.resolve_2many_commands(
184             self.cr, UID, 'child_ids', [])
185
186         self.assertEqual(results, [])
187
188     def test_CREATE_commands(self):
189         " returns the VALUES dict as-is "
190         values = [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}]
191         results = self.partner.resolve_2many_commands(
192             self.cr, UID, 'child_ids', map(CREATE, values))
193
194         self.assertEqual(results, values)
195
196     def test_LINK_TO_command(self):
197         " reads the records from the database, records are returned with their ids. "
198         ids = [
199             self.partner.create(self.cr, UID, {'name': 'foo'}),
200             self.partner.create(self.cr, UID, {'name': 'bar'}),
201             self.partner.create(self.cr, UID, {'name': 'baz'})
202         ]
203         commands = map(LINK_TO, ids)
204
205         results = self.partner.resolve_2many_commands(
206             self.cr, UID, 'child_ids', commands, ['name'])
207
208         self.assertEqual(sorted_by_id(results), sorted_by_id([
209             {'id': ids[0], 'name': 'foo'},
210             {'id': ids[1], 'name': 'bar'},
211             {'id': ids[2], 'name': 'baz'}
212         ]))
213
214     def test_bare_ids_command(self):
215         " same as the equivalent LINK_TO commands "
216         ids = [
217             self.partner.create(self.cr, UID, {'name': 'foo'}),
218             self.partner.create(self.cr, UID, {'name': 'bar'}),
219             self.partner.create(self.cr, UID, {'name': 'baz'})
220         ]
221
222         results = self.partner.resolve_2many_commands(
223             self.cr, UID, 'child_ids', ids, ['name'])
224
225         self.assertEqual(sorted_by_id(results), sorted_by_id([
226             {'id': ids[0], 'name': 'foo'},
227             {'id': ids[1], 'name': 'bar'},
228             {'id': ids[2], 'name': 'baz'}
229         ]))
230
231     def test_UPDATE_command(self):
232         " take the in-db records and merge the provided information in "
233         id_foo = self.partner.create(self.cr, UID, {'name': 'foo'})
234         id_bar = self.partner.create(self.cr, UID, {'name': 'bar'})
235         id_baz = self.partner.create(self.cr, UID, {'name': 'baz', 'city': 'tag'})
236
237         results = self.partner.resolve_2many_commands(
238             self.cr, UID, 'child_ids', [
239                 LINK_TO(id_foo),
240                 UPDATE(id_bar, {'name': 'qux', 'city': 'tagtag'}),
241                 UPDATE(id_baz, {'name': 'quux'})
242             ], ['name', 'city'])
243
244         self.assertEqual(sorted_by_id(results), sorted_by_id([
245             {'id': id_foo, 'name': 'foo', 'city': False},
246             {'id': id_bar, 'name': 'qux', 'city': 'tagtag'},
247             {'id': id_baz, 'name': 'quux', 'city': 'tag'}
248         ]))
249
250     def test_DELETE_command(self):
251         " deleted records are not returned at all. "
252         ids = [
253             self.partner.create(self.cr, UID, {'name': 'foo'}),
254             self.partner.create(self.cr, UID, {'name': 'bar'}),
255             self.partner.create(self.cr, UID, {'name': 'baz'})
256         ]
257         commands = [DELETE(ids[0]), DELETE(ids[1]), DELETE(ids[2])]
258
259         results = self.partner.resolve_2many_commands(
260             self.cr, UID, 'child_ids', commands, ['name'])
261
262         self.assertEqual(results, [])
263
264     def test_mixed_commands(self):
265         ids = [
266             self.partner.create(self.cr, UID, {'name': name})
267             for name in ['NObar', 'baz', 'qux', 'NOquux', 'NOcorge', 'garply']
268         ]
269
270         results = self.partner.resolve_2many_commands(
271             self.cr, UID, 'child_ids', [
272                 CREATE({'name': 'foo'}),
273                 UPDATE(ids[0], {'name': 'bar'}),
274                 LINK_TO(ids[1]),
275                 DELETE(ids[2]),
276                 UPDATE(ids[3], {'name': 'quux',}),
277                 UPDATE(ids[4], {'name': 'corge'}),
278                 CREATE({'name': 'grault'}),
279                 LINK_TO(ids[5])
280             ], ['name'])
281
282         self.assertEqual(sorted_by_id(results), sorted_by_id([
283             {'name': 'foo'},
284             {'id': ids[0], 'name': 'bar'},
285             {'id': ids[1], 'name': 'baz'},
286             {'id': ids[3], 'name': 'quux'},
287             {'id': ids[4], 'name': 'corge'},
288             {'name': 'grault'},
289             {'id': ids[5], 'name': 'garply'}
290         ]))
291
292     def test_LINK_TO_pairs(self):
293         "LINK_TO commands can be written as pairs, instead of triplets"
294         ids = [
295             self.partner.create(self.cr, UID, {'name': 'foo'}),
296             self.partner.create(self.cr, UID, {'name': 'bar'}),
297             self.partner.create(self.cr, UID, {'name': 'baz'})
298         ]
299         commands = map(lambda id: (4, id), ids)
300
301         results = self.partner.resolve_2many_commands(
302             self.cr, UID, 'child_ids', commands, ['name'])
303
304         self.assertEqual(sorted_by_id(results), sorted_by_id([
305             {'id': ids[0], 'name': 'foo'},
306             {'id': ids[1], 'name': 'bar'},
307             {'id': ids[2], 'name': 'baz'}
308         ]))
309
310     def test_singleton_commands(self):
311         "DELETE_ALL can appear as a singleton"
312         results = self.partner.resolve_2many_commands(
313             self.cr, UID, 'child_ids', [DELETE_ALL()], ['name'])
314
315         self.assertEqual(results, [])
316
317 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: