Skip to content

Commit

Permalink
magic-removal: Implemented 'laziness' for QuerySet slicing, to restor…
Browse files Browse the repository at this point in the history
…e functionality that was previously possible with generic views via 'limit' and 'extra_lookup_args'

git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@2485 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
spookylukey committed Mar 4, 2006
1 parent f1c36bc commit ecd7f94
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 11 deletions.
59 changes: 48 additions & 11 deletions django/db/models/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,38 @@ def __iter__(self):

def __getitem__(self, k):
"Retrieve an item or slice from the set of results."
# __getitem__ can't return QuerySet instances, because filter() and
# order_by() on the result would break badly. This means we don't have
# to worry about arithmetic with self._limit or self._offset -- they'll
# both be None at this point.
if self._result_cache is None:
if isinstance(k, slice):
# Offset:
if self._offset is None:
offset = k.start
elif k.start is None:
offset = self._offset
else:
offset = self._offset + k.start
# Now adjust offset to the bounds of any existing limit:
if self._limit is not None and k.start is not None:
limit = self._limit - k.start
else:
limit = self._limit

# Limit:
if k.stop is not None and k.start is not None:
limit = k.stop - k.start
if limit is None:
limit = k.stop - k.start
else:
limit = min((k.stop - k.start), limit)
else:
limit = k.stop
return list(self._clone(_offset=k.start, _limit=limit))[::k.step]
if limit is None:
limit = k.stop
else:
if k.stop is not None:
limit = min(k.stop, limit)

if k.step is None:
return self._clone(_offset=offset, _limit=limit)
else:
return list(self._clone(_offset=offset, _limit=limit))[::k.step]
else:
return self._clone(_offset=k, _limit=1).get()
else:
Expand Down Expand Up @@ -179,13 +200,17 @@ def latest(self, field_name=None):
"""
latest_by = field_name or self.model._meta.get_latest_by
assert bool(latest_by), "latest() requires either a field_name parameter or 'get_latest_by' in the model"
assert self._limit is None and self._offset is None, \
"Cannot change a query once a slice has been taken."
return self._clone(_limit=1, _order_by=('-'+latest_by,)).get()

def in_bulk(self, id_list):
"""
Returns a dictionary mapping each of the given IDs to the object with
that ID.
"""
assert self._limit is None and self._offset is None, \
"Cannot use 'limit' or 'offset' with in_bulk"
assert isinstance(id_list, (tuple, list)), "in_bulk() must be provided with a list of IDs."
id_list = list(id_list)
assert id_list != [], "in_bulk() cannot be passed an empty ID list."
Expand All @@ -198,13 +223,14 @@ def delete(self):
"""
Deletes the records in the current QuerySet.
"""
assert self._limit is None and self._offset is None, \
"Cannot use 'limit' or 'offset' with delete."

del_query = self._clone()

# disable non-supported fields
del_query._select_related = False
del_query._order_by = []
del_query._offset = None
del_query._limit = None

# Collect all the objects to be deleted, and all the objects that are related to
# the objects that are to be deleted
Expand Down Expand Up @@ -251,6 +277,10 @@ def exclude(self, *args, **kwargs):
return self._filter_or_exclude(QNot, *args, **kwargs)

def _filter_or_exclude(self, qtype, *args, **kwargs):
if len(args) > 0 or len(kwargs) > 0:
assert self._limit is None and self._offset is None, \
"Cannot filter a query once a slice has been taken."

clone = self._clone()
if len(kwargs) > 0:
clone._filters = clone._filters & qtype(**kwargs)
Expand All @@ -264,13 +294,17 @@ def select_related(self, true_or_false=True):

def order_by(self, *field_names):
"Returns a new QuerySet instance with the ordering changed."
assert self._limit is None and self._offset is None, \
"Cannot reorder a query once a slice has been taken."
return self._clone(_order_by=field_names)

def distinct(self, true_or_false=True):
"Returns a new QuerySet instance with '_distinct' modified."
return self._clone(_distinct=true_or_false)

def extra(self, select=None, where=None, params=None, tables=None):
assert self._limit is None and self._offset is None, \
"Cannot change a query once a slice has been taken"
clone = self._clone()
if select: clone._select.extend(select)
if where: clone._where.extend(where)
Expand Down Expand Up @@ -301,8 +335,11 @@ def _clone(self, klass=None, **kwargs):
return c

def _combine(self, other):
if self._distinct != other._distinct:
raise ValueException, "Can't combine a unique query with a non-unique query"
assert self._limit is None and self._offset is None \
and other._limit is None and other._offset is None, \
"Cannot combine queries once a slice has been taken."
assert self._distinct == other._distinct, \
"Cannot combine a unique query with a non-unique query"
# use 'other's order by
# (so that A.filter(args1) & A.filter(args2) does the same as
# A.filter(args1).filter(args2)
Expand Down
45 changes: 45 additions & 0 deletions tests/modeltests/basic/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,51 @@ def __repr__(self):
>>> (s1 | s2 | s3)[::2]
[Area woman programs in Python, Third article]
# Slices (without step) are lazy:
>>> Article.objects.all()[0:5].filter()
[Area woman programs in Python, Second article, Third article, Fourth article, Article 6]
# Slicing again works:
>>> Article.objects.all()[0:5][0:2]
[Area woman programs in Python, Second article]
>>> Article.objects.all()[0:5][:2]
[Area woman programs in Python, Second article]
>>> Article.objects.all()[0:5][4:]
[Article 6]
>>> Article.objects.all()[0:5][5:]
[]
# Some more tests!
>>> Article.objects.all()[2:][0:2]
[Third article, Fourth article]
>>> Article.objects.all()[2:][:2]
[Third article, Fourth article]
>>> Article.objects.all()[2:][2:3]
[Article 6]
# Note that you can't use 'offset' without 'limit' (on some dbs), so this doesn't work:
>>> Article.objects.all()[2:]
Traceback (most recent call last):
...
AssertionError: 'offset' is not allowed without 'limit'
# Also, once you have sliced you can't filter, re-order or combine
>>> Article.objects.all()[0:5].filter(id=1)
Traceback (most recent call last):
...
AssertionError: Cannot filter a query once a slice has been taken.
>>> Article.objects.all()[0:5].order_by('id')
Traceback (most recent call last):
...
AssertionError: Cannot reorder a query once a slice has been taken.
>>> Article.objects.all()[0:1] & Article.objects.all()[4:5]
Traceback (most recent call last):
...
AssertionError: Cannot combine queries once a slice has been taken.
# An Article instance doesn't have access to the "objects" attribute.
# That's only available on the class.
>>> a7.objects.all()
Expand Down

0 comments on commit ecd7f94

Please sign in to comment.