From d5accc0faea908edf758a4da22bcd4bc01485ca2 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 10 Oct 2011 15:51:57 +0200 Subject: [PATCH 1/8] [ADD] method to correctly serialize o2m commands into a sequence of record-dicts for e.g. onchanges also alters res.partner.bank _default_value to use it with res.partner addresses, and adds a few tests to ensure behavior is correct bzr revid: xmo@openerp.com-20111010135157-dafgcwwcni5gkeom --- openerp/addons/base/res/res_bank.py | 10 +-- openerp/osv/orm.py | 44 ++++++++++ tests/test_orm.py | 121 ++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 tests/test_orm.py diff --git a/openerp/addons/base/res/res_bank.py b/openerp/addons/base/res/res_bank.py index db92e220c64..5e96a7ecc04 100644 --- a/openerp/addons/base/res/res_bank.py +++ b/openerp/addons/base/res/res_bank.py @@ -108,14 +108,8 @@ class res_partner_bank(osv.osv): if not context.get('address'): return value - for _, id, address_data in context['address']: - if not (id or address): continue - address = {} - if id: - address.update(self.pool['res.partner.address'] - .read(cursor, user, [id], ['type', field], context=context)[0]) - if address_data: - address.update(address_data) + for address in self.pool.get('res.partner').serialize_o2m_commands( + cursor, user, 'address', context['address'], ['type', field], context=context): if address.get('type') == 'default': return address.get(field, value) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 3a3155ed7f1..07329a301fd 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4762,6 +4762,50 @@ class BaseModel(object): return True + def serialize_o2m_commands(self, cr, uid, field_name, o2m_commands, fields=None, context=None): + """ Serializes o2m commands into record dictionaries (as if + all the o2m records came from the database via a read()), and + returns an iterator over these dictionaries. + + Because o2m commands might be creation commands, not all + record ids will contain an ``id`` field. Commands matching an + existing record (UPDATE and LINK_TO) will have an id. + + :param field_name: name of the o2m field matching the commands + :type field_name: str + :param o2m_commands: one2many commands to execute on ``field_name`` + :type o2m_commands: list((int|False, int|False, dict|False)) + :param fields: list of fields to read from the database, when applicable + :type fields: list(str) + :param context: request context + :returns: o2m records in a shape similar to that returned by + ``read()`` (except records may be missing the ``id`` + field if they don't exist in db) + :rtype: iter(dict) + """ + o2m_model = self._all_columns[field_name].column._obj + + # extract and handle case of single ids (instead of commands): + # convert to LINK_TO commands (4) + c1, c2 = itertools.tee(o2m_commands) + commands = list(itertools.chain( + (command for command in c1 if isinstance(command, (list, tuple))), + ((4, id, None) for id in c2 if not isinstance(id, (list, tuple))))) + + # extract records to read, by id, in a mapping dict + ids_to_read = [id for (command, id, _) in commands if command in (1, 4)] + records_by_id = dict( + (record['id'], record) + for record in self.pool.get(o2m_model).read( + cr, uid, ids_to_read, fields=fields, context=context)) + + # merge record from db with record provided by command + for command, id, record in commands: + item = {} + if command in (1, 4): item.update(records_by_id[id]) + if command in (0, 1): item.update(record) + yield item + # keep this import here, at top it will cause dependency cycle errors import expression diff --git a/tests/test_orm.py b/tests/test_orm.py new file mode 100644 index 00000000000..67b97b894d6 --- /dev/null +++ b/tests/test_orm.py @@ -0,0 +1,121 @@ +import os +import unittest2 +import openerp + +UID = 1 +DB = os.environ['OPENERP_DATABASE'] + +CREATE = lambda values: (0, False, values) +UPDATE = lambda id, values: (1, id, values) +LINK_TO = lambda id: (4, id, False) + + +def setUpModule(): + openerp.tools.config['addons_path'] = os.environ['OPENERP_ADDONS_PATH'] + +class TestO2MSerialization(unittest2.TestCase): + def setUp(self): + self.cr = openerp.modules.registry.RegistryManager.get(DB).db.cursor() + self.partner = openerp.modules.registry.RegistryManager.get(DB)['res.partner'] + self.address = openerp.modules.registry.RegistryManager.get(DB)['res.partner.address'] + def tearDown(self): + self.cr.rollback() + self.cr.close() + + def test_no_command(self): + " empty list of commands yields an empty list of records " + results = list(self.partner.serialize_o2m_commands(self.cr, UID, 'address', [])) + + self.assertEqual(results, []) + + def test_CREATE_commands(self): + " returns the VALUES dict as-is " + results = list(self.partner.serialize_o2m_commands( + self.cr, UID, 'address', + map(CREATE, [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}]))) + self.assertEqual(results, [ + {'foo': 'bar'}, + {'foo': 'baz'}, + {'foo': 'baq'} + ]) + + def test_LINK_TO_command(self): + " reads the records from the database, records are returned with their ids. " + ids = [ + self.address.create(self.cr, UID, {'name': 'foo'}), + self.address.create(self.cr, UID, {'name': 'bar'}), + self.address.create(self.cr, UID, {'name': 'baz'}) + ] + commands = map(LINK_TO, ids) + + results = list(self.partner.serialize_o2m_commands(self.cr, UID, 'address', commands, ['name'])) + + self.assertEqual(results, [ + {'id': ids[0], 'name': 'foo'}, + {'id': ids[1], 'name': 'bar'}, + {'id': ids[2], 'name': 'baz'} + ]) + + def test_bare_ids_command(self): + " same as the equivalent LINK_TO commands " + ids = [ + self.address.create(self.cr, UID, {'name': 'foo'}), + self.address.create(self.cr, UID, {'name': 'bar'}), + self.address.create(self.cr, UID, {'name': 'baz'}) + ] + + results = list(self.partner.serialize_o2m_commands(self.cr, UID, 'address', ids, ['name'])) + + self.assertEqual(results, [ + {'id': ids[0], 'name': 'foo'}, + {'id': ids[1], 'name': 'bar'}, + {'id': ids[2], 'name': 'baz'} + ]) + + def test_UPDATE_command(self): + " take the in-db records and merge the provided information in " + id_foo = self.address.create(self.cr, UID, {'name': 'foo'}) + id_bar = self.address.create(self.cr, UID, {'name': 'bar'}) + id_baz = self.address.create(self.cr, UID, {'name': 'baz', 'city': 'tag'}) + + results = list(self.partner.serialize_o2m_commands( + self.cr, UID, 'address', [ + LINK_TO(id_foo), + UPDATE(id_bar, {'name': 'qux', 'city': 'tagtag'}), + UPDATE(id_baz, {'name': 'quux'}) + ], ['name', 'city'])) + + self.assertEqual(results, [ + {'id': id_foo, 'name': 'foo', 'city': False}, + {'id': id_bar, 'name': 'qux', 'city': 'tagtag'}, + {'id': id_baz, 'name': 'quux', 'city': 'tag'} + ]) + + def test_mixed_commands(self): + ids = [ + self.address.create(self.cr, UID, {'name': name}) + for name in ['NObar', 'baz', 'qux', 'NOquux', 'NOcorge', 'garply'] + ] + + results = list(self.partner.serialize_o2m_commands( + self.cr, UID, 'address', [ + CREATE({'name': 'foo'}), + UPDATE(ids[0], {'name': 'bar'}), + LINK_TO(ids[1]), + LINK_TO(ids[2]), + UPDATE(ids[3], {'name': 'quux',}), + UPDATE(ids[4], {'name': 'corge'}), + CREATE({'name': 'grault'}), + LINK_TO(ids[5]) + ], ['name'])) + + self.assertEqual(results, [ + {'name': 'foo'}, + {'id': ids[0], 'name': 'bar'}, + {'id': ids[1], 'name': 'baz'}, + {'id': ids[2], 'name': 'qux'}, + {'id': ids[3], 'name': 'quux'}, + {'id': ids[4], 'name': 'corge'}, + {'name': 'grault'}, + {'id': ids[5], 'name': 'garply'} + ]) From b5ee13a8450b7429dd4235507ed6f44def581baf Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 11 Oct 2011 10:03:38 +0200 Subject: [PATCH 2/8] [IMP] rename serialize_o2m_commands to resolve_o2m_commands_to_record_dicts bzr revid: xmo@openerp.com-20111011080338-hup42vivlvk4dly0 --- openerp/addons/base/res/res_bank.py | 2 +- openerp/osv/orm.py | 2 +- tests/test_orm.py | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openerp/addons/base/res/res_bank.py b/openerp/addons/base/res/res_bank.py index 5e96a7ecc04..ce1cd1c6a24 100644 --- a/openerp/addons/base/res/res_bank.py +++ b/openerp/addons/base/res/res_bank.py @@ -108,7 +108,7 @@ class res_partner_bank(osv.osv): if not context.get('address'): return value - for address in self.pool.get('res.partner').serialize_o2m_commands( + for address in self.pool.get('res.partner').resolve_o2m_commands_to_record_dicts( cursor, user, 'address', context['address'], ['type', field], context=context): if address.get('type') == 'default': diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 07329a301fd..a17eb49e8b3 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4762,7 +4762,7 @@ class BaseModel(object): return True - def serialize_o2m_commands(self, cr, uid, field_name, o2m_commands, fields=None, context=None): + def resolve_o2m_commands_to_record_dicts(self, cr, uid, field_name, o2m_commands, fields=None, context=None): """ Serializes o2m commands into record dictionaries (as if all the o2m records came from the database via a read()), and returns an iterator over these dictionaries. diff --git a/tests/test_orm.py b/tests/test_orm.py index 67b97b894d6..365ac95ee4d 100644 --- a/tests/test_orm.py +++ b/tests/test_orm.py @@ -24,13 +24,13 @@ class TestO2MSerialization(unittest2.TestCase): def test_no_command(self): " empty list of commands yields an empty list of records " - results = list(self.partner.serialize_o2m_commands(self.cr, UID, 'address', [])) + results = list(self.partner.resolve_o2m_commands_to_record_dicts(self.cr, UID, 'address', [])) self.assertEqual(results, []) def test_CREATE_commands(self): " returns the VALUES dict as-is " - results = list(self.partner.serialize_o2m_commands( + results = list(self.partner.resolve_o2m_commands_to_record_dicts( self.cr, UID, 'address', map(CREATE, [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}]))) self.assertEqual(results, [ @@ -48,7 +48,7 @@ class TestO2MSerialization(unittest2.TestCase): ] commands = map(LINK_TO, ids) - results = list(self.partner.serialize_o2m_commands(self.cr, UID, 'address', commands, ['name'])) + results = list(self.partner.resolve_o2m_commands_to_record_dicts(self.cr, UID, 'address', commands, ['name'])) self.assertEqual(results, [ {'id': ids[0], 'name': 'foo'}, @@ -64,7 +64,7 @@ class TestO2MSerialization(unittest2.TestCase): self.address.create(self.cr, UID, {'name': 'baz'}) ] - results = list(self.partner.serialize_o2m_commands(self.cr, UID, 'address', ids, ['name'])) + results = list(self.partner.resolve_o2m_commands_to_record_dicts(self.cr, UID, 'address', ids, ['name'])) self.assertEqual(results, [ {'id': ids[0], 'name': 'foo'}, @@ -78,7 +78,7 @@ class TestO2MSerialization(unittest2.TestCase): id_bar = self.address.create(self.cr, UID, {'name': 'bar'}) id_baz = self.address.create(self.cr, UID, {'name': 'baz', 'city': 'tag'}) - results = list(self.partner.serialize_o2m_commands( + results = list(self.partner.resolve_o2m_commands_to_record_dicts( self.cr, UID, 'address', [ LINK_TO(id_foo), UPDATE(id_bar, {'name': 'qux', 'city': 'tagtag'}), @@ -97,7 +97,7 @@ class TestO2MSerialization(unittest2.TestCase): for name in ['NObar', 'baz', 'qux', 'NOquux', 'NOcorge', 'garply'] ] - results = list(self.partner.serialize_o2m_commands( + results = list(self.partner.resolve_o2m_commands_to_record_dicts( self.cr, UID, 'address', [ CREATE({'name': 'foo'}), UPDATE(ids[0], {'name': 'bar'}), From 4d0a189796e645003e55ac82b0804fce5fb54ae0 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 11 Oct 2011 10:12:02 +0200 Subject: [PATCH 3/8] [IMP] handle case where o2m commands are pairs instead of triplets bzr revid: xmo@openerp.com-20111011081202-y1ou74u038o2kl58 --- openerp/osv/orm.py | 16 ++++++++++------ tests/test_orm.py | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index a17eb49e8b3..7cd2cbc22c5 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4785,12 +4785,16 @@ class BaseModel(object): """ o2m_model = self._all_columns[field_name].column._obj - # extract and handle case of single ids (instead of commands): - # convert to LINK_TO commands (4) - c1, c2 = itertools.tee(o2m_commands) - commands = list(itertools.chain( - (command for command in c1 if isinstance(command, (list, tuple))), - ((4, id, None) for id in c2 if not isinstance(id, (list, tuple))))) + # convert single ids and pairs to tripled commands + commands = [] + for o2m_command in o2m_commands: + if not isinstance(o2m_command, (list, tuple)): + commands.append((4, o2m_command, False)) + elif len(o2m_command) == 2: + command, id = o2m_command + commands.append((command, id, False)) + else: + commands.append(o2m_command) # extract records to read, by id, in a mapping dict ids_to_read = [id for (command, id, _) in commands if command in (1, 4)] diff --git a/tests/test_orm.py b/tests/test_orm.py index 365ac95ee4d..ff82b779cd9 100644 --- a/tests/test_orm.py +++ b/tests/test_orm.py @@ -119,3 +119,21 @@ class TestO2MSerialization(unittest2.TestCase): {'name': 'grault'}, {'id': ids[5], 'name': 'garply'} ]) + + def test_LINK_TO_pairs(self): + "LINK_TO commands can be written as pairs, instead of triplets" + ids = [ + self.address.create(self.cr, UID, {'name': 'foo'}), + self.address.create(self.cr, UID, {'name': 'bar'}), + self.address.create(self.cr, UID, {'name': 'baz'}) + ] + commands = map(lambda id: (4, id), ids) + + results = list(self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', commands, ['name'])) + + self.assertEqual(results, [ + {'id': ids[0], 'name': 'foo'}, + {'id': ids[1], 'name': 'bar'}, + {'id': ids[2], 'name': 'baz'} + ]) From 8734ffb6c2cb33106c1ebc423191b8c0adc6a24d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 11 Oct 2011 10:38:24 +0200 Subject: [PATCH 4/8] [IMP] raise assertion error if an o2m command is not one of CREATE, UPDATE or LINK_TO bzr revid: xmo@openerp.com-20111011083824-4g0ros6f3xgslkq4 --- openerp/osv/orm.py | 3 +++ tests/test_orm.py | 24 +++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 7cd2cbc22c5..8a447a838d5 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4796,6 +4796,9 @@ class BaseModel(object): else: commands.append(o2m_command) + assert not any(command for command, _, _ in commands if command not in (0, 1, 4)),\ + "Only CREATE, UPDATE and LINK_TO commands are supported in resolver" + # extract records to read, by id, in a mapping dict ids_to_read = [id for (command, id, _) in commands if command in (1, 4)] records_by_id = dict( diff --git a/tests/test_orm.py b/tests/test_orm.py index ff82b779cd9..1954e485e69 100644 --- a/tests/test_orm.py +++ b/tests/test_orm.py @@ -7,8 +7,11 @@ DB = os.environ['OPENERP_DATABASE'] CREATE = lambda values: (0, False, values) UPDATE = lambda id, values: (1, id, values) +DELETE = lambda id: (2, id, False) +FORGET = lambda id: (3, id, False) LINK_TO = lambda id: (4, id, False) - +DELETE_ALL = lambda: (5, False, False) +REPLACE_WITH = lambda ids: (6, False, ids) def setUpModule(): openerp.tools.config['addons_path'] = os.environ['OPENERP_ADDONS_PATH'] @@ -137,3 +140,22 @@ class TestO2MSerialization(unittest2.TestCase): {'id': ids[1], 'name': 'bar'}, {'id': ids[2], 'name': 'baz'} ]) + + def test_invalid_commands(self): + "Commands with uncertain semantics in this context should be forbidden" + + with self.assertRaises(AssertionError): + list(self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [DELETE(42)], ['name'])) + + with self.assertRaises(AssertionError): + list(self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [FORGET(42)], ['name'])) + + with self.assertRaises(AssertionError): + list(self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [DELETE_ALL()], ['name'])) + + with self.assertRaises(AssertionError): + list(self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [REPLACE_WITH([42])], ['name'])) From 2646a34cf08dc1c0f4d993d73f50824aee16d053 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 11 Oct 2011 10:48:52 +0200 Subject: [PATCH 5/8] [ADD] don't blow up on a singleton DELETE_ALL command (that command takes no parameters, so can have just (5,)) bzr revid: xmo@openerp.com-20111011084852-hborz317ru5pbzux --- openerp/osv/orm.py | 3 +++ tests/test_orm.py | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 8a447a838d5..39d05eab839 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4790,6 +4790,9 @@ class BaseModel(object): for o2m_command in o2m_commands: if not isinstance(o2m_command, (list, tuple)): commands.append((4, o2m_command, False)) + elif len(o2m_command) == 1: + (command,) = o2m_command + commands.append((command, False, False)) elif len(o2m_command) == 2: command, id = o2m_command commands.append((command, id, False)) diff --git a/tests/test_orm.py b/tests/test_orm.py index 1954e485e69..6e82bb9996b 100644 --- a/tests/test_orm.py +++ b/tests/test_orm.py @@ -141,6 +141,16 @@ class TestO2MSerialization(unittest2.TestCase): {'id': ids[2], 'name': 'baz'} ]) + def test_singleton_commands(self): + "DELETE_ALL can appear as a singleton" + + try: + list(self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [(5,)], ['name'])) + except AssertionError: + # 5 should fail with an assert error, but not e.g. a ValueError + pass + def test_invalid_commands(self): "Commands with uncertain semantics in this context should be forbidden" @@ -159,3 +169,4 @@ class TestO2MSerialization(unittest2.TestCase): with self.assertRaises(AssertionError): list(self.partner.resolve_o2m_commands_to_record_dicts( self.cr, UID, 'address', [REPLACE_WITH([42])], ['name'])) + From 8507ceddcf221b65b6214c24174737383f687452 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 11 Oct 2011 10:57:15 +0200 Subject: [PATCH 6/8] [IMP] make resolve_o2m_commands_to_record_dicts's result eager as per vmt's suggestion bzr revid: xmo@openerp.com-20111011085715-cvcficcy4di9q29i --- openerp/osv/orm.py | 4 +++- tests/test_orm.py | 45 ++++++++++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 39d05eab839..1d5a65456b6 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4809,12 +4809,14 @@ class BaseModel(object): for record in self.pool.get(o2m_model).read( cr, uid, ids_to_read, fields=fields, context=context)) + record_dicts = [] # merge record from db with record provided by command for command, id, record in commands: item = {} if command in (1, 4): item.update(records_by_id[id]) if command in (0, 1): item.update(record) - yield item + record_dicts.append(item) + return record_dicts # keep this import here, at top it will cause dependency cycle errors import expression diff --git a/tests/test_orm.py b/tests/test_orm.py index 6e82bb9996b..61b574df5eb 100644 --- a/tests/test_orm.py +++ b/tests/test_orm.py @@ -27,15 +27,16 @@ class TestO2MSerialization(unittest2.TestCase): def test_no_command(self): " empty list of commands yields an empty list of records " - results = list(self.partner.resolve_o2m_commands_to_record_dicts(self.cr, UID, 'address', [])) + results = self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', []) self.assertEqual(results, []) def test_CREATE_commands(self): " returns the VALUES dict as-is " - results = list(self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_o2m_commands_to_record_dicts( self.cr, UID, 'address', - map(CREATE, [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}]))) + map(CREATE, [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}])) self.assertEqual(results, [ {'foo': 'bar'}, {'foo': 'baz'}, @@ -51,7 +52,8 @@ class TestO2MSerialization(unittest2.TestCase): ] commands = map(LINK_TO, ids) - results = list(self.partner.resolve_o2m_commands_to_record_dicts(self.cr, UID, 'address', commands, ['name'])) + results = self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', commands, ['name']) self.assertEqual(results, [ {'id': ids[0], 'name': 'foo'}, @@ -67,7 +69,8 @@ class TestO2MSerialization(unittest2.TestCase): self.address.create(self.cr, UID, {'name': 'baz'}) ] - results = list(self.partner.resolve_o2m_commands_to_record_dicts(self.cr, UID, 'address', ids, ['name'])) + results = self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', ids, ['name']) self.assertEqual(results, [ {'id': ids[0], 'name': 'foo'}, @@ -81,12 +84,12 @@ class TestO2MSerialization(unittest2.TestCase): id_bar = self.address.create(self.cr, UID, {'name': 'bar'}) id_baz = self.address.create(self.cr, UID, {'name': 'baz', 'city': 'tag'}) - results = list(self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_o2m_commands_to_record_dicts( self.cr, UID, 'address', [ LINK_TO(id_foo), UPDATE(id_bar, {'name': 'qux', 'city': 'tagtag'}), UPDATE(id_baz, {'name': 'quux'}) - ], ['name', 'city'])) + ], ['name', 'city']) self.assertEqual(results, [ {'id': id_foo, 'name': 'foo', 'city': False}, @@ -100,7 +103,7 @@ class TestO2MSerialization(unittest2.TestCase): for name in ['NObar', 'baz', 'qux', 'NOquux', 'NOcorge', 'garply'] ] - results = list(self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_o2m_commands_to_record_dicts( self.cr, UID, 'address', [ CREATE({'name': 'foo'}), UPDATE(ids[0], {'name': 'bar'}), @@ -110,7 +113,7 @@ class TestO2MSerialization(unittest2.TestCase): UPDATE(ids[4], {'name': 'corge'}), CREATE({'name': 'grault'}), LINK_TO(ids[5]) - ], ['name'])) + ], ['name']) self.assertEqual(results, [ {'name': 'foo'}, @@ -132,8 +135,8 @@ class TestO2MSerialization(unittest2.TestCase): ] commands = map(lambda id: (4, id), ids) - results = list(self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', commands, ['name'])) + results = self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', commands, ['name']) self.assertEqual(results, [ {'id': ids[0], 'name': 'foo'}, @@ -145,8 +148,8 @@ class TestO2MSerialization(unittest2.TestCase): "DELETE_ALL can appear as a singleton" try: - list(self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [(5,)], ['name'])) + self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [(5,)], ['name']) except AssertionError: # 5 should fail with an assert error, but not e.g. a ValueError pass @@ -155,18 +158,18 @@ class TestO2MSerialization(unittest2.TestCase): "Commands with uncertain semantics in this context should be forbidden" with self.assertRaises(AssertionError): - list(self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [DELETE(42)], ['name'])) + self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [DELETE(42)], ['name']) with self.assertRaises(AssertionError): - list(self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [FORGET(42)], ['name'])) + self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [FORGET(42)], ['name']) with self.assertRaises(AssertionError): - list(self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [DELETE_ALL()], ['name'])) + self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [DELETE_ALL()], ['name']) with self.assertRaises(AssertionError): - list(self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [REPLACE_WITH([42])], ['name'])) + self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [REPLACE_WITH([42])], ['name']) From 34fcc08a1f0722cd8eafd0dd2d8dd4d175f0e843 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 11 Oct 2011 11:45:51 +0200 Subject: [PATCH 7/8] [IMP] docstring for resolve_o2m_commands_... bzr revid: xmo@openerp.com-20111011094551-vr27on7iibdd9tv6 --- openerp/osv/orm.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 1d5a65456b6..3d9f8324abe 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4765,11 +4765,15 @@ class BaseModel(object): def resolve_o2m_commands_to_record_dicts(self, cr, uid, field_name, o2m_commands, fields=None, context=None): """ Serializes o2m commands into record dictionaries (as if all the o2m records came from the database via a read()), and - returns an iterator over these dictionaries. + returns an iterable over these dictionaries. Because o2m commands might be creation commands, not all record ids will contain an ``id`` field. Commands matching an - existing record (UPDATE and LINK_TO) will have an id. + existing record (``UPDATE`` and ``LINK_TO``) will have an id. + + .. note:: ``CREATE``, ``UPDATE`` and ``LINK_TO`` stand for the + o2m command codes ``0``, ``1`` and ``4`` + respectively :param field_name: name of the o2m field matching the commands :type field_name: str @@ -4778,10 +4782,11 @@ class BaseModel(object): :param fields: list of fields to read from the database, when applicable :type fields: list(str) :param context: request context + :raises AssertionError: if a command is not ``CREATE``, ``UPDATE`` or ``LINK_TO`` :returns: o2m records in a shape similar to that returned by ``read()`` (except records may be missing the ``id`` field if they don't exist in db) - :rtype: iter(dict) + :rtype: ``list(dict)`` """ o2m_model = self._all_columns[field_name].column._obj From c16befa98321a5b5212ee74ab85dddf35b1782fc Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 11 Oct 2011 16:41:46 +0200 Subject: [PATCH 8/8] [IMP] orm: resolve_o2m_commands_...: simplify assert, remove context doc following guidelines bzr revid: odo@openerp.com-20111011144146-b9rt5kc9sb5lj0dr --- openerp/osv/orm.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 3d9f8324abe..ce51ccc1d44 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4781,7 +4781,6 @@ class BaseModel(object): :type o2m_commands: list((int|False, int|False, dict|False)) :param fields: list of fields to read from the database, when applicable :type fields: list(str) - :param context: request context :raises AssertionError: if a command is not ``CREATE``, ``UPDATE`` or ``LINK_TO`` :returns: o2m records in a shape similar to that returned by ``read()`` (except records may be missing the ``id`` @@ -4794,7 +4793,8 @@ class BaseModel(object): commands = [] for o2m_command in o2m_commands: if not isinstance(o2m_command, (list, tuple)): - commands.append((4, o2m_command, False)) + command = 4 + commands.append((command, o2m_command, False)) elif len(o2m_command) == 1: (command,) = o2m_command commands.append((command, False, False)) @@ -4802,10 +4802,10 @@ class BaseModel(object): command, id = o2m_command commands.append((command, id, False)) else: + command = o2m_command[0] commands.append(o2m_command) - - assert not any(command for command, _, _ in commands if command not in (0, 1, 4)),\ - "Only CREATE, UPDATE and LINK_TO commands are supported in resolver" + assert command in (0, 1, 4), \ + "Only CREATE, UPDATE and LINK_TO commands are supported in resolver" # extract records to read, by id, in a mapping dict ids_to_read = [id for (command, id, _) in commands if command in (1, 4)] @@ -4843,7 +4843,7 @@ class Model(BaseModel): class TransientModel(BaseModel): """Model super-class for transient records, meant to be temporarily persisted, and regularly vaccuum-cleaned. - + A TransientModel has a simplified access rights management, all users can create new records, and may only access the records they created. The super-user has unrestricted access