# # test cases for new-style fields # from datetime import date, datetime from collections import defaultdict from openerp.exceptions import AccessError from openerp.tests import common from openerp.exceptions import except_orm class TestNewFields(common.TransactionCase): def test_00_basics(self): """ test accessing new fields """ # find a discussion discussion = self.env.ref('test_new_api.discussion_0') # read field as a record attribute or as a record item self.assertIsInstance(discussion.name, basestring) self.assertIsInstance(discussion['name'], basestring) self.assertEqual(discussion['name'], discussion.name) # read it with method read() values = discussion.read(['name'])[0] self.assertEqual(values['name'], discussion.name) def test_01_basic_get_assertion(self): """ test item getter """ # field access works on single record record = self.env.ref('test_new_api.message_0_0') self.assertEqual(len(record), 1) ok = record.body # field access fails on multiple records records = self.env['test_new_api.message'].search([]) assert len(records) > 1 with self.assertRaises(except_orm): faulty = records.body def test_01_basic_set_assertion(self): """ test item setter """ # field assignment works on single record record = self.env.ref('test_new_api.message_0_0') self.assertEqual(len(record), 1) record.body = 'OK' # field assignment fails on multiple records records = self.env['test_new_api.message'].search([]) assert len(records) > 1 with self.assertRaises(except_orm): records.body = 'Faulty' def test_10_computed(self): """ check definition of computed fields """ # by default function fields are not stored and readonly field = self.env['test_new_api.message']._fields['size'] self.assertFalse(field.store) self.assertTrue(field.readonly) field = self.env['test_new_api.message']._fields['name'] self.assertTrue(field.store) self.assertTrue(field.readonly) def test_10_non_stored(self): """ test non-stored fields """ # find messages for message in self.env['test_new_api.message'].search([]): # check definition of field self.assertEqual(message.size, len(message.body or '')) # check recomputation after record is modified size = message.size message.write({'body': (message.body or '') + "!!!"}) self.assertEqual(message.size, size + 3) # special case: computed field without dependency must be computed record = self.env['test_new_api.mixed'].create({}) self.assertTrue(record.now) def test_11_stored(self): """ test stored fields """ # find the demo discussion discussion = self.env.ref('test_new_api.discussion_0') self.assertTrue(len(discussion.messages) > 0) # check messages name0 = discussion.name or "" for message in discussion.messages: self.assertEqual(message.name, "[%s] %s" % (name0, message.author.name)) # modify discussion name, and check again messages discussion.name = name1 = 'Talking about stuff...' for message in discussion.messages: self.assertEqual(message.name, "[%s] %s" % (name1, message.author.name)) # switch message from discussion, and check again name2 = 'Another discussion' discussion2 = discussion.copy({'name': name2}) message2 = discussion.messages[0] message2.discussion = discussion2 for message in discussion2.messages: self.assertEqual(message.name, "[%s] %s" % (name2, message.author.name)) def test_12_recursive(self): """ test recursively dependent fields """ Category = self.env['test_new_api.category'] abel = Category.create({'name': 'Abel'}) beth = Category.create({'name': 'Bethany'}) cath = Category.create({'name': 'Catherine'}) dean = Category.create({'name': 'Dean'}) ewan = Category.create({'name': 'Ewan'}) finn = Category.create({'name': 'Finnley'}) gabe = Category.create({'name': 'Gabriel'}) cath.parent = finn.parent = gabe abel.parent = beth.parent = cath dean.parent = ewan.parent = finn self.assertEqual(abel.display_name, "Gabriel / Catherine / Abel") self.assertEqual(beth.display_name, "Gabriel / Catherine / Bethany") self.assertEqual(cath.display_name, "Gabriel / Catherine") self.assertEqual(dean.display_name, "Gabriel / Finnley / Dean") self.assertEqual(ewan.display_name, "Gabriel / Finnley / Ewan") self.assertEqual(finn.display_name, "Gabriel / Finnley") self.assertEqual(gabe.display_name, "Gabriel") ewan.parent = cath self.assertEqual(ewan.display_name, "Gabriel / Catherine / Ewan") cath.parent = finn self.assertEqual(ewan.display_name, "Gabriel / Finnley / Catherine / Ewan") def test_12_cascade(self): """ test computed field depending on computed field """ message = self.env.ref('test_new_api.message_0_0') message.invalidate_cache() double_size = message.double_size self.assertEqual(double_size, message.size) def test_13_inverse(self): """ test inverse computation of fields """ Category = self.env['test_new_api.category'] abel = Category.create({'name': 'Abel'}) beth = Category.create({'name': 'Bethany'}) cath = Category.create({'name': 'Catherine'}) dean = Category.create({'name': 'Dean'}) ewan = Category.create({'name': 'Ewan'}) finn = Category.create({'name': 'Finnley'}) gabe = Category.create({'name': 'Gabriel'}) self.assertEqual(ewan.display_name, "Ewan") ewan.display_name = "Abel / Bethany / Catherine / Erwan" self.assertEqual(beth.parent, abel) self.assertEqual(cath.parent, beth) self.assertEqual(ewan.parent, cath) self.assertEqual(ewan.name, "Erwan") def test_14_search(self): """ test search on computed fields """ discussion = self.env.ref('test_new_api.discussion_0') # determine message sizes sizes = set(message.size for message in discussion.messages) # search for messages based on their size for size in sizes: messages0 = self.env['test_new_api.message'].search( [('discussion', '=', discussion.id), ('size', '<=', size)]) messages1 = self.env['test_new_api.message'].browse() for message in discussion.messages: if message.size <= size: messages1 += message self.assertEqual(messages0, messages1) def test_15_constraint(self): """ test new-style Python constraints """ discussion = self.env.ref('test_new_api.discussion_0') # remove oneself from discussion participants: we can no longer create # messages in discussion discussion.participants -= self.env.user with self.assertRaises(Exception): self.env['test_new_api.message'].create({'discussion': discussion.id, 'body': 'Whatever'}) # make sure that assertRaises() does not leave fields to recompute self.assertFalse(self.env.has_todo()) # put back oneself into discussion participants: now we can create # messages in discussion discussion.participants += self.env.user self.env['test_new_api.message'].create({'discussion': discussion.id, 'body': 'Whatever'}) def test_20_float(self): """ test float fields """ record = self.env['test_new_api.mixed'].create({}) # assign value, and expect rounding record.write({'number': 2.4999999999999996}) self.assertEqual(record.number, 2.50) # same with field setter record.number = 2.4999999999999996 self.assertEqual(record.number, 2.50) def test_21_date(self): """ test date fields """ record = self.env['test_new_api.mixed'].create({}) # one may assign False or None record.date = None self.assertFalse(record.date) # one may assign date and datetime objects record.date = date(2012, 05, 01) self.assertEqual(record.date, '2012-05-01') record.date = datetime(2012, 05, 01, 10, 45, 00) self.assertEqual(record.date, '2012-05-01') # one may assign dates in the default format, and it must be checked record.date = '2012-05-01' self.assertEqual(record.date, '2012-05-01') with self.assertRaises(ValueError): record.date = '12-5-1' def test_22_selection(self): """ test selection fields """ record = self.env['test_new_api.mixed'].create({}) # one may assign False or None record.lang = None self.assertFalse(record.lang) # one may assign a value, and it must be checked for language in self.env['res.lang'].search([]): record.lang = language.code with self.assertRaises(ValueError): record.lang = 'zz_ZZ' def test_23_relation(self): """ test relation fields """ demo = self.env.ref('base.user_demo') message = self.env.ref('test_new_api.message_0_0') # check environment of record and related records self.assertEqual(message.env, self.env) self.assertEqual(message.discussion.env, self.env) demo_env = self.env(user=demo) self.assertNotEqual(demo_env, self.env) # check environment of record and related records self.assertEqual(message.env, self.env) self.assertEqual(message.discussion.env, self.env) # "migrate" message into demo_env, and check again demo_message = message.sudo(demo) self.assertEqual(demo_message.env, demo_env) self.assertEqual(demo_message.discussion.env, demo_env) # assign record's parent to a record in demo_env message.discussion = message.discussion.copy({'name': 'Copy'}) # both message and its parent field must be in self.env self.assertEqual(message.env, self.env) self.assertEqual(message.discussion.env, self.env) def test_24_reference(self): """ test reference fields. """ record = self.env['test_new_api.mixed'].create({}) # one may assign False or None record.reference = None self.assertFalse(record.reference) # one may assign a user or a partner... record.reference = self.env.user self.assertEqual(record.reference, self.env.user) record.reference = self.env.user.partner_id self.assertEqual(record.reference, self.env.user.partner_id) # ... but no record from a model that starts with 'ir.' with self.assertRaises(ValueError): record.reference = self.env['ir.model'].search([], limit=1) def test_25_related(self): """ test related fields. """ message = self.env.ref('test_new_api.message_0_0') discussion = message.discussion # by default related fields are not stored field = message._fields['discussion_name'] self.assertFalse(field.store) self.assertFalse(field.readonly) # check value of related field self.assertEqual(message.discussion_name, discussion.name) # change discussion name, and check result discussion.name = 'Foo' self.assertEqual(message.discussion_name, 'Foo') # change discussion name via related field, and check result message.discussion_name = 'Bar' self.assertEqual(discussion.name, 'Bar') self.assertEqual(message.discussion_name, 'Bar') # change discussion name via related field on several records discussion1 = discussion.create({'name': 'X1'}) discussion2 = discussion.create({'name': 'X2'}) discussion1.participants = discussion2.participants = self.env.user message1 = message.create({'discussion': discussion1.id}) message2 = message.create({'discussion': discussion2.id}) self.assertEqual(message1.discussion_name, 'X1') self.assertEqual(message2.discussion_name, 'X2') (message1 + message2).write({'discussion_name': 'X3'}) self.assertEqual(discussion1.name, 'X3') self.assertEqual(discussion2.name, 'X3') # search on related field, and check result search_on_related = self.env['test_new_api.message'].search([('discussion_name', '=', 'Bar')]) search_on_regular = self.env['test_new_api.message'].search([('discussion.name', '=', 'Bar')]) self.assertEqual(search_on_related, search_on_regular) # check that field attributes are copied message_field = message.fields_get(['discussion_name'])['discussion_name'] discussion_field = discussion.fields_get(['name'])['name'] self.assertEqual(message_field['help'], discussion_field['help']) def test_26_inherited(self): """ test inherited fields. """ # a bunch of fields are inherited from res_partner for user in self.env['res.users'].search([]): partner = user.partner_id for field in ('is_company', 'name', 'email', 'country_id'): self.assertEqual(getattr(user, field), getattr(partner, field)) self.assertEqual(user[field], partner[field]) def test_30_read(self): """ test computed fields as returned by read(). """ discussion = self.env.ref('test_new_api.discussion_0') for message in discussion.messages: display_name = message.display_name size = message.size data = message.read(['display_name', 'size'])[0] self.assertEqual(data['display_name'], display_name) self.assertEqual(data['size'], size) def test_31_prefetch(self): """ test prefetch of records handle AccessError """ Category = self.env['test_new_api.category'] cat_1 = Category.create({'name': 'NOACCESS'}).id cat_2 = Category.create({'name': 'ACCESS', 'parent': cat_1}).id self.env.clear() cat = Category.browse(cat_2) self.assertEqual(cat.name, 'ACCESS') # both categories should be in prefetch ids self.assertSetEqual(self.env.prefetch[Category._name], set([cat_1, cat_2])) # but due to our (lame) overwrite of `read`, it should not forbid us to read records we have access to self.assertFalse(len(cat.discussions)) self.assertEqual(cat.parent.id, cat_1) with self.assertRaises(AccessError): Category.browse(cat_1).name def test_40_new(self): """ test new records. """ discussion = self.env.ref('test_new_api.discussion_0') # create a new message message = self.env['test_new_api.message'].new() self.assertFalse(message.id) # assign some fields; should have no side effect message.discussion = discussion message.body = BODY = "May the Force be with you." self.assertEqual(message.discussion, discussion) self.assertEqual(message.body, BODY) self.assertFalse(message.author) self.assertNotIn(message, discussion.messages) # check computed values of fields self.assertEqual(message.name, "[%s] %s" % (discussion.name, '')) self.assertEqual(message.size, len(BODY)) def test_41_defaults(self): """ test default values. """ fields = ['discussion', 'body', 'author', 'size'] defaults = self.env['test_new_api.message'].default_get(fields) self.assertEqual(defaults, {'author': self.env.uid}) defaults = self.env['test_new_api.mixed'].default_get(['number']) self.assertEqual(defaults, {'number': 3.14}) class TestMagicFields(common.TransactionCase): def test_write_date(self): record = self.env['test_new_api.discussion'].create({'name': 'Booba'}) self.assertEqual(record.create_uid, self.env.user) self.assertEqual(record.write_uid, self.env.user)