Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

magic-removal: Implemented 'laziness' for QuerySet slicing, to restor…

…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...
commit ecd7f948dcc25222d9711254354d0f9292683566 1 parent f1c36bc
Luke Plant authored March 04, 2006
59  django/db/models/query.py
@@ -95,17 +95,38 @@ def __iter__(self):
95 95
 
96 96
     def __getitem__(self, k):
97 97
         "Retrieve an item or slice from the set of results."
98  
-        # __getitem__ can't return QuerySet instances, because filter() and
99  
-        # order_by() on the result would break badly. This means we don't have
100  
-        # to worry about arithmetic with self._limit or self._offset -- they'll
101  
-        # both be None at this point.
102 98
         if self._result_cache is None:
103 99
             if isinstance(k, slice):
  100
+                # Offset:
  101
+                if self._offset is None:
  102
+                    offset = k.start
  103
+                elif k.start is None:
  104
+                    offset = self._offset
  105
+                else:
  106
+                    offset = self._offset + k.start
  107
+                # Now adjust offset to the bounds of any existing limit:
  108
+                if self._limit is not None and k.start is not None:
  109
+                    limit = self._limit - k.start
  110
+                else:
  111
+                    limit = self._limit
  112
+
  113
+                # Limit:
104 114
                 if k.stop is not None and k.start is not None:
105  
-                    limit = k.stop - k.start
  115
+                    if limit is None:
  116
+                        limit = k.stop - k.start
  117
+                    else:
  118
+                        limit = min((k.stop - k.start), limit)
106 119
                 else:
107  
-                    limit = k.stop
108  
-                return list(self._clone(_offset=k.start, _limit=limit))[::k.step]
  120
+                    if limit is None:
  121
+                        limit = k.stop
  122
+                    else:
  123
+                        if k.stop is not None:
  124
+                            limit = min(k.stop, limit)
  125
+
  126
+                if k.step is None:
  127
+                    return self._clone(_offset=offset, _limit=limit)
  128
+                else:
  129
+                    return list(self._clone(_offset=offset, _limit=limit))[::k.step]
109 130
             else:
110 131
                 return self._clone(_offset=k, _limit=1).get()
111 132
         else:
@@ -179,6 +200,8 @@ def latest(self, field_name=None):
179 200
         """
180 201
         latest_by = field_name or self.model._meta.get_latest_by
181 202
         assert bool(latest_by), "latest() requires either a field_name parameter or 'get_latest_by' in the model"
  203
+        assert self._limit is None and self._offset is None, \
  204
+                "Cannot change a query once a slice has been taken."
182 205
         return self._clone(_limit=1, _order_by=('-'+latest_by,)).get()
183 206
 
184 207
     def in_bulk(self, id_list):
@@ -186,6 +209,8 @@ def in_bulk(self, id_list):
186 209
         Returns a dictionary mapping each of the given IDs to the object with
187 210
         that ID.
188 211
         """
  212
+        assert self._limit is None and self._offset is None, \
  213
+                "Cannot use 'limit' or 'offset' with in_bulk"
189 214
         assert isinstance(id_list, (tuple,  list)), "in_bulk() must be provided with a list of IDs."
190 215
         id_list = list(id_list)
191 216
         assert id_list != [], "in_bulk() cannot be passed an empty ID list."
@@ -198,13 +223,14 @@ def delete(self):
198 223
         """
199 224
         Deletes the records in the current QuerySet.
200 225
         """
  226
+        assert self._limit is None and self._offset is None, \
  227
+            "Cannot use 'limit' or 'offset' with delete."
  228
+
201 229
         del_query = self._clone()
202 230
 
203 231
         # disable non-supported fields
204 232
         del_query._select_related = False
205 233
         del_query._order_by = []
206  
-        del_query._offset = None
207  
-        del_query._limit = None
208 234
 
209 235
         # Collect all the objects to be deleted, and all the objects that are related to
210 236
         # the objects that are to be deleted
@@ -251,6 +277,10 @@ def exclude(self, *args, **kwargs):
251 277
         return self._filter_or_exclude(QNot, *args, **kwargs)
252 278
         
253 279
     def _filter_or_exclude(self, qtype, *args, **kwargs):
  280
+        if len(args) > 0 or len(kwargs) > 0:
  281
+            assert self._limit is None and self._offset is None, \
  282
+                "Cannot filter a query once a slice has been taken."
  283
+
254 284
         clone = self._clone()
255 285
         if len(kwargs) > 0:
256 286
             clone._filters = clone._filters & qtype(**kwargs)
@@ -264,6 +294,8 @@ def select_related(self, true_or_false=True):
264 294
 
265 295
     def order_by(self, *field_names):
266 296
         "Returns a new QuerySet instance with the ordering changed."
  297
+        assert self._limit is None and self._offset is None, \
  298
+                "Cannot reorder a query once a slice has been taken."
267 299
         return self._clone(_order_by=field_names)
268 300
 
269 301
     def distinct(self, true_or_false=True):
@@ -271,6 +303,8 @@ def distinct(self, true_or_false=True):
271 303
         return self._clone(_distinct=true_or_false)
272 304
 
273 305
     def extra(self, select=None, where=None, params=None, tables=None):
  306
+        assert self._limit is None and self._offset is None, \
  307
+                "Cannot change a query once a slice has been taken"
274 308
         clone = self._clone()
275 309
         if select: clone._select.extend(select)
276 310
         if where: clone._where.extend(where)
@@ -301,8 +335,11 @@ def _clone(self, klass=None, **kwargs):
301 335
         return c
302 336
 
303 337
     def _combine(self, other):
304  
-        if self._distinct != other._distinct:
305  
-            raise ValueException, "Can't combine a unique query with a non-unique query"
  338
+        assert self._limit is None and self._offset is None \
  339
+            and other._limit is None and other._offset is None, \
  340
+            "Cannot combine queries once a slice has been taken."
  341
+        assert self._distinct == other._distinct, \
  342
+            "Cannot combine a unique query with a non-unique query"
306 343
         #  use 'other's order by
307 344
         #  (so that A.filter(args1) & A.filter(args2) does the same as
308 345
         #   A.filter(args1).filter(args2)
45  tests/modeltests/basic/models.py
@@ -239,6 +239,51 @@ def __repr__(self):
239 239
 >>> (s1 | s2 | s3)[::2]
240 240
 [Area woman programs in Python, Third article]
241 241
 
  242
+# Slices (without step) are lazy:
  243
+>>> Article.objects.all()[0:5].filter()
  244
+[Area woman programs in Python, Second article, Third article, Fourth article, Article 6]
  245
+
  246
+# Slicing again works:
  247
+>>> Article.objects.all()[0:5][0:2]
  248
+[Area woman programs in Python, Second article]
  249
+>>> Article.objects.all()[0:5][:2]
  250
+[Area woman programs in Python, Second article]
  251
+>>> Article.objects.all()[0:5][4:]
  252
+[Article 6]
  253
+>>> Article.objects.all()[0:5][5:]
  254
+[]
  255
+
  256
+# Some more tests!
  257
+>>> Article.objects.all()[2:][0:2]
  258
+[Third article, Fourth article]
  259
+>>> Article.objects.all()[2:][:2]
  260
+[Third article, Fourth article]
  261
+>>> Article.objects.all()[2:][2:3]
  262
+[Article 6]
  263
+
  264
+# Note that you can't use 'offset' without 'limit' (on some dbs), so this doesn't work:
  265
+>>> Article.objects.all()[2:]
  266
+Traceback (most recent call last):
  267
+    ...
  268
+AssertionError: 'offset' is not allowed without 'limit'
  269
+
  270
+# Also, once you have sliced you can't filter, re-order or combine
  271
+>>> Article.objects.all()[0:5].filter(id=1)
  272
+Traceback (most recent call last):
  273
+    ...
  274
+AssertionError: Cannot filter a query once a slice has been taken.
  275
+
  276
+>>> Article.objects.all()[0:5].order_by('id')
  277
+Traceback (most recent call last):
  278
+    ...
  279
+AssertionError: Cannot reorder a query once a slice has been taken.
  280
+
  281
+>>> Article.objects.all()[0:1] & Article.objects.all()[4:5]
  282
+Traceback (most recent call last):
  283
+    ...
  284
+AssertionError: Cannot combine queries once a slice has been taken.
  285
+
  286
+
242 287
 # An Article instance doesn't have access to the "objects" attribute.
243 288
 # That's only available on the class.
244 289
 >>> a7.objects.all()

0 notes on commit ecd7f94

Please sign in to comment.
Something went wrong with that request. Please try again.