Skip to content

Commit 3a42f1b

Browse files
committed
Lazy evaluation of RecordList[...] slicing
1 parent 4abd87a commit 3a42f1b

File tree

3 files changed

+49
-2
lines changed

3 files changed

+49
-2
lines changed

CHANGES.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ Changelog
1717
are read or methods are called. It will use `search_read` API method
1818
when it's adequate.
1919

20+
* New: extracting a part of a lazy :class:`RecordList` will not call
21+
API method, for simple use cases like
22+
``env['account.move'].search([])[10:90]``. It will set
23+
``offset`` and ``limit`` on the prepared search instead.
24+
2025
* Retrieve models when user does not have access to ``ir.model``.
2126
Only for Odoo 15 there's no workaround. For Odoo >= 16 a
2227
method ``get_available_models`` is available to all users.

odooly.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2162,6 +2162,26 @@ def fetch(self):
21622162
"""Execute prepared search query."""
21632163
return (self.id or True) and self
21642164

2165+
def __getitem__(self, key):
2166+
if 'id' in self.__dict__ or (getattr(key, 'start', -1) or 0) < 0:
2167+
return super().__getitem__(key)
2168+
is_stop_positive = key.stop is not None and key.stop >= 0
2169+
search_args = {**self._search_args}
2170+
new_offset, new_stop = key.start or 0, search_args.get('limit', None)
2171+
if is_stop_positive:
2172+
new_stop = key.stop if new_stop is None else min(new_stop, key.stop)
2173+
if new_stop is not None:
2174+
search_args['limit'] = limit = new_stop - new_offset
2175+
if limit <= 0:
2176+
return RecordList(self._model, [])
2177+
if new_offset:
2178+
search_args['offset'] = (search_args.get('offset') or 0) + new_offset
2179+
result = RecordList(self._model, None, search=search_args)
2180+
if (is_stop_positive or key.stop is None) and key.step in (1, None):
2181+
return result
2182+
key = slice(None, limit if is_stop_positive else key.stop, key.step)
2183+
return super(RecordList, result).__getitem__(key)
2184+
21652185
def with_env(self, env):
21662186
if 'id' in self.__dict__:
21672187
return super().with_env(env)

tests/test_model.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,11 @@ def test_search(self):
187187
self.assertIsInstance(FooBar.search([searchterm]), odooly.RecordList)
188188
FooBar.search([searchterm], limit=2)
189189
FooBar.search([searchterm], offset=80, limit=99)
190+
FooBar.search([searchterm], offset=80, limit=99)[100:400:3]
190191
FooBar.search([searchterm], order='name ASC')
192+
FooBar.search([searchterm], order='name ASC')[:3]
193+
FooBar.search([searchterm], order='name ASC')[40:]
194+
FooBar.search([searchterm], order='name ASC')[40:99]
191195
FooBar.search(['name = mushroom', 'state != draft'])
192196
FooBar.search([('name', 'like', 'Morice')])
193197
FooBar.search([])
@@ -199,15 +203,14 @@ def test_search(self):
199203

200204
FooBar.search([searchterm], limit=2).ids
201205
FooBar.search([searchterm], offset=80, limit=99).id
202-
FooBar.search([searchterm], order='name ASC')[:3]
203206
FooBar.search(['name = mushroom', 'state != draft']) or 42
204207
FooBar.search([('name', 'like', 'Morice')]).ids
205208
FooBar._execute('search', [('name like Morice')])[0]
206209
FooBar.search([]).ids
210+
207211
self.assertCalls(
208212
OBJ('foo.bar', 'search', domain, 0, 2, None),
209213
OBJ('foo.bar', 'search', domain, 80, 99, None),
210-
OBJ('foo.bar', 'search', domain, 0, None, 'name ASC'),
211214
OBJ('foo.bar', 'search', domain2),
212215
OBJ('foo.bar', 'search', domain),
213216
OBJ('foo.bar', 'search', domain),
@@ -276,6 +279,25 @@ def test_search_count(self):
276279
self.assertCalls()
277280
self.assertOutput('')
278281

282+
def test_search_read(self):
283+
FooBar = self.env['foo.bar']
284+
searchterm = 'name like Morice'
285+
domain = [('name', 'like', 'Morice')]
286+
287+
FooBar.search([searchterm], order='name ASC')[:3].name
288+
289+
expected_calls = [
290+
OBJ('foo.bar', 'fields_get'),
291+
OBJ('foo.bar', 'search_read', domain, ['name'], order='name ASC', limit=3),
292+
]
293+
if float(self.server_version) < 8.0:
294+
expected_calls[1:2] = [
295+
OBJ('foo.bar', 'search', domain, 0, 3, 'name ASC'),
296+
OBJ('foo.bar', 'read', [1001, 1002], ['name']),
297+
]
298+
self.assertCalls(*expected_calls)
299+
self.assertOutput('')
300+
279301
def test_read(self):
280302
FooBar = self.env['foo.bar']
281303

0 commit comments

Comments
 (0)