diff options
Diffstat (limited to 'odoo/addons/test_new_api/tests/test_new_fields.py')
| -rw-r--r-- | odoo/addons/test_new_api/tests/test_new_fields.py | 681 |
1 files changed, 681 insertions, 0 deletions
diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py new file mode 100644 index 00000000000..93f3e3d881e --- /dev/null +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -0,0 +1,681 @@ +# +# test cases for new-style fields +# +from datetime import date, datetime + +from openerp.exceptions import AccessError, except_orm +from openerp.tests import common +from openerp.tools import mute_logger + + +class TestFields(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(ValueError): + 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(ValueError): + 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 """ + # a field declared with store=False should not have a column + field = self.env['test_new_api.category']._fields['dummy'] + self.assertFalse(field.store) + self.assertFalse(field.compute) + self.assertFalse(field.inverse) + + # 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 """ + def check_stored(disc): + """ Check the stored computed field on disc.messages """ + for msg in disc.messages: + self.assertEqual(msg.name, "[%s] %s" % (disc.name, msg.author.name)) + + # find the demo discussion, and check messages + discussion1 = self.env.ref('test_new_api.discussion_0') + self.assertTrue(discussion1.messages) + check_stored(discussion1) + + # modify discussion name, and check again messages + discussion1.name = 'Talking about stuff...' + check_stored(discussion1) + + # switch message from discussion, and check again + discussion2 = discussion1.copy({'name': 'Another discussion'}) + message2 = discussion1.messages[0] + message2.discussion = discussion2 + check_stored(discussion2) + + # create a new discussion with messages, and check their name + user_root = self.env.ref('base.user_root') + user_demo = self.env.ref('base.user_demo') + discussion3 = self.env['test_new_api.discussion'].create({ + 'name': 'Stuff', + 'participants': [(4, user_root.id), (4, user_demo.id)], + 'messages': [ + (0, 0, {'author': user_root.id, 'body': 'one'}), + (0, 0, {'author': user_demo.id, 'body': 'two'}), + (0, 0, {'author': user_root.id, 'body': 'three'}), + ], + }) + check_stored(discussion3) + + # modify the discussion messages: edit the 2nd one, remove the last one + # (keep modifications in that order, as they reproduce a former bug!) + discussion3.write({ + 'messages': [ + (4, discussion3.messages[0].id), + (1, discussion3.messages[1].id, {'author': user_root.id}), + (2, discussion3.messages[2].id), + ], + }) + check_stored(discussion3) + + 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") + + record = self.env['test_new_api.compute.inverse'] + + # create/write on 'foo' should only invoke the compute method + record.counts.update(compute=0, inverse=0) + record = record.create({'foo': 'Hi'}) + self.assertEqual(record.foo, 'Hi') + self.assertEqual(record.bar, 'Hi') + self.assertEqual(record.counts, {'compute': 1, 'inverse': 0}) + + record.counts.update(compute=0, inverse=0) + record.write({'foo': 'Ho'}) + self.assertEqual(record.foo, 'Ho') + self.assertEqual(record.bar, 'Ho') + self.assertEqual(record.counts, {'compute': 1, 'inverse': 0}) + + # create/write on 'bar' should only invoke the inverse method + record.counts.update(compute=0, inverse=0) + record = record.create({'bar': 'Hi'}) + self.assertEqual(record.foo, 'Hi') + self.assertEqual(record.bar, 'Hi') + self.assertEqual(record.counts, {'compute': 0, 'inverse': 1}) + + record.counts.update(compute=0, inverse=0) + record.write({'bar': 'Ho'}) + self.assertEqual(record.foo, 'Ho') + self.assertEqual(record.bar, 'Ho') + self.assertEqual(record.counts, {'compute': 0, 'inverse': 1}) + + 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_20_monetary(self): + """ test monetary fields """ + record = self.env['test_new_api.mixed'].create({}) + self.assertTrue(record.currency_id) + self.assertEqual(record.currency_id.rounding, 0.01) + + # the conversion to cache should round the value to 14.700000000000001 + record.amount = 14.7 + self.assertNotEqual(record.amount, 14.7) + self.assertEqual(record.amount, 14.700000000000001) + + # however when stored to database, it should be serialized as 14.70 + self.cr.execute('SELECT amount FROM test_new_api_mixed WHERE id=%s', (record.id,)) + (amount,) = self.cr.fetchone() + self.assertEqual(amount, 14.7) + + 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_25_related_single(self): + """ test related fields with a single field in the path. """ + record = self.env['test_new_api.related'].create({'name': 'A'}) + self.assertEqual(record.related_name, record.name) + self.assertEqual(record.related_related_name, record.name) + + # check searching on related fields + records0 = record.search([('name', '=', 'A')]) + self.assertIn(record, records0) + records1 = record.search([('related_name', '=', 'A')]) + self.assertEqual(records1, records0) + records2 = record.search([('related_related_name', '=', 'A')]) + self.assertEqual(records2, records0) + + # check writing on related fields + record.write({'related_name': 'B'}) + self.assertEqual(record.name, 'B') + record.write({'related_related_name': 'C'}) + self.assertEqual(record.name, 'C') + + def test_25_related_multi(self): + """ test write() on several related fields based on a common computed field. """ + foo = self.env['test_new_api.foo'].create({'name': 'A', 'value1': 1, 'value2': 2}) + bar = self.env['test_new_api.bar'].create({'name': 'A'}) + self.assertEqual(bar.foo, foo) + self.assertEqual(bar.value1, 1) + self.assertEqual(bar.value2, 2) + + foo.invalidate_cache() + bar.write({'value1': 3, 'value2': 4}) + self.assertEqual(foo.value1, 3) + self.assertEqual(foo.value2, 4) + + 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_27_company_dependent(self): + """ test company-dependent fields. """ + # consider three companies + company0 = self.env.ref('base.main_company') + company1 = self.env['res.company'].create({'name': 'A', 'parent_id': company0.id}) + company2 = self.env['res.company'].create({'name': 'B', 'parent_id': company1.id}) + # create one user per company + user0 = self.env['res.users'].create({'name': 'Foo', 'login': 'foo', + 'company_id': company0.id, 'company_ids': []}) + user1 = self.env['res.users'].create({'name': 'Bar', 'login': 'bar', + 'company_id': company1.id, 'company_ids': []}) + user2 = self.env['res.users'].create({'name': 'Baz', 'login': 'baz', + 'company_id': company2.id, 'company_ids': []}) + # create a default value for the company-dependent field + field = self.env['ir.model.fields'].search([('model', '=', 'test_new_api.company'), + ('name', '=', 'foo')]) + self.env['ir.property'].create({'name': 'foo', 'fields_id': field.id, + 'value': 'default', 'type': 'char'}) + + # create/modify a record, and check the value for each user + record = self.env['test_new_api.company'].create({'foo': 'main'}) + record.invalidate_cache() + self.assertEqual(record.sudo(user0).foo, 'main') + self.assertEqual(record.sudo(user1).foo, 'default') + self.assertEqual(record.sudo(user2).foo, 'default') + + record.sudo(user1).foo = 'alpha' + record.invalidate_cache() + self.assertEqual(record.sudo(user0).foo, 'main') + self.assertEqual(record.sudo(user1).foo, 'alpha') + self.assertEqual(record.sudo(user2).foo, 'default') + + def test_28_sparse(self): + """ test sparse fields. """ + record = self.env['test_new_api.sparse'].create({}) + self.assertFalse(record.data) + + partner = self.env.ref('base.main_partner') + values = [ + ('boolean', True), + ('integer', 42), + ('float', 3.14), + ('char', 'John'), + ('selection', 'two'), + ('partner', partner.id), + ] + for n, (key, val) in enumerate(values): + record.write({key: val}) + self.assertEqual(record.data, dict(values[:n+1])) + + for key, val in values[:-1]: + self.assertEqual(record[key], val) + self.assertEqual(record.partner, partner) + + for n, (key, val) in enumerate(values): + record.write({key: False}) + self.assertEqual(record.data, dict(values[n+1:])) + + 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'] + cat1 = Category.create({'name': 'NOACCESS'}) + cat2 = Category.create({'name': 'ACCESS', 'parent': cat1.id}) + cats = cat1 + cat2 + + self.env.clear() + + cat1, cat2 = cats + self.assertEqual(cat2.name, 'ACCESS') + # both categories should be ready for prefetching + self.assertItemsEqual(cat2._prefetch[Category._name], cats.ids) + # but due to our (lame) overwrite of `read`, it should not forbid us to read records we have access to + self.assertFalse(cat2.discussions) + self.assertEqual(cat2.parent, cat1) + with self.assertRaises(AccessError): + cat1.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)) + + @mute_logger('openerp.addons.base.ir.ir_model') + def test_41_new_related(self): + """ test the behavior of related fields starting on new records. """ + # make discussions unreadable for demo user + access = self.env.ref('test_new_api.access_discussion') + access.write({'perm_read': False}) + + # create an environment for demo user + env = self.env(user=self.env.ref('base.user_demo')) + self.assertEqual(env.user.login, "demo") + + # create a new message as demo user + discussion = self.env.ref('test_new_api.discussion_0') + message = env['test_new_api.message'].new({'discussion': discussion}) + self.assertEqual(message.discussion, discussion) + + # read the related field discussion_name + self.assertEqual(message.discussion.env, env) + self.assertEqual(message.discussion_name, discussion.name) + with self.assertRaises(AccessError): + message.discussion.name + + @mute_logger('openerp.addons.base.ir.ir_model') + def test_42_new_related(self): + """ test the behavior of related fields traversing new records. """ + # make discussions unreadable for demo user + access = self.env.ref('test_new_api.access_discussion') + access.write({'perm_read': False}) + + # create an environment for demo user + env = self.env(user=self.env.ref('base.user_demo')) + self.assertEqual(env.user.login, "demo") + + # create a new discussion and a new message as demo user + discussion = env['test_new_api.discussion'].new({'name': 'Stuff'}) + message = env['test_new_api.message'].new({'discussion': discussion}) + self.assertEqual(message.discussion, discussion) + + # read the related field discussion_name + self.assertNotEqual(message.sudo().env, message.env) + self.assertEqual(message.discussion_name, discussion.name) + + def test_50_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}) + + def test_50_search_many2one(self): + """ test search through a path of computed fields""" + messages = self.env['test_new_api.message'].search( + [('author_partner.name', '=', 'Demo User')]) + self.assertEqual(messages, self.env.ref('test_new_api.message_0_1')) + + def test_60_x2many_domain(self): + """ test the cache consistency of a x2many field with a domain """ + discussion = self.env.ref('test_new_api.discussion_0') + message = discussion.messages[0] + self.assertNotIn(message, discussion.important_messages) + + message.important = True + self.assertIn(message, discussion.important_messages) + + +class TestHtmlField(common.TransactionCase): + + def setUp(self): + super(TestHtmlField, self).setUp() + self.model = self.env['test_new_api.mixed'] + + def test_00_sanitize(self): + self.assertEqual(self.model._fields['comment1'].sanitize, False) + self.assertEqual(self.model._fields['comment2'].sanitize_attributes, True) + self.assertEqual(self.model._fields['comment2'].strip_classes, False) + self.assertEqual(self.model._fields['comment3'].sanitize_attributes, True) + self.assertEqual(self.model._fields['comment3'].strip_classes, True) + + some_ugly_html = """<p>Oops this should maybe be sanitized +% if object.some_field and not object.oriented: +<table> + % if object.other_field: + <tr style="margin: 0px; border: 10px solid black;"> + ${object.mako_thing} + <td> + </tr> + <tr class="custom_class"> + This is some html. + </tr> + % endif + <tr> +%if object.dummy_field: + <p>Youpie</p> +%endif""" + + record = self.model.create({ + 'comment1': some_ugly_html, + 'comment2': some_ugly_html, + 'comment3': some_ugly_html, + 'comment4': some_ugly_html, + }) + + self.assertEqual(record.comment1, some_ugly_html, 'Error in HTML field: content was sanitized but field has sanitize=False') + + self.assertIn('<tr class="', record.comment2) + + # sanitize should have closed tags left open in the original html + self.assertIn('</table>', record.comment3, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True') + self.assertIn('</td>', record.comment3, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True') + self.assertIn('<tr style="', record.comment3, 'Style attr should not have been stripped') + # sanitize does not keep classes if asked to + self.assertNotIn('<tr class="', record.comment3) + + self.assertNotIn('<tr style="', record.comment4, 'Style attr should have been stripped') + + +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) |
