[IMP] orm: fix and generalize method resolve_o2m_commands_to_record_dicts (now named...
authorRaphael Collet <rco@openerp.com>
Thu, 6 Sep 2012 14:48:36 +0000 (16:48 +0200)
committerRaphael Collet <rco@openerp.com>
Thu, 6 Sep 2012 14:48:36 +0000 (16:48 +0200)
bzr revid: rco@openerp.com-20120906144836-v2ye3o8n12iir84b

openerp/addons/base/res/res_bank.py
openerp/addons/base/res/res_company.py
openerp/osv/orm.py
openerp/tests/test_orm.py

index c5c05c1..a839afc 100644 (file)
@@ -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':
index f56d334..a51f37a 100644 (file)
@@ -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')
index 2ba8afd..a6233a8 100644 (file)
@@ -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.
-
-        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.
-
-        .. 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)``
+    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 commands might be creation commands, not all record dicts
+            will contain an ``id`` field.  Commands matching an existing record
+            will have an ``id``.
+
+            :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
-
-        # 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"
-
-        # 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))
-
-        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
+        result = []             # result (list of dict)
+        record_ids = set()      # ids of records to read
+        updates = {}            # {id: dict} of updates on particular records
+
+        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])
+
+        # 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)
+
+        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
index ea9b37e..8c7043a 100644 (file)
@@ -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'])