diff --git a/openerp/addons/base/res/res_bank.py b/openerp/addons/base/res/res_bank.py index c5c05c1807c..a839afc10e4 100644 --- a/openerp/addons/base/res/res_bank.py +++ b/openerp/addons/base/res/res_bank.py @@ -109,7 +109,7 @@ class res_partner_bank(osv.osv): if not context.get('address'): return value - for address in self.pool.get('res.partner').resolve_o2m_commands_to_record_dicts( + for address in self.pool.get('res.partner').resolve_2many_commands( cursor, user, 'address', context['address'], ['type', field], context=context): if address.get('type') == 'default': diff --git a/openerp/addons/base/res/res_company.py b/openerp/addons/base/res/res_company.py index f56d33444b3..a51f37af1f4 100644 --- a/openerp/addons/base/res/res_company.py +++ b/openerp/addons/base/res/res_company.py @@ -159,7 +159,7 @@ class res_company(osv.osv): ])) # second line: bank accounts - accounts = self.resolve_o2m_commands_to_record_dicts(cr, uid, 'bank_ids', bank_ids, context=context) + accounts = self.resolve_2many_commands(cr, uid, 'bank_ids', bank_ids, context=context) accounts_names = [('%(bank_name)s %(acc_number)s' % acc) for acc in accounts if acc['footer']] if accounts_names: title = _('Bank Accounts') if len(accounts_names) > 1 else _('Bank Account') diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 2ba8afd8e72..a6233a8bb46 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -5009,66 +5009,57 @@ class BaseModel(object): return True - 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 iterable over these dictionaries. + def resolve_2many_commands(self, cr, uid, field_name, commands, fields=None, context=None): + """ Serializes one2many and many2many commands into record dictionaries + (as if all the records came from the database via a read()). This + method is aimed at onchange methods on one2many and many2many fields. - 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. + Because commands might be creation commands, not all record dicts + will contain an ``id`` field. Commands matching an existing record + 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 - :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) - :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: ``list(dict)`` + :param field_name: name of the one2many or many2many field matching the commands + :type field_name: str + :param commands: one2many or many2many commands to execute on ``field_name`` + :type commands: list((int|False, int|False, dict|False)) + :param fields: list of fields to read from the database, when applicable + :type fields: list(str) + :returns: 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: list(dict) """ - o2m_model = self._all_columns[field_name].column._obj + result = [] # result (list of dict) + record_ids = set() # ids of records to read + updates = {} # {id: dict} of updates on particular records - # convert single ids and pairs to tripled commands - commands = [] - for o2m_command in o2m_commands: - if not isinstance(o2m_command, (list, tuple)): - command = 4 - commands.append((command, 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)) - else: - command = o2m_command[0] - commands.append(o2m_command) - assert command in (0, 1, 4), \ - "Only CREATE, UPDATE and LINK_TO commands are supported in resolver" + for command in commands: + if not isinstance(command, (list, tuple)): + record_ids.add(command) + elif command[0] == 0: + result.append(command[2]) + elif command[0] == 1: + record_ids.add(command[1]) + updates.setdefault(command[1], {}).update(command[2]) + elif command[0] in (2, 3): + record_ids.discard(command[1]) + elif command[0] == 4: + record_ids.add(command[1]) + elif command[0] == 5: + result, record_ids = [], [] + elif command[0] == 6: + result, record_ids = [], list(command[2]) - # 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)) + # read the records and apply the updates + other_model = self.pool.get(self._all_columns[field_name].column._obj) + for record in other_model.read(cr, uid, list(record_ids), fields=fields, context=context): + record.update(updates.get(record['id'], {})) + result.append(record) - 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) - record_dicts.append(item) - return record_dicts + return result + + # for backward compatibility + def resolve_2many_commands(self, cr, uid, field_name, o2m_commands, fields=None, context=None): + return self.resolve_2many_commands(cr, uid, field_name, o2m_commands, fields, context) # keep this import here, at top it will cause dependency cycle errors import expression diff --git a/openerp/tests/test_orm.py b/openerp/tests/test_orm.py index ea9b37e5f3b..8c7043a5ff0 100644 --- a/openerp/tests/test_orm.py +++ b/openerp/tests/test_orm.py @@ -23,14 +23,14 @@ class TestO2MSerialization(common.TransactionCase): def test_no_command(self): " empty list of commands yields an empty list of records " - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', []) self.assertEqual(results, []) def test_CREATE_commands(self): " returns the VALUES dict as-is " - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', map(CREATE, [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}])) self.assertEqual(results, [ @@ -48,7 +48,7 @@ class TestO2MSerialization(common.TransactionCase): ] commands = map(LINK_TO, ids) - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', commands, ['name']) self.assertEqual(results, [ @@ -65,7 +65,7 @@ class TestO2MSerialization(common.TransactionCase): self.partner.create(self.cr, UID, {'name': 'baz'}) ] - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', ids, ['name']) self.assertEqual(results, [ @@ -80,7 +80,7 @@ class TestO2MSerialization(common.TransactionCase): id_bar = self.partner.create(self.cr, UID, {'name': 'bar'}) id_baz = self.partner.create(self.cr, UID, {'name': 'baz', 'city': 'tag'}) - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', [ LINK_TO(id_foo), UPDATE(id_bar, {'name': 'qux', 'city': 'tagtag'}), @@ -99,7 +99,7 @@ class TestO2MSerialization(common.TransactionCase): for name in ['NObar', 'baz', 'qux', 'NOquux', 'NOcorge', 'garply'] ] - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', [ CREATE({'name': 'foo'}), UPDATE(ids[0], {'name': 'bar'}), @@ -131,7 +131,7 @@ class TestO2MSerialization(common.TransactionCase): ] commands = map(lambda id: (4, id), ids) - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', commands, ['name']) self.assertEqual(results, [ @@ -144,7 +144,7 @@ class TestO2MSerialization(common.TransactionCase): "DELETE_ALL can appear as a singleton" try: - self.partner.resolve_o2m_commands_to_record_dicts( + self.partner.resolve_2many_commands( self.cr, UID, 'address', [(5,)], ['name']) except AssertionError: # 5 should fail with an assert error, but not e.g. a ValueError @@ -154,19 +154,19 @@ class TestO2MSerialization(common.TransactionCase): "Commands with uncertain semantics in this context should be forbidden" with self.assertRaises(AssertionError): - self.partner.resolve_o2m_commands_to_record_dicts( + self.partner.resolve_2many_commands( self.cr, UID, 'address', [DELETE(42)], ['name']) with self.assertRaises(AssertionError): - self.partner.resolve_o2m_commands_to_record_dicts( + self.partner.resolve_2many_commands( self.cr, UID, 'address', [FORGET(42)], ['name']) with self.assertRaises(AssertionError): - self.partner.resolve_o2m_commands_to_record_dicts( + self.partner.resolve_2many_commands( self.cr, UID, 'address', [DELETE_ALL()], ['name']) with self.assertRaises(AssertionError): - self.partner.resolve_o2m_commands_to_record_dicts( + self.partner.resolve_2many_commands( self.cr, UID, 'address', [REPLACE_WITH([42])], ['name'])