1 from collections import defaultdict
2 from openerp.tools import mute_logger
3 from openerp.tests import common
5 UID = common.ADMIN_USER_ID
9 class TestORM(common.TransactionCase):
10 """ test special behaviors of ORM CRUD functions
12 TODO: use real Exceptions types instead of Exception """
15 super(TestORM, self).setUp()
16 cr, uid = self.cr, self.uid
17 self.partner = self.registry('res.partner')
18 self.users = self.registry('res.users')
19 self.p1 = self.partner.name_create(cr, uid, 'W')[0]
20 self.p2 = self.partner.name_create(cr, uid, 'Y')[0]
21 self.ir_rule = self.registry('ir.rule')
23 # sample unprivileged user
24 employee_gid = self.ref('base.group_user')
25 self.uid2 = self.users.create(cr, uid, {'name': 'test user', 'login': 'test', 'groups_id': [4,employee_gid]})
27 @mute_logger('openerp.models')
28 def testAccessDeletedRecords(self):
29 """ Verify that accessing deleted records works as expected """
30 cr, uid, uid2, p1, p2 = self.cr, self.uid, self.uid2, self.p1, self.p2
31 self.partner.unlink(cr, uid, [p1])
33 # read() is expected to skip deleted records because our API is not
34 # transactional for a sequence of search()->read() performed from the
35 # client-side... a concurrent deletion could therefore cause spurious
36 # exceptions even when simply opening a list view!
37 # /!\ Using unprileged user to detect former side effects of ir.rules!
38 self.assertEqual([{'id': p2, 'name': 'Y'}], self.partner.read(cr, uid2, [p1,p2], ['name']), "read() should skip deleted records")
39 self.assertEqual([], self.partner.read(cr, uid2, [p1], ['name']), "read() should skip deleted records")
41 # Deleting an already deleted record should be simply ignored
42 self.assertTrue(self.partner.unlink(cr, uid, [p1]), "Re-deleting should be a no-op")
44 # Updating an already deleted record should raise, even as admin
45 with self.assertRaises(Exception):
46 self.partner.write(cr, uid, [p1], {'name': 'foo'})
48 @mute_logger('openerp.models')
49 def testAccessFilteredRecords(self):
50 """ Verify that accessing filtered records works as expected for non-admin user """
51 cr, uid, uid2, p1, p2 = self.cr, self.uid, self.uid2, self.p1, self.p2
52 partner_model = self.registry('ir.model').search(cr, uid, [('model','=','res.partner')])[0]
53 self.ir_rule.create(cr, uid, {'name': 'Y is invisible',
54 'domain_force': [('id', '!=', p1)],
55 'model_id': partner_model})
56 # search as unprivileged user
57 partners = self.partner.search(cr, uid2, [])
58 self.assertFalse(p1 in partners, "W should not be visible...")
59 self.assertTrue(p2 in partners, "... but Y should be visible")
61 # read as unprivileged user
62 with self.assertRaises(Exception):
63 self.partner.read(cr, uid2, [p1], ['name'])
64 # write as unprivileged user
65 with self.assertRaises(Exception):
66 self.partner.write(cr, uid2, [p1], {'name': 'foo'})
67 # unlink as unprivileged user
68 with self.assertRaises(Exception):
69 self.partner.unlink(cr, uid2, [p1])
72 self.partner.unlink(cr, uid, [p2])
73 # read mixed records: some deleted and some filtered
74 with self.assertRaises(Exception):
75 self.partner.read(cr, uid2, [p1,p2], ['name'])
76 # delete mixed records: some deleted and some filtered
77 with self.assertRaises(Exception):
78 self.partner.unlink(cr, uid2, [p1,p2])
80 def test_multi_read(self):
81 record_id = self.partner.create(self.cr, UID, {'name': 'MyPartner1'})
82 records = self.partner.read(self.cr, UID, [record_id])
83 self.assertIsInstance(records, list)
85 def test_one_read(self):
86 record_id = self.partner.create(self.cr, UID, {'name': 'MyPartner1'})
87 record = self.partner.read(self.cr, UID, record_id)
88 self.assertIsInstance(record, dict)
90 @mute_logger('openerp.models')
91 def test_search_read(self):
93 self.partner.create(self.cr, UID, {'name': 'MyPartner1'})
94 found = self.partner.search_read(self.cr, UID, [['name', '=', 'MyPartner1']], ['name'])
95 self.assertEqual(len(found), 1)
96 self.assertEqual(found[0]['name'], 'MyPartner1')
97 self.assertTrue('id' in found[0])
99 # search_read correct order
100 self.partner.create(self.cr, UID, {'name': 'MyPartner2'})
101 found = self.partner.search_read(self.cr, UID, [['name', 'like', 'MyPartner']], ['name'], order="name")
102 self.assertEqual(len(found), 2)
103 self.assertEqual(found[0]['name'], 'MyPartner1')
104 self.assertEqual(found[1]['name'], 'MyPartner2')
105 found = self.partner.search_read(self.cr, UID, [['name', 'like', 'MyPartner']], ['name'], order="name desc")
106 self.assertEqual(len(found), 2)
107 self.assertEqual(found[0]['name'], 'MyPartner2')
108 self.assertEqual(found[1]['name'], 'MyPartner1')
110 # search_read that finds nothing
111 found = self.partner.search_read(self.cr, UID, [['name', '=', 'Does not exists']], ['name'])
112 self.assertEqual(len(found), 0)
114 def test_exists(self):
115 partner = self.partner.browse(self.cr, UID, [])
117 # check that records obtained from search exist
118 recs = partner.search([])
119 self.assertTrue(recs)
120 self.assertEqual(recs.exists(), recs)
122 # check that there is no record with id 0
123 recs = partner.browse([0])
124 self.assertFalse(recs.exists())
126 def test_groupby_date(self):
138 partners_by_day = defaultdict(set)
139 partners_by_month = defaultdict(set)
140 partners_by_year = defaultdict(set)
142 for name, date in partners.items():
143 p = self.partner.create(self.cr, UID, dict(name=name, date=date))
144 all_partners.append(p)
145 partners_by_day[date].add(p)
146 partners_by_month[date.rsplit('-', 1)[0]].add(p)
147 partners_by_year[date.split('-', 1)[0]].add(p)
149 def read_group(interval, domain=None):
150 main_domain = [('id', 'in', all_partners)]
152 domain = ['&'] + main_domain + domain
156 rg = self.partner.read_group(self.cr, self.uid, domain, ['date'], 'date' + ':' + interval)
159 result[r['date:' + interval]] = set(self.partner.search(self.cr, self.uid, r['__domain']))
162 self.assertEqual(len(read_group('day')), len(partners_by_day))
163 self.assertEqual(len(read_group('month')), len(partners_by_month))
164 self.assertEqual(len(read_group('year')), len(partners_by_year))
166 rg = self.partner.read_group(self.cr, self.uid, [('id', 'in', all_partners)],
167 ['date'], ['date:month', 'date:day'], lazy=False)
168 self.assertEqual(len(rg), len(all_partners))
171 class TestInherits(common.TransactionCase):
172 """ test the behavior of the orm for models that use _inherits;
173 specifically: res.users, that inherits from res.partner
177 super(TestInherits, self).setUp()
178 self.partner = self.registry('res.partner')
179 self.user = self.registry('res.users')
181 def test_default(self):
182 """ `default_get` cannot return a dictionary or a new id """
183 defaults = self.user.default_get(self.cr, UID, ['partner_id'])
184 if 'partner_id' in defaults:
185 self.assertIsInstance(defaults['partner_id'], (bool, int, long))
187 def test_create(self):
188 """ creating a user should automatically create a new partner """
189 partners_before = self.partner.search(self.cr, UID, [])
190 foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
191 foo = self.user.browse(self.cr, UID, foo_id)
193 self.assertNotIn(foo.partner_id.id, partners_before)
195 def test_create_with_ancestor(self):
196 """ creating a user with a specific 'partner_id' should not create a new partner """
197 par_id = self.partner.create(self.cr, UID, {'name': 'Foo'})
198 partners_before = self.partner.search(self.cr, UID, [])
199 foo_id = self.user.create(self.cr, UID, {'partner_id': par_id, 'login': 'foo', 'password': 'foo'})
200 partners_after = self.partner.search(self.cr, UID, [])
202 self.assertEqual(set(partners_before), set(partners_after))
204 foo = self.user.browse(self.cr, UID, foo_id)
205 self.assertEqual(foo.name, 'Foo')
206 self.assertEqual(foo.partner_id.id, par_id)
208 @mute_logger('openerp.models')
210 """ inherited fields should be read without any indirection """
211 foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
212 foo_values, = self.user.read(self.cr, UID, [foo_id])
213 partner_id = foo_values['partner_id'][0]
214 partner_values, = self.partner.read(self.cr, UID, [partner_id])
215 self.assertEqual(foo_values['name'], partner_values['name'])
217 foo = self.user.browse(self.cr, UID, foo_id)
218 self.assertEqual(foo.name, foo.partner_id.name)
220 @mute_logger('openerp.models')
222 """ copying a user should automatically copy its partner, too """
223 foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'})
224 foo_before, = self.user.read(self.cr, UID, [foo_id])
225 del foo_before['__last_update']
226 bar_id = self.user.copy(self.cr, UID, foo_id, {'login': 'bar', 'password': 'bar'})
227 foo_after, = self.user.read(self.cr, UID, [foo_id])
228 del foo_after['__last_update']
230 self.assertEqual(foo_before, foo_after)
232 foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id])
233 self.assertEqual(bar.login, 'bar')
234 self.assertNotEqual(foo.id, bar.id)
235 self.assertNotEqual(foo.partner_id.id, bar.partner_id.id)
237 @mute_logger('openerp.models')
238 def test_copy_with_ancestor(self):
239 """ copying a user with 'parent_id' in defaults should not duplicate the partner """
240 foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo',
241 'login_date': '2016-01-01', 'signature': 'XXX'})
242 par_id = self.partner.create(self.cr, UID, {'name': 'Bar'})
244 foo_before, = self.user.read(self.cr, UID, [foo_id])
245 del foo_before['__last_update']
246 partners_before = self.partner.search(self.cr, UID, [])
247 bar_id = self.user.copy(self.cr, UID, foo_id, {'partner_id': par_id, 'login': 'bar'})
248 foo_after, = self.user.read(self.cr, UID, [foo_id])
249 del foo_after['__last_update']
250 partners_after = self.partner.search(self.cr, UID, [])
252 self.assertEqual(foo_before, foo_after)
253 self.assertEqual(set(partners_before), set(partners_after))
255 foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id])
256 self.assertNotEqual(foo.id, bar.id)
257 self.assertEqual(bar.partner_id.id, par_id)
258 self.assertEqual(bar.login, 'bar', "login is given from copy parameters")
259 self.assertFalse(bar.login_date, "login_date should not be copied from original record")
260 self.assertEqual(bar.name, 'Bar', "name is given from specific partner")
261 self.assertEqual(bar.signature, foo.signature, "signature should be copied")
265 CREATE = lambda values: (0, False, values)
266 UPDATE = lambda id, values: (1, id, values)
267 DELETE = lambda id: (2, id, False)
268 FORGET = lambda id: (3, id, False)
269 LINK_TO = lambda id: (4, id, False)
270 DELETE_ALL = lambda: (5, False, False)
271 REPLACE_WITH = lambda ids: (6, False, ids)
273 def sorted_by_id(list_of_dicts):
274 "sort dictionaries by their 'id' field; useful for comparisons"
275 return sorted(list_of_dicts, key=lambda d: d.get('id'))
277 class TestO2MSerialization(common.TransactionCase):
278 """ test the orm method 'write' on one2many fields """
281 super(TestO2MSerialization, self).setUp()
282 self.partner = self.registry('res.partner')
284 def test_no_command(self):
285 " empty list of commands yields an empty list of records "
286 results = self.partner.resolve_2many_commands(
287 self.cr, UID, 'child_ids', [])
289 self.assertEqual(results, [])
291 def test_CREATE_commands(self):
292 " returns the VALUES dict as-is "
293 values = [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}]
294 results = self.partner.resolve_2many_commands(
295 self.cr, UID, 'child_ids', map(CREATE, values))
297 self.assertEqual(results, values)
299 def test_LINK_TO_command(self):
300 " reads the records from the database, records are returned with their ids. "
302 self.partner.create(self.cr, UID, {'name': 'foo'}),
303 self.partner.create(self.cr, UID, {'name': 'bar'}),
304 self.partner.create(self.cr, UID, {'name': 'baz'})
306 commands = map(LINK_TO, ids)
308 results = self.partner.resolve_2many_commands(
309 self.cr, UID, 'child_ids', commands, ['name'])
311 self.assertEqual(sorted_by_id(results), sorted_by_id([
312 {'id': ids[0], 'name': 'foo'},
313 {'id': ids[1], 'name': 'bar'},
314 {'id': ids[2], 'name': 'baz'}
317 def test_bare_ids_command(self):
318 " same as the equivalent LINK_TO commands "
320 self.partner.create(self.cr, UID, {'name': 'foo'}),
321 self.partner.create(self.cr, UID, {'name': 'bar'}),
322 self.partner.create(self.cr, UID, {'name': 'baz'})
325 results = self.partner.resolve_2many_commands(
326 self.cr, UID, 'child_ids', ids, ['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_UPDATE_command(self):
335 " take the in-db records and merge the provided information in "
336 id_foo = self.partner.create(self.cr, UID, {'name': 'foo'})
337 id_bar = self.partner.create(self.cr, UID, {'name': 'bar'})
338 id_baz = self.partner.create(self.cr, UID, {'name': 'baz', 'city': 'tag'})
340 results = self.partner.resolve_2many_commands(
341 self.cr, UID, 'child_ids', [
343 UPDATE(id_bar, {'name': 'qux', 'city': 'tagtag'}),
344 UPDATE(id_baz, {'name': 'quux'})
347 self.assertEqual(sorted_by_id(results), sorted_by_id([
348 {'id': id_foo, 'name': 'foo', 'city': False},
349 {'id': id_bar, 'name': 'qux', 'city': 'tagtag'},
350 {'id': id_baz, 'name': 'quux', 'city': 'tag'}
353 def test_DELETE_command(self):
354 " deleted records are not returned at all. "
356 self.partner.create(self.cr, UID, {'name': 'foo'}),
357 self.partner.create(self.cr, UID, {'name': 'bar'}),
358 self.partner.create(self.cr, UID, {'name': 'baz'})
360 commands = [DELETE(ids[0]), DELETE(ids[1]), DELETE(ids[2])]
362 results = self.partner.resolve_2many_commands(
363 self.cr, UID, 'child_ids', commands, ['name'])
365 self.assertEqual(results, [])
367 def test_mixed_commands(self):
369 self.partner.create(self.cr, UID, {'name': name})
370 for name in ['NObar', 'baz', 'qux', 'NOquux', 'NOcorge', 'garply']
373 results = self.partner.resolve_2many_commands(
374 self.cr, UID, 'child_ids', [
375 CREATE({'name': 'foo'}),
376 UPDATE(ids[0], {'name': 'bar'}),
379 UPDATE(ids[3], {'name': 'quux',}),
380 UPDATE(ids[4], {'name': 'corge'}),
381 CREATE({'name': 'grault'}),
385 self.assertEqual(sorted_by_id(results), sorted_by_id([
387 {'id': ids[0], 'name': 'bar'},
388 {'id': ids[1], 'name': 'baz'},
389 {'id': ids[3], 'name': 'quux'},
390 {'id': ids[4], 'name': 'corge'},
392 {'id': ids[5], 'name': 'garply'}
395 def test_LINK_TO_pairs(self):
396 "LINK_TO commands can be written as pairs, instead of triplets"
398 self.partner.create(self.cr, UID, {'name': 'foo'}),
399 self.partner.create(self.cr, UID, {'name': 'bar'}),
400 self.partner.create(self.cr, UID, {'name': 'baz'})
402 commands = map(lambda id: (4, id), ids)
404 results = self.partner.resolve_2many_commands(
405 self.cr, UID, 'child_ids', commands, ['name'])
407 self.assertEqual(sorted_by_id(results), sorted_by_id([
408 {'id': ids[0], 'name': 'foo'},
409 {'id': ids[1], 'name': 'bar'},
410 {'id': ids[2], 'name': 'baz'}
413 def test_singleton_commands(self):
414 "DELETE_ALL can appear as a singleton"
415 results = self.partner.resolve_2many_commands(
416 self.cr, UID, 'child_ids', [DELETE_ALL()], ['name'])
418 self.assertEqual(results, [])
420 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: