1# -*- coding: utf-8 -*- 2# Part of Odoo. See LICENSE file for full copyright and licensing details. 3 4from odoo import models 5from odoo.addons.base.tests.common import SavepointCaseWithUserDemo 6from odoo.tools import mute_logger 7from odoo.exceptions import AccessError 8 9 10class TestAPI(SavepointCaseWithUserDemo): 11 """ test the new API of the ORM """ 12 13 @classmethod 14 def setUpClass(cls): 15 super(TestAPI, cls).setUpClass() 16 cls._load_partners_set() 17 18 def assertIsRecordset(self, value, model): 19 self.assertIsInstance(value, models.BaseModel) 20 self.assertEqual(value._name, model) 21 22 def assertIsRecord(self, value, model): 23 self.assertIsRecordset(value, model) 24 self.assertTrue(len(value) <= 1) 25 26 def assertIsNull(self, value, model): 27 self.assertIsRecordset(value, model) 28 self.assertFalse(value) 29 30 @mute_logger('odoo.models') 31 def test_00_query(self): 32 """ Build a recordset, and check its contents. """ 33 domain = [('name', 'ilike', 'j')] 34 partners = self.env['res.partner'].search(domain) 35 36 # partners is a collection of browse records 37 self.assertTrue(partners) 38 39 # partners and its contents are instance of the model 40 self.assertIsRecordset(partners, 'res.partner') 41 for p in partners: 42 self.assertIsRecord(p, 'res.partner') 43 44 @mute_logger('odoo.models') 45 def test_01_query_offset(self): 46 """ Build a recordset with offset, and check equivalence. """ 47 partners1 = self.env['res.partner'].search([], offset=10) 48 partners2 = self.env['res.partner'].search([])[10:] 49 self.assertIsRecordset(partners1, 'res.partner') 50 self.assertIsRecordset(partners2, 'res.partner') 51 self.assertEqual(list(partners1), list(partners2)) 52 53 @mute_logger('odoo.models') 54 def test_02_query_limit(self): 55 """ Build a recordset with offset, and check equivalence. """ 56 partners1 = self.env['res.partner'].search([], order='id asc', limit=10) 57 partners2 = self.env['res.partner'].search([], order='id asc')[:10] 58 self.assertIsRecordset(partners1, 'res.partner') 59 self.assertIsRecordset(partners2, 'res.partner') 60 self.assertEqual(list(partners1), list(partners2)) 61 62 @mute_logger('odoo.models') 63 def test_03_query_offset_limit(self): 64 """ Build a recordset with offset and limit, and check equivalence. """ 65 partners1 = self.env['res.partner'].search([], order='id asc', offset=3, limit=7) 66 partners2 = self.env['res.partner'].search([], order='id asc')[3:10] 67 self.assertIsRecordset(partners1, 'res.partner') 68 self.assertIsRecordset(partners2, 'res.partner') 69 self.assertEqual(list(partners1), list(partners2)) 70 71 @mute_logger('odoo.models') 72 def test_04_query_count(self): 73 """ Test the search method with count=True. """ 74 self.cr.execute("SELECT COUNT(*) FROM res_partner WHERE active") 75 count1 = self.cr.fetchone()[0] 76 count2 = self.env['res.partner'].search([], count=True) 77 self.assertIsInstance(count1, int) 78 self.assertIsInstance(count2, int) 79 self.assertEqual(count1, count2) 80 81 @mute_logger('odoo.models') 82 def test_05_immutable(self): 83 """ Check that a recordset remains the same, even after updates. """ 84 domain = [('name', 'ilike', 'g')] 85 partners = self.env['res.partner'].search(domain) 86 self.assertTrue(partners) 87 ids = partners.ids 88 89 # modify those partners, and check that partners has not changed 90 partners.write({'active': False}) 91 self.assertEqual(ids, partners.ids) 92 93 # redo the search, and check that the result is now empty 94 partners2 = self.env['res.partner'].search(domain) 95 self.assertFalse(partners2) 96 97 @mute_logger('odoo.models') 98 def test_06_fields(self): 99 """ Check that relation fields return records, recordsets or nulls. """ 100 user = self.env.user 101 self.assertIsRecord(user, 'res.users') 102 self.assertIsRecord(user.partner_id, 'res.partner') 103 self.assertIsRecordset(user.groups_id, 'res.groups') 104 105 partners = self.env['res.partner'].search([]) 106 for name, field in partners._fields.items(): 107 if field.type == 'many2one': 108 for p in partners: 109 self.assertIsRecord(p[name], field.comodel_name) 110 elif field.type == 'reference': 111 for p in partners: 112 if p[name]: 113 self.assertIsRecord(p[name], field.comodel_name) 114 elif field.type in ('one2many', 'many2many'): 115 for p in partners: 116 self.assertIsRecordset(p[name], field.comodel_name) 117 118 @mute_logger('odoo.models') 119 def test_07_null(self): 120 """ Check behavior of null instances. """ 121 # select a partner without a parent 122 partner = self.env['res.partner'].search([('parent_id', '=', False)])[0] 123 124 # check partner and related null instances 125 self.assertTrue(partner) 126 self.assertIsRecord(partner, 'res.partner') 127 128 self.assertFalse(partner.parent_id) 129 self.assertIsNull(partner.parent_id, 'res.partner') 130 131 self.assertIs(partner.parent_id.id, False) 132 133 self.assertFalse(partner.parent_id.user_id) 134 self.assertIsNull(partner.parent_id.user_id, 'res.users') 135 136 self.assertIs(partner.parent_id.user_id.name, False) 137 138 self.assertFalse(partner.parent_id.user_id.groups_id) 139 self.assertIsRecordset(partner.parent_id.user_id.groups_id, 'res.groups') 140 141 @mute_logger('odoo.models') 142 def test_40_new_new(self): 143 """ Call new-style methods in the new API style. """ 144 partners = self.env['res.partner'].search([('name', 'ilike', 'g')]) 145 self.assertTrue(partners) 146 147 # call method write on partners itself, and check its effect 148 partners.write({'active': False}) 149 for p in partners: 150 self.assertFalse(p.active) 151 152 @mute_logger('odoo.models') 153 def test_45_new_new(self): 154 """ Call new-style methods on records (new API style). """ 155 partners = self.env['res.partner'].search([('name', 'ilike', 'g')]) 156 self.assertTrue(partners) 157 158 # call method write on partner records, and check its effects 159 for p in partners: 160 p.write({'active': False}) 161 for p in partners: 162 self.assertFalse(p.active) 163 164 @mute_logger('odoo.models') 165 @mute_logger('odoo.addons.base.models.ir_model') 166 def test_50_environment(self): 167 """ Test environment on records. """ 168 # partners and reachable records are attached to self.env 169 partners = self.env['res.partner'].search([('name', 'ilike', 'j')]) 170 self.assertEqual(partners.env, self.env) 171 for x in (partners, partners[0], partners[0].company_id): 172 self.assertEqual(x.env, self.env) 173 for p in partners: 174 self.assertEqual(p.env, self.env) 175 176 # check that the current user can read and modify company data 177 partners[0].company_id.name 178 partners[0].company_id.write({'name': 'Fools'}) 179 180 # create an environment with the demo user 181 demo = self.env['res.users'].search([('login', 'ilike', 'demo')])[0] 182 demo_env = self.env(user=demo) 183 self.assertNotEqual(demo_env, self.env) 184 185 # partners and related records are still attached to self.env 186 self.assertEqual(partners.env, self.env) 187 for x in (partners, partners[0], partners[0].company_id): 188 self.assertEqual(x.env, self.env) 189 for p in partners: 190 self.assertEqual(p.env, self.env) 191 192 # create record instances attached to demo_env 193 demo_partners = partners.with_user(demo) 194 self.assertEqual(demo_partners.env, demo_env) 195 for x in (demo_partners, demo_partners[0], demo_partners[0].company_id): 196 self.assertEqual(x.env, demo_env) 197 for p in demo_partners: 198 self.assertEqual(p.env, demo_env) 199 200 # demo user can read but not modify company data 201 demo_partner = self.env['res.partner'].search([('name', '=', 'Landon Roberts')]).with_user(demo) 202 self.assertTrue(demo_partner.company_id, 'This partner is supposed to be linked to a company') 203 demo_partner.company_id.name 204 with self.assertRaises(AccessError): 205 demo_partner.company_id.write({'name': 'Pricks'}) 206 207 # remove demo user from all groups 208 demo.write({'groups_id': [(5,)]}) 209 210 # demo user can no longer access partner data 211 with self.assertRaises(AccessError): 212 demo_partner.company_id.name 213 214 @mute_logger('odoo.models') 215 def test_60_cache(self): 216 """ Check the record cache behavior """ 217 Partners = self.env['res.partner'] 218 pids = [] 219 data = { 220 'partner One': ['Partner One - One', 'Partner One - Two'], 221 'Partner Two': ['Partner Two - One'], 222 'Partner Three': ['Partner Three - One'], 223 } 224 for p in data: 225 pids.append(Partners.create({ 226 'name': p, 227 'child_ids': [(0, 0, {'name': c}) for c in data[p]], 228 }).id) 229 230 partners = Partners.search([('id', 'in', pids)]) 231 partner1, partner2 = partners[0], partners[1] 232 children1, children2 = partner1.child_ids, partner2.child_ids 233 self.assertTrue(children1) 234 self.assertTrue(children2) 235 236 # take a child contact 237 child = children1[0] 238 self.assertEqual(child.parent_id, partner1) 239 self.assertIn(child, partner1.child_ids) 240 self.assertNotIn(child, partner2.child_ids) 241 242 # fetch data in the cache 243 for p in partners: 244 p.name, p.company_id.name, p.user_id.name, p.contact_address 245 self.env.cache.check(self.env) 246 247 # change its parent 248 child.write({'parent_id': partner2.id}) 249 self.env.cache.check(self.env) 250 251 # check recordsets 252 self.assertEqual(child.parent_id, partner2) 253 self.assertNotIn(child, partner1.child_ids) 254 self.assertIn(child, partner2.child_ids) 255 self.assertEqual(set(partner1.child_ids + child), set(children1)) 256 self.assertEqual(set(partner2.child_ids), set(children2 + child)) 257 self.env.cache.check(self.env) 258 259 # delete it 260 child.unlink() 261 self.env.cache.check(self.env) 262 263 # check recordsets 264 self.assertEqual(set(partner1.child_ids), set(children1) - set([child])) 265 self.assertEqual(set(partner2.child_ids), set(children2)) 266 self.env.cache.check(self.env) 267 268 # convert from the cache format to the write format 269 partner = partner1 270 partner.country_id, partner.child_ids 271 data = partner._convert_to_write(partner._cache) 272 self.assertEqual(data['country_id'], partner.country_id.id) 273 self.assertEqual(data['child_ids'], [(6, 0, partner.child_ids.ids)]) 274 275 @mute_logger('odoo.models') 276 def test_60_prefetch(self): 277 """ Check the record cache prefetching """ 278 partners = self.env['res.partner'].search([], limit=models.PREFETCH_MAX) 279 self.assertTrue(len(partners) > 1) 280 281 # all the records in partners are ready for prefetching 282 self.assertItemsEqual(partners.ids, partners._prefetch_ids) 283 284 # reading ONE partner should fetch them ALL 285 for partner in partners: 286 state = partner.state_id 287 break 288 partner_ids_with_field = [partner.id 289 for partner in partners 290 if 'state_id' in partner._cache] 291 self.assertItemsEqual(partner_ids_with_field, partners.ids) 292 293 # partners' states are ready for prefetching 294 state_ids = { 295 partner._cache['state_id'] 296 for partner in partners 297 if partner._cache['state_id'] is not None 298 } 299 self.assertTrue(len(state_ids) > 1) 300 self.assertItemsEqual(state_ids, state._prefetch_ids) 301 302 # reading ONE partner country should fetch ALL partners' countries 303 for partner in partners: 304 if partner.state_id: 305 partner.state_id.name 306 break 307 state_ids_with_field = [st.id for st in partners.state_id if 'name' in st._cache] 308 self.assertItemsEqual(state_ids_with_field, state_ids) 309 310 @mute_logger('odoo.models') 311 def test_60_prefetch_model(self): 312 """ Check the prefetching model. """ 313 partners = self.env['res.partner'].search([], limit=models.PREFETCH_MAX) 314 self.assertTrue(partners) 315 316 def same_prefetch(a, b): 317 self.assertEqual(set(a._prefetch_ids), set(b._prefetch_ids)) 318 319 def diff_prefetch(a, b): 320 self.assertNotEqual(set(a._prefetch_ids), set(b._prefetch_ids)) 321 322 # the recordset operations below use different prefetch sets 323 diff_prefetch(partners, partners.browse()) 324 diff_prefetch(partners, partners[0]) 325 diff_prefetch(partners, partners[:10]) 326 327 # the recordset operations below share the prefetch set 328 same_prefetch(partners, partners.browse(partners.ids)) 329 same_prefetch(partners, partners.with_user(self.user_demo)) 330 same_prefetch(partners, partners.with_context(active_test=False)) 331 same_prefetch(partners, partners[:10].with_prefetch(partners._prefetch_ids)) 332 333 # iteration and relational fields should use the same prefetch set 334 self.assertEqual(type(partners).country_id.type, 'many2one') 335 self.assertEqual(type(partners).bank_ids.type, 'one2many') 336 self.assertEqual(type(partners).category_id.type, 'many2many') 337 338 vals0 = { 339 'name': 'Empty relational fields', 340 'country_id': False, 341 'bank_ids': [], 342 'category_id': [], 343 } 344 vals1 = { 345 'name': 'Non-empty relational fields', 346 'country_id': self.ref('base.be'), 347 'bank_ids': [(0, 0, {'acc_number': 'FOO42'})], 348 'category_id': [(4, self.partner_category.id)], 349 } 350 partners = partners.create(vals0) + partners.create(vals1) 351 for partner in partners: 352 same_prefetch(partner, partners) 353 same_prefetch(partner.country_id, partners.country_id) 354 same_prefetch(partner.bank_ids, partners.bank_ids) 355 same_prefetch(partner.category_id, partners.category_id) 356 357 @mute_logger('odoo.models') 358 def test_60_prefetch_read(self): 359 """ Check that reading a field computes it on self only. """ 360 Partner = self.env['res.partner'] 361 field = type(Partner).company_type 362 self.assertTrue(field.compute and not field.store) 363 364 partner1 = Partner.create({'name': 'Foo'}) 365 partner2 = Partner.create({'name': 'Bar', 'parent_id': partner1.id}) 366 self.assertEqual(partner1.child_ids, partner2) 367 368 # reading partner1 should not prefetch 'company_type' on partner2 369 self.env.clear() 370 partner1 = partner1.with_prefetch() 371 partner1.read(['company_type']) 372 self.assertIn('company_type', partner1._cache) 373 self.assertNotIn('company_type', partner2._cache) 374 375 # reading partner1 should not prefetch 'company_type' on partner2 376 self.env.clear() 377 partner1 = partner1.with_prefetch() 378 partner1.read(['child_ids', 'company_type']) 379 self.assertIn('company_type', partner1._cache) 380 self.assertNotIn('company_type', partner2._cache) 381 382 @mute_logger('odoo.models') 383 def test_70_one(self): 384 """ Check method one(). """ 385 # check with many records 386 ps = self.env['res.partner'].search([('name', 'ilike', 'a')]) 387 self.assertTrue(len(ps) > 1) 388 with self.assertRaises(ValueError): 389 ps.ensure_one() 390 391 p1 = ps[0] 392 self.assertEqual(len(p1), 1) 393 self.assertEqual(p1.ensure_one(), p1) 394 395 p0 = self.env['res.partner'].browse() 396 self.assertEqual(len(p0), 0) 397 with self.assertRaises(ValueError): 398 p0.ensure_one() 399 400 @mute_logger('odoo.models') 401 def test_80_contains(self): 402 """ Test membership on recordset. """ 403 p1 = self.env['res.partner'].search([('name', 'ilike', 'a')], limit=1).ensure_one() 404 ps = self.env['res.partner'].search([('name', 'ilike', 'a')]) 405 self.assertTrue(p1 in ps) 406 407 @mute_logger('odoo.models') 408 def test_80_set_operations(self): 409 """ Check set operations on recordsets. """ 410 pa = self.env['res.partner'].search([('name', 'ilike', 'a')]) 411 pb = self.env['res.partner'].search([('name', 'ilike', 'b')]) 412 self.assertTrue(pa) 413 self.assertTrue(pb) 414 self.assertTrue(set(pa) & set(pb)) 415 416 concat = pa + pb 417 self.assertEqual(list(concat), list(pa) + list(pb)) 418 self.assertEqual(len(concat), len(pa) + len(pb)) 419 420 difference = pa - pb 421 self.assertEqual(len(difference), len(set(difference))) 422 self.assertEqual(set(difference), set(pa) - set(pb)) 423 self.assertLessEqual(difference, pa) 424 425 intersection = pa & pb 426 self.assertEqual(len(intersection), len(set(intersection))) 427 self.assertEqual(set(intersection), set(pa) & set(pb)) 428 self.assertLessEqual(intersection, pa) 429 self.assertLessEqual(intersection, pb) 430 431 union = pa | pb 432 self.assertEqual(len(union), len(set(union))) 433 self.assertEqual(set(union), set(pa) | set(pb)) 434 self.assertGreaterEqual(union, pa) 435 self.assertGreaterEqual(union, pb) 436 437 # one cannot mix different models with set operations 438 ps = pa 439 ms = self.env['ir.ui.menu'].search([]) 440 self.assertNotEqual(ps._name, ms._name) 441 self.assertNotEqual(ps, ms) 442 443 with self.assertRaises(TypeError): 444 res = ps + ms 445 with self.assertRaises(TypeError): 446 res = ps - ms 447 with self.assertRaises(TypeError): 448 res = ps & ms 449 with self.assertRaises(TypeError): 450 res = ps | ms 451 with self.assertRaises(TypeError): 452 res = ps < ms 453 with self.assertRaises(TypeError): 454 res = ps <= ms 455 with self.assertRaises(TypeError): 456 res = ps > ms 457 with self.assertRaises(TypeError): 458 res = ps >= ms 459 460 @mute_logger('odoo.models') 461 def test_80_filter(self): 462 """ Check filter on recordsets. """ 463 ps = self.env['res.partner'].search([]) 464 customers = ps.browse([p.id for p in ps if p.employee]) 465 466 # filter on a single field 467 self.assertEqual(ps.filtered(lambda p: p.employee), customers) 468 self.assertEqual(ps.filtered('employee'), customers) 469 470 # filter on a sequence of fields 471 self.assertEqual( 472 ps.filtered(lambda p: p.parent_id.employee), 473 ps.filtered('parent_id.employee') 474 ) 475 476 @mute_logger('odoo.models') 477 def test_80_map(self): 478 """ Check map on recordsets. """ 479 ps = self.env['res.partner'].search([]) 480 parents = ps.browse() 481 for p in ps: 482 parents |= p.parent_id 483 484 # map a single field 485 self.assertEqual(ps.mapped(lambda p: p.parent_id), parents) 486 self.assertEqual(ps.mapped('parent_id'), parents) 487 self.assertEqual(ps.parent_id, parents) 488 489 # map a sequence of fields 490 self.assertEqual( 491 ps.mapped(lambda p: p.parent_id.name), 492 [p.parent_id.name for p in ps] 493 ) 494 self.assertEqual( 495 ps.mapped('parent_id.name'), 496 [p.name for p in parents] 497 ) 498 self.assertEqual( 499 ps.parent_id.mapped('name'), 500 [p.name for p in parents] 501 ) 502 503 # map an empty sequence of fields 504 self.assertEqual(ps.mapped(''), ps) 505 506 @mute_logger('odoo.models') 507 def test_80_sorted(self): 508 """ Check sorted on recordsets. """ 509 ps = self.env['res.partner'].search([]) 510 511 # sort by model order 512 qs = ps[:len(ps) // 2] + ps[len(ps) // 2:] 513 self.assertEqual(qs.sorted().ids, ps.ids) 514 515 # sort by name, with a function or a field name 516 by_name_ids = [p.id for p in sorted(ps, key=lambda p: p.name)] 517 self.assertEqual(ps.sorted(lambda p: p.name).ids, by_name_ids) 518 self.assertEqual(ps.sorted('name').ids, by_name_ids) 519 520 # sort by inverse name, with a field name 521 by_name_ids = [p.id for p in sorted(ps, key=lambda p: p.name, reverse=True)] 522 self.assertEqual(ps.sorted('name', reverse=True).ids, by_name_ids) 523