# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, models, Command from odoo.addons.base.tests.common import SavepointCaseWithUserDemo from odoo.tools import mute_logger, unique, lazy from odoo.exceptions import AccessError class TestAPI(SavepointCaseWithUserDemo): """ test the new API of the ORM """ @classmethod def setUpClass(cls): super(TestAPI, cls).setUpClass() cls._load_partners_set() def assertIsRecordset(self, value, model): self.assertIsInstance(value, models.BaseModel) self.assertEqual(value._name, model) def assertIsRecord(self, value, model): self.assertIsRecordset(value, model) self.assertTrue(len(value) <= 1) def assertIsNull(self, value, model): self.assertIsRecordset(value, model) self.assertFalse(value) @mute_logger('odoo.models') def test_00_query(self): """ Build a recordset, and check its contents. """ domain = [('name', 'ilike', 'j'), ('id', 'in', self.partners.ids)] partners = self.env['res.partner'].search(domain) # partners is a collection of browse records self.assertTrue(partners) # partners and its contents are instance of the model self.assertIsRecordset(partners, 'res.partner') for p in partners: self.assertIsRecord(p, 'res.partner') @mute_logger('odoo.models') def test_01_query_offset(self): """ Build a recordset with offset, and check equivalence. """ partners1 = self.env['res.partner'].search([('id', 'in', self.partners.ids)], offset=5) partners2 = self.env['res.partner'].search([('id', 'in', self.partners.ids)])[5:] self.assertIsRecordset(partners1, 'res.partner') self.assertIsRecordset(partners2, 'res.partner') self.assertEqual(list(partners1), list(partners2)) @mute_logger('odoo.models') def test_02_query_limit(self): """ Build a recordset with offset, and check equivalence. """ partners1 = self.env['res.partner'].search([('id', 'in', self.partners.ids)], order='id asc', limit=5) partners2 = self.env['res.partner'].search([('id', 'in', self.partners.ids)], order='id asc')[:5] self.assertIsRecordset(partners1, 'res.partner') self.assertIsRecordset(partners2, 'res.partner') self.assertEqual(list(partners1), list(partners2)) @mute_logger('odoo.models') def test_03_query_offset_limit(self): """ Build a recordset with offset and limit, and check equivalence. """ partners1 = self.env['res.partner'].search([('id', 'in', self.partners.ids)], order='id asc', offset=3, limit=7) partners2 = self.env['res.partner'].search([('id', 'in', self.partners.ids)], order='id asc')[3:10] self.assertIsRecordset(partners1, 'res.partner') self.assertIsRecordset(partners2, 'res.partner') self.assertEqual(list(partners1), list(partners2)) @mute_logger('odoo.models') def test_04_query_count(self): """ Test the search_count method. """ self.cr.execute("SELECT COUNT(*) FROM res_partner WHERE active") count1 = self.cr.fetchone()[0] count2 = self.env['res.partner'].search_count([]) self.assertIsInstance(count1, int) self.assertIsInstance(count2, int) self.assertEqual(count1, count2) @mute_logger('odoo.models') def test_05_immutable(self): """ Check that a recordset remains the same, even after updates. """ domain = [('name', 'ilike', 'g'), ('id', 'in', self.partners.ids)] partners = self.env['res.partner'].search(domain) self.assertTrue(partners) ids = partners.ids # modify those partners, and check that partners has not changed partners.write({'active': False}) self.assertEqual(ids, partners.ids) # redo the search, and check that the result is now empty partners2 = self.env['res.partner'].search(domain) self.assertFalse(partners2) @mute_logger('odoo.models') def test_06_fields(self): """ Check that relation fields return records, recordsets or nulls. """ user = self.env.user self.assertIsRecord(user, 'res.users') self.assertIsRecord(user.partner_id, 'res.partner') self.assertIsRecordset(user.groups_id, 'res.groups') for name, field in self.partners._fields.items(): if field.type == 'many2one': for p in self.partners: self.assertIsRecord(p[name], field.comodel_name) elif field.type == 'reference': for p in self.partners: if p[name]: self.assertIsRecord(p[name], field.comodel_name) elif field.type in ('one2many', 'many2many'): for p in self.partners: self.assertIsRecordset(p[name], field.comodel_name) @mute_logger('odoo.models') def test_07_null(self): """ Check behavior of null instances. """ # select a partner without a parent partner = self.env['res.partner'].search([('parent_id', '=', False), ('id', 'in', self.partners.ids)])[0] # check partner and related null instances self.assertTrue(partner) self.assertIsRecord(partner, 'res.partner') self.assertFalse(partner.parent_id) self.assertIsNull(partner.parent_id, 'res.partner') self.assertIs(partner.parent_id.id, False) self.assertFalse(partner.parent_id.user_id) self.assertIsNull(partner.parent_id.user_id, 'res.users') self.assertIs(partner.parent_id.user_id.name, False) self.assertFalse(partner.parent_id.user_id.groups_id) self.assertIsRecordset(partner.parent_id.user_id.groups_id, 'res.groups') @mute_logger('odoo.models') def test_40_new_new(self): """ Call new-style methods in the new API style. """ partners = self.env['res.partner'].search([('name', 'ilike', 'g'), ('id', 'in', self.partners.ids)]) self.assertTrue(partners) # call method write on partners itself, and check its effect partners.write({'active': False}) for p in partners: self.assertFalse(p.active) @mute_logger('odoo.models') def test_45_new_new(self): """ Call new-style methods on records (new API style). """ partners = self.env['res.partner'].search([('name', 'ilike', 'g'), ('id', 'in', self.partners.ids)]) self.assertTrue(partners) # call method write on partner records, and check its effects for p in partners: p.write({'active': False}) for p in partners: self.assertFalse(p.active) @mute_logger('odoo.models') @mute_logger('odoo.addons.base.models.ir_model') def test_50_environment(self): """ Test environment on records. """ # partners and reachable records are attached to self.env partners = self.env['res.partner'].search([('name', 'ilike', 'j'), ('id', 'in', self.partners.ids)]) self.assertEqual(partners.env, self.env) for x in (partners, partners[0], partners[0].company_id): self.assertEqual(x.env, self.env) for p in partners: self.assertEqual(p.env, self.env) # check that the current user can read and modify company data partners[0].company_id.name partners[0].company_id.write({'name': 'Fools'}) # create an environment with a demo user demo = self.env['res.users'].create({ 'name': 'test_environment_demo', 'login': 'test_environment_demo', 'password': 'test_environment_demo', }) demo_env = self.env(user=demo) self.assertNotEqual(demo_env, self.env) # partners and related records are still attached to self.env self.assertEqual(partners.env, self.env) for x in (partners, partners[0], partners[0].company_id): self.assertEqual(x.env, self.env) for p in partners: self.assertEqual(p.env, self.env) # create record instances attached to demo_env demo_partners = partners.with_user(demo) self.assertEqual(demo_partners.env, demo_env) for x in (demo_partners, demo_partners[0], demo_partners[0].company_id): self.assertEqual(x.env, demo_env) for p in demo_partners: self.assertEqual(p.env, demo_env) # demo user can read but not modify company data demo_partner = self.env['res.partner'].search([('name', '=', 'Landon Roberts')]).with_user(demo) self.assertTrue(demo_partner.company_id, 'This partner is supposed to be linked to a company') demo_partner.company_id.name with self.assertRaises(AccessError): demo_partner.company_id.write({'name': 'Pricks'}) # remove demo user from all groups demo.write({'groups_id': [Command.clear()]}) # demo user can no longer access partner data with self.assertRaises(AccessError): demo_partner.company_id.name @mute_logger('odoo.models') def test_55_environment_lang(self): """ Check the record env.lang behavior """ partner = self.partner_demo self.env['res.lang']._activate_lang('fr_FR') self.assertEqual(partner.with_context(lang=None).env.lang, None, 'None lang context should have None env.lang') self.assertEqual(partner.with_context(lang='en_US').env.lang, 'en_US', 'en_US active lang context should have en_US env.lang') self.assertEqual(partner.with_context(lang='fr_FR').env.lang, 'fr_FR', 'fr_FR active lang context should have fr_FR env.lang') self.assertEqual(partner.with_context(lang='nl_NL').env.lang, None, 'Inactive lang context lang should have None env.lang') self.assertEqual(partner.with_context(lang='Dummy').env.lang, None, 'Ilegal lang context should have None env.lang') def test_56_environment_uid_origin(self): """Check the expected behavior of `env.uid_origin`""" user_demo = self.env.ref('base.user_demo') user_admin = self.env.ref('base.user_admin') self.assertEqual(self.env.uid_origin, None) self.assertEqual(self.env['base'].with_user(user_demo).env.uid_origin, user_demo.id) self.assertEqual(self.env['base'].with_user(user_demo).with_user(user_admin).env.uid_origin, user_demo.id) self.assertEqual(self.env['base'].with_user(user_admin).with_user(user_demo).env.uid_origin, user_admin.id) @mute_logger('odoo.models') def test_60_cache(self): """ Check the record cache behavior """ Partners = self.env['res.partner'] pids = [] data = { 'partner One': ['Partner One - One', 'Partner One - Two'], 'Partner Two': ['Partner Two - One'], 'Partner Three': ['Partner Three - One'], } for p in data: pids.append(Partners.create({ 'name': p, 'child_ids': [Command.create({'name': c}) for c in data[p]], }).id) partners = Partners.search([('id', 'in', pids)]) partner1, partner2 = partners[0], partners[1] children1, children2 = partner1.child_ids, partner2.child_ids self.assertTrue(children1) self.assertTrue(children2) # take a child contact child = children1[0] self.assertEqual(child.parent_id, partner1) self.assertIn(child, partner1.child_ids) self.assertNotIn(child, partner2.child_ids) # fetch data in the cache for p in partners: p.name, p.company_id.name, p.user_id.name, p.contact_address self.env.cache.check(self.env) # change its parent child.write({'parent_id': partner2.id}) self.env.cache.check(self.env) # check recordsets self.assertEqual(child.parent_id, partner2) self.assertNotIn(child, partner1.child_ids) self.assertIn(child, partner2.child_ids) self.assertEqual(set(partner1.child_ids + child), set(children1)) self.assertEqual(set(partner2.child_ids), set(children2 + child)) self.env.cache.check(self.env) # delete it child.unlink() self.env.cache.check(self.env) # check recordsets self.assertEqual(set(partner1.child_ids), set(children1) - set([child])) self.assertEqual(set(partner2.child_ids), set(children2)) self.env.cache.check(self.env) # convert from the cache format to the write format partner = partner1 partner.country_id, partner.child_ids data = partner._convert_to_write(partner._cache) self.assertEqual(data['country_id'], partner.country_id.id) self.assertEqual(data['child_ids'], [Command.set(partner.child_ids.ids)]) @mute_logger('odoo.models') def test_60_prefetch(self): """ Check the record cache prefetching """ partners = self.env['res.partner'].search([('id', 'in', self.partners.ids)], limit=models.PREFETCH_MAX) self.assertTrue(len(partners) > 1) # all the records in partners are ready for prefetching self.assertItemsEqual(partners.ids, partners._prefetch_ids) # reading ONE partner should fetch them ALL for partner in partners: state = partner.state_id break partner_ids_with_field = [partner.id for partner in partners if 'state_id' in partner._cache] self.assertItemsEqual(partner_ids_with_field, partners.ids) # partners' states are ready for prefetching state_ids = { partner._cache['state_id'] for partner in partners if partner._cache['state_id'] is not None } self.assertTrue(len(state_ids) > 1) self.assertItemsEqual(state_ids, state._prefetch_ids) # reading ONE partner country should fetch ALL partners' countries for partner in partners: if partner.state_id: partner.state_id.name break state_ids_with_field = [st.id for st in partners.state_id if 'name' in st._cache] self.assertItemsEqual(state_ids_with_field, state_ids) @mute_logger('odoo.models') def test_60_prefetch_model(self): """ Check the prefetching model. """ partners = self.env['res.partner'].search([('id', 'in', self.partners.ids)], limit=models.PREFETCH_MAX) self.assertTrue(partners) def same_prefetch(a, b): self.assertEqual(set(a._prefetch_ids), set(b._prefetch_ids)) def diff_prefetch(a, b): self.assertNotEqual(set(a._prefetch_ids), set(b._prefetch_ids)) # the recordset operations below use different prefetch sets diff_prefetch(partners, partners.browse()) diff_prefetch(partners, partners[0]) diff_prefetch(partners, partners[:5]) # the recordset operations below share the prefetch set same_prefetch(partners, partners.browse(partners.ids)) same_prefetch(partners, partners.with_user(self.user_demo)) same_prefetch(partners, partners.with_context(active_test=False)) same_prefetch(partners, partners[:10].with_prefetch(partners._prefetch_ids)) # iteration and relational fields should use the same prefetch set self.assertEqual(type(partners).country_id.type, 'many2one') self.assertEqual(type(partners).bank_ids.type, 'one2many') self.assertEqual(type(partners).category_id.type, 'many2many') vals0 = { 'name': 'Empty relational fields', 'country_id': False, 'bank_ids': [], 'category_id': [], } vals1 = { 'name': 'Non-empty relational fields', 'country_id': self.ref('base.be'), 'bank_ids': [Command.create({'acc_number': 'FOO42'})], 'category_id': [Command.link(self.partner_category.id)], } partners = partners.create(vals0) + partners.create(vals1) for partner in partners: same_prefetch(partner, partners) same_prefetch(partner.country_id, partners.country_id) same_prefetch(partner.bank_ids, partners.bank_ids) same_prefetch(partner.category_id, partners.category_id) @mute_logger('odoo.models') def test_60_prefetch_read(self): """ Check that reading a field computes it on self only. """ Partner = self.env['res.partner'] field = type(Partner).company_type self.assertTrue(field.compute and not field.store) partner1 = Partner.create({'name': 'Foo'}) partner2 = Partner.create({'name': 'Bar', 'parent_id': partner1.id}) self.assertEqual(partner1.child_ids, partner2) # reading partner1 should not prefetch 'company_type' on partner2 self.env.clear() partner1 = partner1.with_prefetch() partner1.read(['company_type']) self.assertIn('company_type', partner1._cache) self.assertNotIn('company_type', partner2._cache) # reading partner1 should not prefetch 'company_type' on partner2 self.env.clear() partner1 = partner1.with_prefetch() partner1.read(['child_ids', 'company_type']) self.assertIn('company_type', partner1._cache) self.assertNotIn('company_type', partner2._cache) def test_60_reversed(self): records = self.partners self.assertGreater(len(records), 1) # check order self.assertEqual(list(reversed(records)), list(reversed(list(records)))) first = next(iter(records)) last = next(reversed(records)) self.assertEqual(first, records[0]) self.assertEqual(last, records[-1]) # check prefetching prefetch_ids = records.ids reversed_ids = [record.id for record in reversed(records)] self.assertEqual(list(first._prefetch_ids), prefetch_ids) self.assertEqual(list(last._prefetch_ids), reversed_ids) self.assertEqual(list(reversed(first._prefetch_ids)), reversed_ids) self.assertEqual(list(reversed(last._prefetch_ids)), prefetch_ids) # check prefetching across many2one field prefetch_ids = records.state_id.ids reversed_ids = list(unique( record.state_id.id for record in reversed(records) if record.state_id )) self.assertEqual(list(first.state_id._prefetch_ids), prefetch_ids) self.assertEqual(list(last.state_id._prefetch_ids), reversed_ids) self.assertEqual(list(reversed(first.state_id._prefetch_ids)), reversed_ids) self.assertEqual(list(reversed(last.state_id._prefetch_ids)), prefetch_ids) # check prefetching across x2many field prefetch_ids = records.child_ids.ids reversed_ids = list(unique( child.id for record in reversed(records) for child in record.child_ids )) self.assertEqual(list(first.child_ids._prefetch_ids), prefetch_ids) self.assertEqual(list(last.child_ids._prefetch_ids), reversed_ids) self.assertEqual(list(reversed(first.child_ids._prefetch_ids)), reversed_ids) self.assertEqual(list(reversed(last.child_ids._prefetch_ids)), prefetch_ids) @mute_logger('odoo.models') def test_70_one(self): """ Check method one(). """ # check with many records ps = self.env['res.partner'].search([('name', 'ilike', 'a'), ('id', 'in', self.partners.ids)]) self.assertTrue(len(ps) > 1) with self.assertRaises(ValueError): ps.ensure_one() p1 = ps[0] self.assertEqual(len(p1), 1) self.assertEqual(p1.ensure_one(), p1) p0 = self.env['res.partner'].browse() self.assertEqual(len(p0), 0) with self.assertRaises(ValueError): p0.ensure_one() @mute_logger('odoo.models') def test_80_contains(self): """ Test membership on recordset. """ p1 = self.partners[0] ps = self.partners self.assertTrue(p1 in ps) with self.assertRaisesRegex(TypeError, r"unsupported operand types in: 42 in res\.partner.*"): 42 in ps with self.assertRaisesRegex(TypeError, r"inconsistent models in: ir\.ui\.menu.* in res\.partner.*"): self.env['ir.ui.menu'] in ps @mute_logger('odoo.models') def test_80_lazy_contains(self): """ Test membership on recordset. """ p1 = lazy(lambda: self.partners[0]) ps = lazy(lambda: self.partners) self.assertTrue(p1 in ps) with self.assertRaisesRegex(TypeError, r"unsupported operand types in: 42 in res\.partner.*"): lazy(lambda: 42) in ps with self.assertRaisesRegex(TypeError, r"inconsistent models in: ir\.ui\.menu.* in res\.partner.*"): lazy(lambda: self.env['ir.ui.menu']) in ps @mute_logger('odoo.models') def test_80_set_operations(self): """ Check set operations on recordsets. """ pa = self.env['res.partner'].search([('name', 'ilike', 'a'), ('id', 'in', self.partners.ids)]) pb = self.env['res.partner'].search([('name', 'ilike', 'b'), ('id', 'in', self.partners.ids)]) self.assertTrue(pa) self.assertTrue(pb) self.assertTrue(set(pa) & set(pb)) concat = pa + pb self.assertEqual(list(concat), list(pa) + list(pb)) self.assertEqual(len(concat), len(pa) + len(pb)) difference = pa - pb self.assertEqual(len(difference), len(set(difference))) self.assertEqual(set(difference), set(pa) - set(pb)) self.assertLessEqual(difference, pa) intersection = pa & pb self.assertEqual(len(intersection), len(set(intersection))) self.assertEqual(set(intersection), set(pa) & set(pb)) self.assertLessEqual(intersection, pa) self.assertLessEqual(intersection, pb) union = pa | pb self.assertEqual(len(union), len(set(union))) self.assertEqual(set(union), set(pa) | set(pb)) self.assertGreaterEqual(union, pa) self.assertGreaterEqual(union, pb) # one cannot mix different models with set operations ps = pa ms = self.env['ir.ui.menu'].search([]) self.assertNotEqual(ps._name, ms._name) self.assertNotEqual(ps, ms) with self.assertRaisesRegex(TypeError, r"unsupported operand types in: res\.partner.* \+ 'string'"): ps + 'string' with self.assertRaisesRegex(TypeError, r"inconsistent models in: res\.partner.* \+ ir\.ui\.menu.*"): ps + ms with self.assertRaisesRegex(TypeError, r"inconsistent models in: res\.partner.* - ir\.ui\.menu.*"): ps - ms with self.assertRaisesRegex(TypeError, r"inconsistent models in: res\.partner.* & ir\.ui\.menu.*"): ps & ms with self.assertRaisesRegex(TypeError, r"inconsistent models in: res\.partner.* \| ir\.ui\.menu.*"): ps | ms with self.assertRaises(TypeError): ps < ms with self.assertRaises(TypeError): ps <= ms with self.assertRaises(TypeError): ps > ms with self.assertRaises(TypeError): ps >= ms @mute_logger('odoo.models') def test_80_lazy_set_operations(self): """ Check set operations on recordsets. """ pa = lazy(lambda: self.env['res.partner'].search([('name', 'ilike', 'a'), ('id', 'in', self.partners.ids)])) pb = lazy(lambda: self.env['res.partner'].search([('name', 'ilike', 'b'), ('id', 'in', self.partners.ids)])) self.assertTrue(pa) self.assertTrue(pb) self.assertTrue(set(pa) & set(pb)) concat = pa + pb self.assertEqual(list(concat), list(pa) + list(pb)) self.assertEqual(len(concat), len(pa) + len(pb)) difference = pa - pb self.assertEqual(len(difference), len(set(difference))) self.assertEqual(set(difference), set(pa) - set(pb)) self.assertLessEqual(difference, pa) intersection = pa & pb self.assertEqual(len(intersection), len(set(intersection))) self.assertEqual(set(intersection), set(pa) & set(pb)) self.assertLessEqual(intersection, pa) self.assertLessEqual(intersection, pb) union = pa | pb self.assertEqual(len(union), len(set(union))) self.assertEqual(set(union), set(pa) | set(pb)) self.assertGreaterEqual(union, pa) self.assertGreaterEqual(union, pb) # one cannot mix different models with set operations ps = pa ms = lazy(lambda: self.env['ir.ui.menu'].search([])) self.assertNotEqual(ps._name, ms._name) self.assertNotEqual(ps, ms) with self.assertRaisesRegex(TypeError, r"unsupported operand types in: res\.partner.* \+ 'string'"): ps + 'string' with self.assertRaisesRegex(TypeError, r"inconsistent models in: res\.partner.* \+ ir\.ui\.menu.*"): ps + ms with self.assertRaisesRegex(TypeError, r"inconsistent models in: res\.partner.* - ir\.ui\.menu.*"): ps - ms with self.assertRaisesRegex(TypeError, r"inconsistent models in: res\.partner.* & ir\.ui\.menu.*"): ps & ms with self.assertRaisesRegex(TypeError, r"inconsistent models in: res\.partner.* \| ir\.ui\.menu.*"): ps | ms with self.assertRaises(TypeError): ps < ms with self.assertRaises(TypeError): ps <= ms with self.assertRaises(TypeError): ps > ms with self.assertRaises(TypeError): ps >= ms @mute_logger('odoo.models') def test_80_filter(self): """ Check filter on recordsets. """ ps = self.partners customers = ps.browse([p.id for p in ps if p.employee]) # filter on a single field self.assertEqual(ps.filtered(lambda p: p.employee), customers) self.assertEqual(ps.filtered('employee'), customers) # filter on a sequence of fields self.assertEqual( ps.filtered(lambda p: p.parent_id.employee), ps.filtered('parent_id.employee') ) @mute_logger('odoo.models') def test_80_map(self): """ Check map on recordsets. """ ps = self.partners parents = ps.browse() for p in ps: parents |= p.parent_id # map a single field self.assertEqual(ps.mapped(lambda p: p.parent_id), parents) self.assertEqual(ps.mapped('parent_id'), parents) self.assertEqual(ps.parent_id, parents) # map a sequence of fields self.assertEqual( ps.mapped(lambda p: p.parent_id.name), [p.parent_id.name for p in ps] ) self.assertEqual( ps.mapped('parent_id.name'), [p.name for p in parents] ) self.assertEqual( ps.parent_id.mapped('name'), [p.name for p in parents] ) # map an empty sequence of fields self.assertEqual(ps.mapped(''), ps) @mute_logger('odoo.models') def test_80_sorted(self): """ Check sorted on recordsets. """ ps = self.env['res.partner'].search([('id', 'in', self.partners.ids)]) # sort by model order qs = ps[:len(ps) // 2] + ps[len(ps) // 2:] self.assertEqual(qs.sorted().ids, ps.ids) # sort by name, with a function or a field name by_name_ids = [p.id for p in sorted(ps, key=lambda p: p.name)] self.assertEqual(ps.sorted(lambda p: p.name).ids, by_name_ids) self.assertEqual(ps.sorted('name').ids, by_name_ids) # sort by inverse name, with a field name by_name_ids = [p.id for p in sorted(ps, key=lambda p: p.name, reverse=True)] self.assertEqual(ps.sorted('name', reverse=True).ids, by_name_ids) # sorted doesn't filter out new records but don't sort them either (limitation) new_p = self.env['res.partner'].new({ 'child_ids': [ Command.create({'name': 'z'}), Command.create({'name': 'a'}), ], }) self.assertEqual(len(new_p.child_ids.sorted()), 2) # sorted keeps the _prefetch_ids partners_with_children = self.env['res.partner'].create([ { 'name': 'required', 'child_ids': [ Command.create({'name': 'z'}), Command.create({'name': 'a'}), ], }, { 'name': 'required', 'child_ids': [ Command.create({'name': 'z'}), Command.create({'name': 'a'}), ], }, ]) partners_with_children.invalidate_model(['name']) # Only one query to fetch name of children of each partner with self.assertQueryCount(1): for partner in partners_with_children: partner.child_ids.sorted('id').mapped('name') def test_group_on(self): p0, p1, p2 = self.env['res.partner'].create([ {'name': "bob", 'function': "guest"}, {'name': "james", 'function': "host"}, {'name': "rhod", 'function': "guest"} ]) pn = self.env['res.partner'].new({'name': 'alex', 'function': "host"}) with self.subTest("Should work with mixes of db and new records"): self.assertEqual( (p0 | p1 | p2 | pn).grouped('function'), {'guest': p0 | p2, 'host': p1 | pn} ) self.assertEqual( (p0 | p1 | p2 | pn).grouped(lambda r: len(r.name)), {3: p0, 4: p2 | pn, 5: p1}, ) with self.subTest("Should allow cross-group prefetching"): byfn = (p0 | p1 | p2).grouped('function') self.env.invalidate_all(flush=False) self.assertFalse(self.env.cache._data, "ensure the cache is empty") self.assertEqual(byfn['guest'].mapped('name'), ['bob', 'rhod']) # name should have been prefetched by previous statement (on guest # group), so should be nothing here with self.assertQueries([]): _ = byfn['host'].name class TestExternalAPI(SavepointCaseWithUserDemo): def test_call_kw(self): """kwargs is not modified by the execution of the call""" partner = self.env['res.partner'].create({'name': 'MyPartner1'}) args = (partner.ids, ['name']) kwargs = {'context': {'test': True}} api.call_kw(self.env['res.partner'], 'read', args, kwargs) self.assertEqual(kwargs, {'context': {'test': True}})