Skip to content

Commit 88c7a40

Browse files
committed
Lazy evaluation of RecordList[...] slicing
1 parent 0ade159 commit 88c7a40

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
@@ -2162,6 +2162,27 @@ def refresh(self):
21622162
for key in 'id', 'ids', '_idnames':
21632163
self.__dict__.pop(key, None)
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 = key.start or 0
2171+
new_stop = search_args.get('limit', None)
2172+
if is_stop_positive:
2173+
new_stop = key.stop if new_stop is None else min(new_stop, key.stop)
2174+
if new_stop is not None:
2175+
search_args['limit'] = limit = new_stop - new_offset
2176+
if limit <= 0:
2177+
return RecordList(self._model, [])
2178+
if new_offset:
2179+
search_args['offset'] = (search_args.get('offset') or 0) + new_offset
2180+
result = RecordList(self._model, None, search=search_args)
2181+
if (is_stop_positive or key.stop is None) and key.step in (1, None):
2182+
return result
2183+
key = slice(None, limit if is_stop_positive else key.stop, key.step)
2184+
return super(RecordList, result).__getitem__(key)
2185+
21652186
def with_env(self, env):
21662187
if 'id' in self.__dict__:
21672188
return super().with_env(env)

tests/test_model.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,11 @@ def test_search(self):
193193
self.assertIsInstance(FooBar.search([searchterm]), odooly.RecordList)
194194
FooBar.search([searchterm], limit=2)
195195
FooBar.search([searchterm], offset=80, limit=99)
196+
FooBar.search([searchterm], offset=80, limit=99)[100:400:3]
196197
FooBar.search([searchterm], order='name ASC')
198+
FooBar.search([searchterm], order='name ASC')[:3]
199+
FooBar.search([searchterm], order='name ASC')[40:]
200+
FooBar.search([searchterm], order='name ASC')[40:99]
197201
FooBar.search(['name = mushroom', 'state != draft'])
198202
FooBar.search([('name', 'like', 'Morice')])
199203
FooBar.search([])
@@ -205,15 +209,14 @@ def test_search(self):
205209

206210
FooBar.search([searchterm], limit=2).ids
207211
FooBar.search([searchterm], offset=80, limit=99).id
208-
FooBar.search([searchterm], order='name ASC')[:3]
209212
FooBar.search(['name = mushroom', 'state != draft']) or 42
210213
FooBar.search([('name', 'like', 'Morice')]).ids
211214
FooBar._execute('search', [('name like Morice')])[0]
212215
FooBar.search([]).ids
216+
213217
self.assertCalls(
214218
OBJ('foo.bar', 'search', domain, 0, 2, None),
215219
OBJ('foo.bar', 'search', domain, 80, 99, None),
216-
OBJ('foo.bar', 'search', domain, 0, None, 'name ASC'),
217220
OBJ('foo.bar', 'search', domain2),
218221
OBJ('foo.bar', 'search', domain),
219222
OBJ('foo.bar', 'search', domain),
@@ -282,6 +285,25 @@ def test_search_count(self):
282285
self.assertCalls()
283286
self.assertOutput('')
284287

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

0 commit comments

Comments
 (0)