1 from openerp.tools import mute_logger
4 UID = common.ADMIN_USER_ID
8 class TestORM(common.TransactionCase):
9 """ test special behaviors of ORM CRUD functions
11 TODO: use real Exceptions types instead of Exception """
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')
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]})
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])
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")
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")
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'})
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")
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])
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])
79 @mute_logger('openerp.osv.orm')
80 def test_search_read(self):
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])
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')
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)
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
110 super(TestInherits, self).setUp()
111 self.partner = self.registry('res.partner')
112 self.user = self.registry('res.users')
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)
120 self.assertNotIn(foo.partner_id.id, partners_before)
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, [])
129 self.assertEqual(set(partners_before), set(partners_after))
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)
135 @mute_logger('openerp.osv.orm')
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'])
144 foo = self.user.browse(self.cr, UID, foo_id)
145 self.assertEqual(foo.name, foo.partner_id.name)
147 @mute_logger('openerp.osv.orm')
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])
155 self.assertEqual(foo_before, foo_after)
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)
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'})
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, [])
174 self.assertEqual(foo_before, foo_after)
175 self.assertEqual(set(partners_before), set(partners_after))
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")
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)
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'))
198 class TestO2MSerialization(common.TransactionCase):
199 """ test the orm method 'write' on one2many fields """
202 super(TestO2MSerialization, self).setUp()
203 self.partner = self.registry('res.partner')
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', [])
210 self.assertEqual(results, [])
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))
218 self.assertEqual(results, values)
220 def test_LINK_TO_command(self):
221 " reads the records from the database, records are returned with their 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'})
227 commands = map(LINK_TO, ids)
229 results = self.partner.resolve_2many_commands(
230 self.cr, UID, 'child_ids', commands, ['name'])
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'}
238 def test_bare_ids_command(self):
239 " same as the equivalent LINK_TO commands "
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'})
246 results = self.partner.resolve_2many_commands(
247 self.cr, UID, 'child_ids', ids, ['name'])
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'}
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'})
261 results = self.partner.resolve_2many_commands(
262 self.cr, UID, 'child_ids', [
264 UPDATE(id_bar, {'name': 'qux', 'city': 'tagtag'}),
265 UPDATE(id_baz, {'name': 'quux'})
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'}
274 def test_DELETE_command(self):
275 " deleted records are not returned at all. "
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'})
281 commands = [DELETE(ids[0]), DELETE(ids[1]), DELETE(ids[2])]
283 results = self.partner.resolve_2many_commands(
284 self.cr, UID, 'child_ids', commands, ['name'])
286 self.assertEqual(results, [])
288 def test_mixed_commands(self):
290 self.partner.create(self.cr, UID, {'name': name})
291 for name in ['NObar', 'baz', 'qux', 'NOquux', 'NOcorge', 'garply']
294 results = self.partner.resolve_2many_commands(
295 self.cr, UID, 'child_ids', [
296 CREATE({'name': 'foo'}),
297 UPDATE(ids[0], {'name': 'bar'}),
300 UPDATE(ids[3], {'name': 'quux',}),
301 UPDATE(ids[4], {'name': 'corge'}),
302 CREATE({'name': 'grault'}),
306 self.assertEqual(sorted_by_id(results), sorted_by_id([
308 {'id': ids[0], 'name': 'bar'},
309 {'id': ids[1], 'name': 'baz'},
310 {'id': ids[3], 'name': 'quux'},
311 {'id': ids[4], 'name': 'corge'},
313 {'id': ids[5], 'name': 'garply'}
316 def test_LINK_TO_pairs(self):
317 "LINK_TO commands can be written as pairs, instead of triplets"
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'})
323 commands = map(lambda id: (4, id), ids)
325 results = self.partner.resolve_2many_commands(
326 self.cr, UID, 'child_ids', commands, ['name'])
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'}
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'])
339 self.assertEqual(results, [])
341 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: