diff --git a/django/core/paginator.py b/django/core/paginator.py index 6b0b3542f89..084a29b77fb 100644 --- a/django/core/paginator.py +++ b/django/core/paginator.py @@ -1,5 +1,8 @@ from math import ceil +from django.utils import six + + class InvalidPage(Exception): pass @@ -88,6 +91,8 @@ class Page(object): return len(self.object_list) def __getitem__(self, index): + if not isinstance(index, (slice,) + six.integer_types): + raise TypeError # The object_list is converted to a list so that if it was a QuerySet # it won't be a database hit per __getitem__. return list(self.object_list)[index] diff --git a/tests/regressiontests/pagination/tests.py b/tests/regressiontests/pagination/tests.py index a49f9b8fa12..63ccd8f61cc 100644 --- a/tests/regressiontests/pagination/tests.py +++ b/tests/regressiontests/pagination/tests.py @@ -266,3 +266,25 @@ class ModelPaginationTests(TestCase): self.assertEqual(1, p.previous_page_number()) self.assertEqual(6, p.start_index()) self.assertEqual(9, p.end_index()) + + def test_page_getitem(self): + """ + Tests proper behaviour of a paginator page __getitem__ (queryset + evaluation, slicing, exception raised). + """ + paginator = Paginator(Article.objects.all(), 5) + p = paginator.page(1) + + # Make sure object_list queryset is not evaluated by an invalid __getitem__ call. + # (this happens from the template engine when using eg: {% page_obj.has_previous %}) + self.assertIsNone(p.object_list._result_cache) + self.assertRaises(TypeError, lambda: p['has_previous']) + self.assertIsNone(p.object_list._result_cache) + + # Make sure slicing the Page object with numbers and slice objects work. + self.assertEqual(p[0], Article.objects.get(headline='Article 1')) + self.assertQuerysetEqual(p[slice(2)], [ + "", + "", + ] + )