[FIX] website: url_for() does not mess up anchor links
[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     @mute_logger('openerp.osv.orm')
80     def test_search_read(self):
81         # simple search_read
82         self.partner.create(self.cr, UID, {'name': 'MyPartner1'})
83         found = self.partner.search_read(self.cr, UID, [['name', '=', 'MyPartner1']], ['name'])
84         self.assertEqual(len(found), 1)
85         self.assertEqual(found[0]['name'], 'MyPartner1')
86         self.assertTrue('id' in found[0])
87
88         # search_read correct order
89         self.partner.create(self.cr, UID, {'name': 'MyPartner2'})
90         found = self.partner.search_read(self.cr, UID, [['name', 'like', 'MyPartner']], ['name'], order="name")
91         self.assertEqual(len(found), 2)
92         self.assertEqual(found[0]['name'], 'MyPartner1')
93         self.assertEqual(found[1]['name'], 'MyPartner2')
94         found = self.partner.search_read(self.cr, UID, [['name', 'like', 'MyPartner']], ['name'], order="name desc")
95         self.assertEqual(len(found), 2)
96         self.assertEqual(found[0]['name'], 'MyPartner2')
97         self.assertEqual(found[1]['name'], 'MyPartner1')
98
99         # search_read that finds nothing
100         found = self.partner.search_read(self.cr, UID, [['name', '=', 'Does not exists']], ['name'])
101         self.assertEqual(len(found), 0)
102
103
104 class TestInherits(common.TransactionCase):
105     """ test the behavior of the orm for models that use _inherits;
106         specifically: res.users, that inherits from res.partner
107     """
108
109     def setUp(self):
110         super(TestInherits, self).setUp()
111         self.partner = self.registry('res.partner')
112         self.user = self.registry('res.users')
113
114     def test_create(self):
115         """ creating a user should automatically create a new partner """
116         partners_before = self.partner.search(self.cr, UID, [])
117         foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
118         foo = self.user.browse(self.cr, UID, foo_id)
119
120         self.assertNotIn(foo.partner_id.id, partners_before)
121
122     def test_create_with_ancestor(self):
123         """ creating a user with a specific 'partner_id' should not create a new partner """
124         par_id = self.partner.create(self.cr, UID, {'name': 'Foo'})
125         partners_before = self.partner.search(self.cr, UID, [])
126         foo_id = self.user.create(self.cr, UID, {'partner_id': par_id, 'login': 'foo', 'password': 'foo'})
127         partners_after = self.partner.search(self.cr, UID, [])
128
129         self.assertEqual(set(partners_before), set(partners_after))
130
131         foo = self.user.browse(self.cr, UID, foo_id)
132         self.assertEqual(foo.name, 'Foo')
133         self.assertEqual(foo.partner_id.id, par_id)
134
135     @mute_logger('openerp.osv.orm')
136     def test_read(self):
137         """ inherited fields should be read without any indirection """
138         foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
139         foo_values, = self.user.read(self.cr, UID, [foo_id])
140         partner_id = foo_values['partner_id'][0]
141         partner_values, = self.partner.read(self.cr, UID, [partner_id])
142         self.assertEqual(foo_values['name'], partner_values['name'])
143
144         foo = self.user.browse(self.cr, UID, foo_id)
145         self.assertEqual(foo.name, foo.partner_id.name)
146
147     @mute_logger('openerp.osv.orm')
148     def test_copy(self):
149         """ copying a user should automatically copy its partner, too """
150         foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
151         foo_before, = self.user.read(self.cr, UID, [foo_id])
152         bar_id = self.user.copy(self.cr, UID, foo_id, {'login': 'bar', 'password': 'bar'})
153         foo_after, = self.user.read(self.cr, UID, [foo_id])
154
155         self.assertEqual(foo_before, foo_after)
156
157         foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id])
158         self.assertEqual(bar.login, 'bar')
159         self.assertNotEqual(foo.id, bar.id)
160         self.assertNotEqual(foo.partner_id.id, bar.partner_id.id)
161
162     @mute_logger('openerp.osv.orm')
163     def test_copy_with_ancestor(self):
164         """ copying a user with 'parent_id' in defaults should not duplicate the partner """
165         foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
166         par_id = self.partner.create(self.cr, UID, {'name': 'Bar'})
167
168         foo_before, = self.user.read(self.cr, UID, [foo_id])
169         partners_before = self.partner.search(self.cr, UID, [])
170         bar_id = self.user.copy(self.cr, UID, foo_id, {'partner_id': par_id, 'login': 'bar'})
171         foo_after, = self.user.read(self.cr, UID, [foo_id])
172         partners_after = self.partner.search(self.cr, UID, [])
173
174         self.assertEqual(foo_before, foo_after)
175         self.assertEqual(set(partners_before), set(partners_after))
176
177         foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id])
178         self.assertNotEqual(foo.id, bar.id)
179         self.assertEqual(bar.partner_id.id, par_id)
180         self.assertEqual(bar.login, 'bar', "login is given from copy parameters")
181         self.assertEqual(bar.password, foo.password, "password is given from original record")
182         self.assertEqual(bar.name, 'Bar', "name is given from specific partner")
183
184
185
186 CREATE = lambda values: (0, False, values)
187 UPDATE = lambda id, values: (1, id, values)
188 DELETE = lambda id: (2, id, False)
189 FORGET = lambda id: (3, id, False)
190 LINK_TO = lambda id: (4, id, False)
191 DELETE_ALL = lambda: (5, False, False)
192 REPLACE_WITH = lambda ids: (6, False, ids)
193
194 def sorted_by_id(list_of_dicts):
195     "sort dictionaries by their 'id' field; useful for comparisons"
196     return sorted(list_of_dicts, key=lambda d: d.get('id'))
197
198 class TestO2MSerialization(common.TransactionCase):
199     """ test the orm method 'write' on one2many fields """
200
201     def setUp(self):
202         super(TestO2MSerialization, self).setUp()
203         self.partner = self.registry('res.partner')
204
205     def test_no_command(self):
206         " empty list of commands yields an empty list of records "
207         results = self.partner.resolve_2many_commands(
208             self.cr, UID, 'child_ids', [])
209
210         self.assertEqual(results, [])
211
212     def test_CREATE_commands(self):
213         " returns the VALUES dict as-is "
214         values = [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}]
215         results = self.partner.resolve_2many_commands(
216             self.cr, UID, 'child_ids', map(CREATE, values))
217
218         self.assertEqual(results, values)
219
220     def test_LINK_TO_command(self):
221         " reads the records from the database, records are returned with their ids. "
222         ids = [
223             self.partner.create(self.cr, UID, {'name': 'foo'}),
224             self.partner.create(self.cr, UID, {'name': 'bar'}),
225             self.partner.create(self.cr, UID, {'name': 'baz'})
226         ]
227         commands = map(LINK_TO, ids)
228
229         results = self.partner.resolve_2many_commands(
230             self.cr, UID, 'child_ids', commands, ['name'])
231
232         self.assertEqual(sorted_by_id(results), sorted_by_id([
233             {'id': ids[0], 'name': 'foo'},
234             {'id': ids[1], 'name': 'bar'},
235             {'id': ids[2], 'name': 'baz'}
236         ]))
237
238     def test_bare_ids_command(self):
239         " same as the equivalent LINK_TO commands "
240         ids = [
241             self.partner.create(self.cr, UID, {'name': 'foo'}),
242             self.partner.create(self.cr, UID, {'name': 'bar'}),
243             self.partner.create(self.cr, UID, {'name': 'baz'})
244         ]
245
246         results = self.partner.resolve_2many_commands(
247             self.cr, UID, 'child_ids', ids, ['name'])
248
249         self.assertEqual(sorted_by_id(results), sorted_by_id([
250             {'id': ids[0], 'name': 'foo'},
251             {'id': ids[1], 'name': 'bar'},
252             {'id': ids[2], 'name': 'baz'}
253         ]))
254
255     def test_UPDATE_command(self):
256         " take the in-db records and merge the provided information in "
257         id_foo = self.partner.create(self.cr, UID, {'name': 'foo'})
258         id_bar = self.partner.create(self.cr, UID, {'name': 'bar'})
259         id_baz = self.partner.create(self.cr, UID, {'name': 'baz', 'city': 'tag'})
260
261         results = self.partner.resolve_2many_commands(
262             self.cr, UID, 'child_ids', [
263                 LINK_TO(id_foo),
264                 UPDATE(id_bar, {'name': 'qux', 'city': 'tagtag'}),
265                 UPDATE(id_baz, {'name': 'quux'})
266             ], ['name', 'city'])
267
268         self.assertEqual(sorted_by_id(results), sorted_by_id([
269             {'id': id_foo, 'name': 'foo', 'city': False},
270             {'id': id_bar, 'name': 'qux', 'city': 'tagtag'},
271             {'id': id_baz, 'name': 'quux', 'city': 'tag'}
272         ]))
273
274     def test_DELETE_command(self):
275         " deleted records are not returned at all. "
276         ids = [
277             self.partner.create(self.cr, UID, {'name': 'foo'}),
278             self.partner.create(self.cr, UID, {'name': 'bar'}),
279             self.partner.create(self.cr, UID, {'name': 'baz'})
280         ]
281         commands = [DELETE(ids[0]), DELETE(ids[1]), DELETE(ids[2])]
282
283         results = self.partner.resolve_2many_commands(
284             self.cr, UID, 'child_ids', commands, ['name'])
285
286         self.assertEqual(results, [])
287
288     def test_mixed_commands(self):
289         ids = [
290             self.partner.create(self.cr, UID, {'name': name})
291             for name in ['NObar', 'baz', 'qux', 'NOquux', 'NOcorge', 'garply']
292         ]
293
294         results = self.partner.resolve_2many_commands(
295             self.cr, UID, 'child_ids', [
296                 CREATE({'name': 'foo'}),
297                 UPDATE(ids[0], {'name': 'bar'}),
298                 LINK_TO(ids[1]),
299                 DELETE(ids[2]),
300                 UPDATE(ids[3], {'name': 'quux',}),
301                 UPDATE(ids[4], {'name': 'corge'}),
302                 CREATE({'name': 'grault'}),
303                 LINK_TO(ids[5])
304             ], ['name'])
305
306         self.assertEqual(sorted_by_id(results), sorted_by_id([
307             {'name': 'foo'},
308             {'id': ids[0], 'name': 'bar'},
309             {'id': ids[1], 'name': 'baz'},
310             {'id': ids[3], 'name': 'quux'},
311             {'id': ids[4], 'name': 'corge'},
312             {'name': 'grault'},
313             {'id': ids[5], 'name': 'garply'}
314         ]))
315
316     def test_LINK_TO_pairs(self):
317         "LINK_TO commands can be written as pairs, instead of triplets"
318         ids = [
319             self.partner.create(self.cr, UID, {'name': 'foo'}),
320             self.partner.create(self.cr, UID, {'name': 'bar'}),
321             self.partner.create(self.cr, UID, {'name': 'baz'})
322         ]
323         commands = map(lambda id: (4, id), ids)
324
325         results = self.partner.resolve_2many_commands(
326             self.cr, UID, 'child_ids', commands, ['name'])
327
328         self.assertEqual(sorted_by_id(results), sorted_by_id([
329             {'id': ids[0], 'name': 'foo'},
330             {'id': ids[1], 'name': 'bar'},
331             {'id': ids[2], 'name': 'baz'}
332         ]))
333
334     def test_singleton_commands(self):
335         "DELETE_ALL can appear as a singleton"
336         results = self.partner.resolve_2many_commands(
337             self.cr, UID, 'child_ids', [DELETE_ALL()], ['name'])
338
339         self.assertEqual(results, [])
340
341 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: