Skip to content

Commit f18e253

Browse files
committed
Lazy evaluation of RecordList[...] slicing
1 parent 8df608e commit f18e253

File tree

3 files changed

+50
-2
lines changed

3 files changed

+50
-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
* Consider users with restricted rights. Since Odoo 15, most
2126
users don't have access to ``ir.model``.
2227

odooly.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2157,6 +2157,27 @@ def refresh(self):
21572157
for key in 'id', 'ids', '_idnames':
21582158
self.__dict__.pop(key, None)
21592159

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

tests/test_model.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,11 @@ def test_search(self):
195195
self.assertIsInstance(FooBar.search([searchterm]), odooly.RecordList)
196196
FooBar.search([searchterm], limit=2)
197197
FooBar.search([searchterm], offset=80, limit=99)
198+
FooBar.search([searchterm], offset=80, limit=99)[100:400:3]
198199
FooBar.search([searchterm], order='name ASC')
200+
FooBar.search([searchterm], order='name ASC')[:3]
201+
FooBar.search([searchterm], order='name ASC')[40:]
202+
FooBar.search([searchterm], order='name ASC')[40:99]
199203
FooBar.search(['name = mushroom', 'state != draft'])
200204
FooBar.search([('name', 'like', 'Morice')])
201205
FooBar.search([])
@@ -207,15 +211,14 @@ def test_search(self):
207211

208212
FooBar.search([searchterm], limit=2).ids
209213
FooBar.search([searchterm], offset=80, limit=99).id
210-
FooBar.search([searchterm], order='name ASC')[:3]
211214
FooBar.search(['name = mushroom', 'state != draft']) or 42
212215
FooBar.search([('name', 'like', 'Morice')]).ids
213216
FooBar._execute('search', [('name like Morice')])[0]
214217
FooBar.search([]).ids
218+
215219
self.assertCalls(
216220
OBJ('foo.bar', 'search', domain, 0, 2, None),
217221
OBJ('foo.bar', 'search', domain, 80, 99, None),
218-
OBJ('foo.bar', 'search', domain, 0, None, 'name ASC'),
219222
OBJ('foo.bar', 'search', domain2),
220223
OBJ('foo.bar', 'search', domain),
221224
OBJ('foo.bar', 'search', domain),
@@ -284,6 +287,25 @@ def test_search_count(self):
284287
self.assertCalls()
285288
self.assertOutput('')
286289

290+
def test_search_read(self):
291+
FooBar = self.env['foo.bar']
292+
searchterm = 'name like Morice'
293+
domain = [('name', 'like', 'Morice')]
294+
295+
FooBar.search([searchterm], order='name ASC')[:3].name
296+
297+
expected_calls = [
298+
OBJ('foo.bar', 'fields_get'),
299+
OBJ('foo.bar', 'search_read', domain, ['name'], order='name ASC', limit=3),
300+
]
301+
if float(self.server_version) < 8.0:
302+
expected_calls[1:2] = [
303+
OBJ('foo.bar', 'search', domain, 0, 3, 'name ASC'),
304+
OBJ('foo.bar', 'read', [1001, 1002], ['name']),
305+
]
306+
self.assertCalls(*expected_calls)
307+
self.assertOutput('')
308+
287309
def test_read(self):
288310
FooBar = self.env['foo.bar']
289311

0 commit comments

Comments
 (0)