Browse Source

Fixed #22550 -- Prohibited QuerySet.last()/reverse() after slicing.

Matthias Erll 11 years ago
parent
commit
eee34ef64c

+ 2 - 0
django/db/models/query.py

@@ -944,6 +944,8 @@ class QuerySet:
 
     def reverse(self):
         """Reverse the ordering of the QuerySet."""
+        if not self.query.can_filter():
+            raise TypeError('Cannot reverse a query once a slice has been taken.')
         clone = self._clone()
         clone.query.standard_ordering = not clone.query.standard_ordering
         return clone

+ 12 - 0
docs/releases/2.0.txt

@@ -314,6 +314,18 @@ If you wish to keep this restriction in the admin when editing users, set
     admin.site.unregister(User)
     admin.site.register(User, MyUserAdmin)
 
+``QuerySet.reverse()`` and ``last()`` are prohibited after slicing
+------------------------------------------------------------------
+
+Calling ``QuerySet.reverse()`` or ``last()`` on a sliced queryset leads to
+unexpected results due to the slice being applied after reordering. This is
+now prohibited, e.g.::
+
+    >>> Model.objects.all()[:2].reverse()
+    Traceback (most recent call last):
+    ...
+    TypeError: Cannot reverse a query once a slice has been taken.
+
 Miscellaneous
 -------------
 

+ 3 - 0
docs/topics/db/queries.txt

@@ -361,6 +361,9 @@ every *second* object of the first 10::
 
     >>> Entry.objects.all()[:10:2]
 
+Further filtering or ordering of a sliced queryset is prohibited due to the
+ambiguous nature of how that might work.
+
 To retrieve a *single* object rather than a list
 (e.g. ``SELECT foo FROM bar LIMIT 1``), use a simple index instead of a
 slice. For example, this returns the first ``Entry`` in the database, after

+ 8 - 0
tests/ordering/tests.py

@@ -203,6 +203,14 @@ class OrderingTests(TestCase):
             attrgetter("headline")
         )
 
+    def test_no_reordering_after_slicing(self):
+        msg = 'Cannot reverse a query once a slice has been taken.'
+        qs = Article.objects.all()[0:2]
+        with self.assertRaisesMessage(TypeError, msg):
+            qs.reverse()
+        with self.assertRaisesMessage(TypeError, msg):
+            qs.last()
+
     def test_extra_ordering(self):
         """
         Ordering can be based on fields included from an 'extra' clause

+ 1 - 1
tests/queries/tests.py

@@ -731,10 +731,10 @@ class Queries1Tests(TestCase):
                 q.extra(select={'foo': "1"}),
                 []
             )
+            self.assertQuerysetEqual(q.reverse(), [])
             q.query.low_mark = 1
             with self.assertRaisesMessage(AssertionError, 'Cannot change a query once a slice has been taken'):
                 q.extra(select={'foo': "1"})
-            self.assertQuerysetEqual(q.reverse(), [])
             self.assertQuerysetEqual(q.defer('meal'), [])
             self.assertQuerysetEqual(q.only('meal'), [])