Files
odoo_source/odoo/addons/base/tests/test_api.py
T
ef00294e71 [IMP] core: store translated fields as JSONB columns
Translated fields no longer use the model ir.translation.  Instead they store
all their values as JSON, and store them into JSONB columns in the model's
table.  The field's column value is either NULL or a JSON dict mapping language
codes to text (the field's value in the corresponding language), and must
contain an entry for key 'en_US' (as it is used as a fallback for all other
languages).  Empty text is allowed in translation values, but not NULL.

Here are examples for a field with translate=True:

    NULL
    {"en_US": "Foo"}
    {"en_US": "Foo", "fr_FR": "Bar", "nl_NL": "Baz"}
    {"en_US": "Foo", "fr_FR": "", "nl_NL": "Baz"}

Like before, writing False to the field makes it NULL, i.e., False in all
languages.  However, writing "" to the field makes its value empty in the
current language, but does not discard the values in the other languages.

Here are examples for a field with translate=xml_translate:

    NULL
    {"en_US": "<div>Foo<p>Bar</p></div>", "fr_FR": "<div>Fou<p>Barre</p></div>"}

Change for callable(translate) fields: one can now write any value in any
language on such a field.  The new value will be adapted in all languages, based
on the mapping of terms between languages in the old values.  Basically the
structure of the value must remain the same in all languages, like before.

Reading a translated field is now both simpler and faster than the former
implementation.  We fetch the value of the field in the current language by
coalescing its value with the 'en_US' value of the field:

    SELECT id, COALESCE(name->>'fr_FR', name->>'en_US') AS name ...

The raw cache of the field contains either None or a dict which is conceptually
a subset of the JSON value in database (except for missing languages).  For the
sake of simplicity, most cache operations deal with the dict and return the text
value in the current language.

Trigram indexes have been adapted to the new storing strategy, and should enable
to search in any language.  Before this change, only the source value of the
field ('en_US') could be indexed.

Computed stored translated fields are not supported by the framework, because of
the complexity of the computation itself: the field would need to be computed in
all active languages.  We chose to not provide any hook to compute a field in
all languages at once, and the framework always invokes a compute method once to
recompute it.

Code translations are no longer stored into the database.  They become static,
and are extracted from the PO files when needed.  The worker simply uses a cache
with extracted code translations for performance.  This is reasonable, since
fr_FR code translations for all modules takes around 2MB of memory, and the
cache can be shared among all registries in the worker.  Changing code
translations requires to update the corresponding PO file and reloading the
worker(s).

Performance summary:
 (+) reading 'model' translated fields is faster
 (+) reading 'model_terms' translated fields is much faster (no need to inject
     translations into the source value)
 (+) searching translated fields with operator 'ilike' is much faster when the
     field is indexed with 'trigram'
 (+) updating translated fields requires less ORM flushing
 (-) importing translations from PO files is 2x slower

Some extra fixes:
 - make field 'name' of ir.actions.actions translated; because of the PG
   inheritance, this is necessary to make the column definition consistent in
   all models that inherit from ir.actions.actions.
 - add some backend API for the web/website client for editing translations
 - move methods get_field_string() to model ir.model.fields
 - move _load_module_terms to model ir.module.module
 - adapt tests in test_impex, test_new_api
 - because env.lang is injected into SQL queries, its returned value is
   now guaranteed to correspond to a valid active language or None
 - remove wizard to insert missing translations (no longer makes sense)

task-id: 2081307

Co-authored-by: Fabien Pinckaers <fp@openerp.com>
Co-authored-by: Raphael Collet <rco@odoo.com>
2022-09-15 22:37:50 +02:00

674 lines
27 KiB
Python

# -*- 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 method with count=True. """
self.cr.execute("SELECT COUNT(*) FROM res_partner WHERE active")
count1 = self.cr.fetchone()[0]
count2 = self.env['res.partner'].search([], count=True)
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')
@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)
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}})