Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixes #11596 -- Make paginator.Page iterable

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16018 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 4fa96467164d738c6f8a9c59423382257f95b747 1 parent de5f075
Chris Beaven authored April 06, 2011
39  django/core/paginator.py
... ...
@@ -1,4 +1,5 @@
1 1
 from math import ceil
  2
+import collections
2 3
 
3 4
 class InvalidPage(Exception):
4 5
     pass
@@ -84,6 +85,44 @@ def __init__(self, object_list, number, paginator):
84 85
     def __repr__(self):
85 86
         return '<Page %s of %s>' % (self.number, self.paginator.num_pages)
86 87
 
  88
+    def __len__(self):
  89
+        return len(self.object_list)
  90
+
  91
+    def __getitem__(self, index):
  92
+        # The object_list is converted to a list so that if it was a QuerySet
  93
+        # it won't be a database hit per __getitem__.
  94
+        return list(self.object_list)[index]
  95
+
  96
+    # The following four methods are only necessary for Python <2.6
  97
+    # compatibility (this class could just extend 2.6's collections.Sequence).
  98
+
  99
+    def __iter__(self):
  100
+        i = 0
  101
+        try:
  102
+            while True:
  103
+                v = self[i]
  104
+                yield v
  105
+                i += 1
  106
+        except IndexError:
  107
+            return
  108
+
  109
+    def __contains__(self, value):
  110
+        for v in self:
  111
+            if v == value:
  112
+                return True
  113
+        return False
  114
+
  115
+    def index(self, value):
  116
+        for i, v in enumerate(self):
  117
+            if v == value:
  118
+                return i
  119
+        raise ValueError
  120
+
  121
+    def count(self, value):
  122
+        return sum([1 for v in self if v == value])
  123
+
  124
+    # End of compatibility methods.
  125
+
87 126
     def has_next(self):
88 127
         return self.number < self.paginator.num_pages
89 128
 
27  docs/topics/pagination.txt
@@ -81,22 +81,20 @@ show how you can display the results. This example assumes you have a
81 81
 
82 82
 The view function looks like this::
83 83
 
84  
-    from django.core.paginator import Paginator, InvalidPage, EmptyPage
  84
+    from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
85 85
 
86 86
     def listing(request):
87 87
         contact_list = Contacts.objects.all()
88 88
         paginator = Paginator(contact_list, 25) # Show 25 contacts per page
89 89
 
90  
-        # Make sure page request is an int. If not, deliver first page.
91  
-        try:
92  
-            page = int(request.GET.get('page', '1'))
93  
-        except ValueError:
94  
-            page = 1
95  
-
96  
-        # If page request (9999) is out of range, deliver last page of results.
  90
+        page = request.GET.get('page')
97 91
         try:
98 92
             contacts = paginator.page(page)
99  
-        except (EmptyPage, InvalidPage):
  93
+        except PageNotAnInteger:
  94
+            # If page is not an integer, deliver first page.
  95
+            contacts = paginator.page(1)
  96
+        except EmptyPage:
  97
+            # If page is out of range (e.g. 9999), deliver last page of results.
100 98
             contacts = paginator.page(paginator.num_pages)
101 99
 
102 100
         return render_to_response('list.html', {"contacts": contacts})
@@ -104,7 +102,7 @@ The view function looks like this::
104 102
 In the template :file:`list.html`, you'll want to include navigation between
105 103
 pages along with any interesting information from the objects themselves::
106 104
 
107  
-    {% for contact in contacts.object_list %}
  105
+    {% for contact in contacts %}
108 106
         {# Each "contact" is a Contact model object. #}
109 107
         {{ contact.full_name|upper }}<br />
110 108
         ...
@@ -126,6 +124,11 @@ pages along with any interesting information from the objects themselves::
126 124
         </span>
127 125
     </div>
128 126
 
  127
+.. versionchanged:: 1.4
  128
+    Previously, you would need to use
  129
+    ``{% for contact in contacts.object_list %}``, since the ``Page``
  130
+    object was not iterable.
  131
+
129 132
 
130 133
 ``Paginator`` objects
131 134
 =====================
@@ -194,6 +197,7 @@ Attributes
194 197
 
195 198
     A 1-based range of page numbers, e.g., ``[1, 2, 3, 4]``.
196 199
 
  200
+
197 201
 ``InvalidPage`` exceptions
198 202
 ==========================
199 203
 
@@ -221,6 +225,9 @@ them both with a simple ``except InvalidPage``.
221 225
 You usually won't construct :class:`Pages <Page>` by hand -- you'll get them
222 226
 using :meth:`Paginator.page`.
223 227
 
  228
+.. versionadded:: 1.4
  229
+    A page acts like a sequence of :attr:`Page.object_list` when using
  230
+    ``len()`` or iterating it directly.
224 231
 
225 232
 Methods
226 233
 -------
12  tests/regressiontests/pagination_regress/tests.py
@@ -154,3 +154,15 @@ def test_page_indexes(self):
154 154
         self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 0, False), 1, None)
155 155
         self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 1, False), 1, None)
156 156
         self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 2, False), 1, None)
  157
+
  158
+    def test_page_sequence(self):
  159
+        """
  160
+        Tests that a paginator page acts like a standard sequence.
  161
+        """
  162
+        eleven = 'abcdefghijk'
  163
+        page2 = Paginator(eleven, per_page=5, orphans=1).page(2)
  164
+        self.assertEqual(len(page2), 6)
  165
+        self.assertTrue('k' in page2)
  166
+        self.assertFalse('a' in page2)
  167
+        self.assertEqual(''.join(page2), 'fghijk')
  168
+        self.assertEqual(''.join(reversed(page2)), 'kjihgf')

0 notes on commit 4fa9646

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