diff --git a/odoo/models.py b/odoo/models.py index fb20a4bfbd0f093d8e4f658eb8e9a56d3b808f49..c4c46afd2a099e6c62395722ecaa80157184c5e4 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -4949,9 +4949,7 @@ class BaseModel(object): if self: vals = [func(rec) for rec in self] if isinstance(vals[0], BaseModel): - # return the union of all recordsets in O(n) - ids = OrderedSet(itertools.chain(*[rec._ids for rec in vals])) - return vals[0].browse(ids) + return vals[0].union(*vals) # union of all recordsets return vals else: vals = func(self) @@ -5100,12 +5098,23 @@ class BaseModel(object): def __add__(self, other): """ Return the concatenation of two recordsets. """ - if not isinstance(other, BaseModel) or self._name != other._name: - raise TypeError("Mixing apples and oranges: %s + %s" % (self, other)) - return self.browse(self._ids + other._ids) + return self.concat(other) + + def concat(self, *args): + """ Return the concatenation of ``self`` with all the arguments (in + linear time complexity). + """ + ids = list(self._ids) + for arg in args: + if not (isinstance(arg, BaseModel) and arg._name == self._name): + raise TypeError("Mixing apples and oranges: %s.concat(%s)" % (self, arg)) + ids.extend(arg._ids) + return self.browse(ids) def __sub__(self, other): - """ Return the recordset of all the records in ``self`` that are not in ``other``. """ + """ Return the recordset of all the records in ``self`` that are not in + ``other``. Note that recordset order is preserved. + """ if not isinstance(other, BaseModel) or self._name != other._name: raise TypeError("Mixing apples and oranges: %s - %s" % (self, other)) other_ids = set(other._ids) @@ -5113,19 +5122,29 @@ class BaseModel(object): def __and__(self, other): """ Return the intersection of two recordsets. - Note that recordset order is not preserved. + Note that first occurrence order is preserved. """ if not isinstance(other, BaseModel) or self._name != other._name: raise TypeError("Mixing apples and oranges: %s & %s" % (self, other)) - return self.browse(OrderedSet(self._ids) & OrderedSet(other._ids)) + other_ids = set(other._ids) + return self.browse(OrderedSet(id for id in self._ids if id in other_ids)) def __or__(self, other): """ Return the union of two recordsets. - Note that recordset order is not preserved. + Note that first occurrence order is preserved. """ - if not isinstance(other, BaseModel) or self._name != other._name: - raise TypeError("Mixing apples and oranges: %s | %s" % (self, other)) - return self.browse(OrderedSet(self._ids) | OrderedSet(other._ids)) + return self.union(other) + + def union(self, *args): + """ Return the union of ``self`` with all the arguments (in linear time + complexity, with first occurrence order preserved). + """ + ids = list(self._ids) + for arg in args: + if not (isinstance(arg, BaseModel) and arg._name == self._name): + raise TypeError("Mixing apples and oranges: %s.union(%s)" % (self, arg)) + ids.extend(arg._ids) + return self.browse(OrderedSet(ids)) def __eq__(self, other): """ Test whether two recordsets are equivalent (up to reordering). """