[FIX] fields: generalize inverse_field to a list of inverse fields
authorRaphael Collet <rco@openerp.com>
Fri, 8 Aug 2014 12:04:40 +0000 (14:04 +0200)
committerRaphael Collet <rco@openerp.com>
Fri, 8 Aug 2014 12:57:00 +0000 (14:57 +0200)
Some many2one fields happen to have several corresponding one2many fields,
typically with different domains. It is also the case for the field 'res_id' of
mail.message, where each model inheriting from mail.thread defines a one2many
based on that field. The fix ensures that when a relational field is updated,
all its inverse fields are invalidated.

openerp/fields.py
openerp/models.py

index 683ff92..a52b406 100644 (file)
@@ -268,7 +268,7 @@ class Field(object):
     relational = False          # whether the field is a relational one
     model_name = None           # name of the model of this field
     comodel_name = None         # name of the model of values (if relational)
-    inverse_field = None        # inverse field (object), if it exists
+    inverse_fields = None       # list of inverse fields (objects)
 
     store = True                # whether the field is stored in database
     index = False               # whether the field is indexed in database
@@ -360,7 +360,7 @@ class Field(object):
         # invalidates the cache of each `field`, and registers the records to
         # recompute based on `path`. See method `modified` below for details.
         self._triggers = set()
-        self.inverse_field = None
+        self.inverse_fields = []
 
     def setup(self, env):
         """ Complete the setup of `self` (dependencies, recomputation triggers,
@@ -509,10 +509,10 @@ class Field(object):
             #_logger.debug("Add trigger on %s to recompute %s", field, self)
             field._triggers.add((self, '.'.join(path0 or ['id'])))
 
-            # add trigger on inverse field, too
-            if field.inverse_field:
-                #_logger.debug("Add trigger on %s to recompute %s", field.inverse_field, self)
-                field.inverse_field._triggers.add((self, '.'.join(path0 + [head])))
+            # add trigger on inverse fields, too
+            for invf in field.inverse_fields:
+                #_logger.debug("Add trigger on %s to recompute %s", invf, self)
+                invf._triggers.add((self, '.'.join(path0 + [head])))
 
             # recursively traverse the dependency
             if tail:
@@ -725,8 +725,8 @@ class Field(object):
             # set value in cache, inverse field, and mark record as dirty
             record._cache[self] = value
             if env.in_onchange:
-                if self.inverse_field:
-                    self.inverse_field._update(value, record)
+                for invf in self.inverse_fields:
+                    invf._update(value, record)
                 record._dirty = True
 
             # determine more dependent fields, and invalidate them
@@ -1294,12 +1294,12 @@ class _Relational(Field):
         return env[self.comodel_name]
 
     def modified(self, records):
-        # Invalidate cache for self.inverse_field, too. Note that recomputation
-        # of fields that depend on self.inverse_field is already covered by the
+        # Invalidate cache for self.inverse_fields, too. Note that recomputation
+        # of fields that depend on self.inverse_fields is already covered by the
         # triggers (see above).
         spec = super(_Relational, self).modified(records)
-        if self.inverse_field:
-            spec.append((self.inverse_field, None))
+        for invf in self.inverse_fields:
+            spec.append((invf, None))
         return spec
 
 
@@ -1338,7 +1338,7 @@ class Many2one(_Relational):
     def _setup_regular(self, env):
         super(Many2one, self)._setup_regular(env)
 
-        # self.inverse_field is determined by the corresponding One2many field
+        # self.inverse_fields is populated by the corresponding One2many field
 
         # determine self.delegate
         self.delegate = self.name in env[self.model_name]._inherits.values()
@@ -1455,10 +1455,10 @@ class _RelationalMulti(_Relational):
             add_existing = lambda id: result.append((4, id))
 
         if fnames is None:
-            # take all fields in cache, except the inverse of self
+            # take all fields in cache, except the inverses of self
             fnames = set(value._fields) - set(MAGIC_COLUMNS)
-            if self.inverse_field:
-                fnames.discard(self.inverse_field.name)
+            for invf in self.inverse_fields:
+                fnames.discard(invf.name)
 
         # add new and existing records
         for record in value:
@@ -1525,8 +1525,8 @@ class One2many(_RelationalMulti):
         if self.inverse_name:
             # link self to its inverse field and vice-versa
             invf = env[self.comodel_name]._fields[self.inverse_name]
-            self.inverse_field = invf
-            invf.inverse_field = self
+            self.inverse_fields.append(invf)
+            invf.inverse_fields.append(self)
 
     _description_relation_field = property(attrgetter('inverse_name'))
 
@@ -1596,8 +1596,8 @@ class Many2many(_RelationalMulti):
             # if inverse field has already been setup, it is present in m2m
             invf = m2m.get((self.relation, self.column2, self.column1))
             if invf:
-                self.inverse_field = invf
-                invf.inverse_field = self
+                self.inverse_fields.append(invf)
+                invf.inverse_fields.append(self)
             else:
                 # add self in m2m, so that its inverse field can find it
                 m2m[(self.relation, self.column1, self.column2)] = self
index 8a6dd5e..32d74df 100644 (file)
@@ -5215,8 +5215,9 @@ class BaseModel(object):
             # records, if that field depends on the main record.
             for name in values:
                 field = self._fields.get(name)
-                if field and field.inverse_field:
-                    field.inverse_field._update(record[name], record)
+                if field:
+                    for invf in field.inverse_fields:
+                        invf._update(record[name], record)
 
         return record
 
@@ -5414,7 +5415,7 @@ class BaseModel(object):
 
         # invalidate fields and inverse fields, too
         spec = [(f, ids) for f in fields] + \
-               [(f.inverse_field, None) for f in fields if f.inverse_field]
+               [(invf, None) for f in fields for invf in f.inverse_fields]
         self.env.invalidate(spec)
 
     @api.multi