diff --git a/openerp/models.py b/openerp/models.py index 90d77085e5c..054eeb7d722 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -5762,20 +5762,20 @@ class BaseModel(object): """ while self.env.has_todo(): field, recs = self.env.get_todo() - # evaluate the fields to recompute, and save them to database - computed = self.env[field.model_name]._field_computed[field] - names = [f.name for f in computed if f.store and self.env.field_todo(f)] - for rec in recs: - try: - values = rec._convert_to_write({ - name: rec[name] for name in names - }) - with rec.env.norecompute(): - rec._write(values) - except MissingError: - pass - # mark the computed fields as done - map(recs._recompute_done, computed) + # determine the fields to recompute + fs = self.env[field.model_name]._field_computed[field] + ns = [f.name for f in fs if f.store] + # evaluate fields, and group record ids by update + updates = defaultdict(set) + for rec in recs.exists(): + vals = rec._convert_to_write({n: rec[n] for n in ns}) + updates[frozendict(vals)].add(rec.id) + # update records in batch when possible + with recs.env.norecompute(): + for vals, ids in updates.iteritems(): + recs.browse(ids)._write(dict(vals)) + # mark computed fields as done + map(recs._recompute_done, fs) # # Generic onchange method diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index 436eb5b47f2..175028b3eba 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -18,7 +18,7 @@ import threading import time import werkzeug.utils import zipfile -from collections import defaultdict, Mapping, OrderedDict +from collections import defaultdict, Hashable, Iterable, Mapping, OrderedDict from itertools import islice, izip, groupby from lxml import etree from which import which @@ -1047,6 +1047,16 @@ def dumpstacks(sig=None, frame=None): _logger.info("\n".join(code)) +def freehash(arg): + if isinstance(arg, Mapping): + return hash(frozendict(arg)) + elif isinstance(arg, Iterable): + return hash(frozenset(arg)) + elif isinstance(arg, Hashable): + return hash(arg) + else: + return id(arg) + class frozendict(dict): """ An implementation of an immutable dictionary. """ def __delitem__(self, key): @@ -1063,6 +1073,8 @@ class frozendict(dict): raise NotImplementedError("'setdefault' not supported on frozendict") def update(self, *args, **kwargs): raise NotImplementedError("'update' not supported on frozendict") + def __hash__(self): + return hash(frozenset((key, freehash(val)) for key, val in self.iteritems())) class Collector(Mapping): """ A mapping from keys to lists. This is essentially a space optimization