diff --git a/django/core/paginator.py b/django/core/paginator.py index 495cdf2d764..9f398137046 100644 --- a/django/core/paginator.py +++ b/django/core/paginator.py @@ -1,4 +1,5 @@ from math import ceil +import collections class InvalidPage(Exception): pass @@ -84,6 +85,44 @@ class Page(object): def __repr__(self): return '' % (self.number, self.paginator.num_pages) + def __len__(self): + return len(self.object_list) + + def __getitem__(self, index): + # 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] + + # The following four methods are only necessary for Python <2.6 + # compatibility (this class could just extend 2.6's collections.Sequence). + + def __iter__(self): + i = 0 + try: + while True: + v = self[i] + yield v + i += 1 + except IndexError: + return + + def __contains__(self, value): + for v in self: + if v == value: + return True + return False + + def index(self, value): + for i, v in enumerate(self): + if v == value: + return i + raise ValueError + + def count(self, value): + return sum([1 for v in self if v == value]) + + # End of compatibility methods. + def has_next(self): return self.number < self.paginator.num_pages diff --git a/docs/topics/pagination.txt b/docs/topics/pagination.txt index db776aaf00d..2ae3d5415eb 100644 --- a/docs/topics/pagination.txt +++ b/docs/topics/pagination.txt @@ -81,22 +81,20 @@ show how you can display the results. This example assumes you have a The view function looks like this:: - from django.core.paginator import Paginator, InvalidPage, EmptyPage + from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger def listing(request): contact_list = Contacts.objects.all() paginator = Paginator(contact_list, 25) # Show 25 contacts per page - # Make sure page request is an int. If not, deliver first page. - try: - page = int(request.GET.get('page', '1')) - except ValueError: - page = 1 - - # If page request (9999) is out of range, deliver last page of results. + page = request.GET.get('page') try: contacts = paginator.page(page) - except (EmptyPage, InvalidPage): + except PageNotAnInteger: + # If page is not an integer, deliver first page. + contacts = paginator.page(1) + except EmptyPage: + # If page is out of range (e.g. 9999), deliver last page of results. contacts = paginator.page(paginator.num_pages) return render_to_response('list.html', {"contacts": contacts}) @@ -104,7 +102,7 @@ The view function looks like this:: In the template :file:`list.html`, you'll want to include navigation between pages along with any interesting information from the objects themselves:: - {% for contact in contacts.object_list %} + {% for contact in contacts %} {# Each "contact" is a Contact model object. #} {{ contact.full_name|upper }}
... @@ -126,6 +124,11 @@ pages along with any interesting information from the objects themselves:: +.. versionchanged:: 1.4 + Previously, you would need to use + ``{% for contact in contacts.object_list %}``, since the ``Page`` + object was not iterable. + ``Paginator`` objects ===================== @@ -194,6 +197,7 @@ Attributes A 1-based range of page numbers, e.g., ``[1, 2, 3, 4]``. + ``InvalidPage`` exceptions ========================== @@ -221,6 +225,9 @@ them both with a simple ``except InvalidPage``. You usually won't construct :class:`Pages ` by hand -- you'll get them using :meth:`Paginator.page`. +.. versionadded:: 1.4 + A page acts like a sequence of :attr:`Page.object_list` when using + ``len()`` or iterating it directly. Methods ------- diff --git a/tests/regressiontests/pagination_regress/tests.py b/tests/regressiontests/pagination_regress/tests.py index 28fe3163eb4..f3bd0d1974c 100644 --- a/tests/regressiontests/pagination_regress/tests.py +++ b/tests/regressiontests/pagination_regress/tests.py @@ -154,3 +154,15 @@ class PaginatorTests(TestCase): self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 0, False), 1, None) self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 1, False), 1, None) self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 2, False), 1, None) + + def test_page_sequence(self): + """ + Tests that a paginator page acts like a standard sequence. + """ + eleven = 'abcdefghijk' + page2 = Paginator(eleven, per_page=5, orphans=1).page(2) + self.assertEqual(len(page2), 6) + self.assertTrue('k' in page2) + self.assertFalse('a' in page2) + self.assertEqual(''.join(page2), 'fghijk') + self.assertEqual(''.join(reversed(page2)), 'kjihgf')