Code

Ticket #2367: 5992-pagination-for-datebased-generic-views.diff

File 5992-pagination-for-datebased-generic-views.diff, 16.4 KB (added by Øyvind Saltvik <oyvind@…>, 7 years ago)

removed Http404 dependency in pagination.py, reraised InvalidPage, wrapped compute_pagination in a try except, use count instead of len on queryset.

Line 
1Index: django/core/paginator.py
2===================================================================
3--- django/core/paginator.py    (revision 5992)
4+++ django/core/paginator.py    (working copy)
5@@ -86,3 +86,31 @@
6 
7     hits = property(_get_hits)
8     pages = property(_get_pages)
9+
10+def compute_pagination(paginator, page, paginate_by, allow_empty):
11+    """
12+    Compute queryset considering pagination, and update the context.
13+    """
14+    try:
15+        page = int(page)
16+        queryset = paginator.get_page(page - 1)
17+    except (InvalidPage, ValueError):
18+        if page == 1 and allow_empty:
19+            queryset = paginator.query_set.empty()
20+        else:
21+            raise InvalidPage
22+
23+    c = {}
24+    c['is_paginated'] = paginator.pages > 1
25+    c['results_per_page'] = paginate_by
26+    c['has_next'] = paginator.has_next_page(page - 1)
27+    c['has_previous'] = paginator.has_previous_page(page - 1)
28+    c['page'] = page
29+    c['next'] = page + 1
30+    c['previous'] = page - 1
31+    c['last_on_page'] = paginator.last_on_page(page - 1),
32+    c['first_on_page'] = paginator.first_on_page(page - 1),
33+    c['pages'] = paginator.pages
34+    c['hits'] = paginator.hits
35+
36+    return (queryset, c)
37Index: django/views/generic/date_based.py
38===================================================================
39--- django/views/generic/date_based.py  (revision 5992)
40+++ django/views/generic/date_based.py  (working copy)
41@@ -1,6 +1,7 @@
42 from django.template import loader, RequestContext
43 from django.core.exceptions import ObjectDoesNotExist
44 from django.core.xheaders import populate_xheaders
45+from django.core.paginator import compute_pagination, ObjectPaginator, InvalidPage
46 from django.db.models.fields import DateTimeField
47 from django.http import Http404, HttpResponse
48 import datetime, time
49@@ -8,7 +9,7 @@
50 def archive_index(request, queryset, date_field, num_latest=15,
51         template_name=None, template_loader=loader,
52         extra_context=None, allow_empty=False, context_processors=None,
53-        mimetype=None, allow_future=False):
54+        mimetype=None, allow_future=False, paginate_by=None, page=None):
55     """
56     Generic top-level archive of date-based objects.
57 
58@@ -18,6 +19,30 @@
59             List of years
60         latest
61             Latest N (defaults to 15) objects by date
62+        is_paginated
63+            are the results paginated?
64+        results_per_page
65+            number of objects per page (if paginated)
66+        has_next
67+            is there a next page?
68+        has_previous
69+            is there a prev page?
70+        page
71+            the current page
72+        next
73+            the next page
74+        previous
75+            the previous page
76+        pages
77+            number of pages, total
78+        hits
79+            number of objects, total
80+        last_on_page
81+            the result number of the last of object in the
82+            object_list (1-indexed)
83+        first_on_page
84+            the result number of the first object in the
85+            object_list (1-indexed)
86     """
87     if extra_context is None: extra_context = {}
88     model = queryset.model
89@@ -30,8 +55,18 @@
90     if date_list and num_latest:
91         latest = queryset.order_by('-'+date_field)[:num_latest]
92     else:
93-        latest = None
94+        latest = []
95 
96+    # Pagination.
97+    pagination_context = {'is_paginated': False}
98+    if paginate_by:
99+        paginator = ObjectPaginator(latest, paginate_by)
100+        page = page or request.GET.get('page', 1)
101+        try:
102+            latest, pagination_context = compute_pagination(paginator, page, paginate_by, allow_empty)
103+        except InvalidPage:
104+            raise Http404
105+
106     if not template_name:
107         template_name = "%s/%s_archive.html" % (model._meta.app_label, model._meta.object_name.lower())
108     t = template_loader.get_template(template_name)
109@@ -44,12 +79,15 @@
110             c[key] = value()
111         else:
112             c[key] = value
113+
114+    c.update(pagination_context)
115+
116     return HttpResponse(t.render(c), mimetype=mimetype)
117 
118 def archive_year(request, year, queryset, date_field, template_name=None,
119         template_loader=loader, extra_context=None, allow_empty=False,
120         context_processors=None, template_object_name='object', mimetype=None,
121-        make_object_list=False, allow_future=False):
122+        make_object_list=False, allow_future=False, paginate_by=None, page=None):
123     """
124     Generic yearly archive view.
125 
126@@ -61,7 +99,33 @@
127             This year
128         object_list
129             List of objects published in the given month
130+
131             (Only available if make_object_list argument is True)
132+
133+        is_paginated
134+            are the results paginated?
135+        results_per_page
136+            number of objects per page (if paginated)
137+        has_next
138+            is there a next page?
139+        has_previous
140+            is there a prev page?
141+        page
142+            the current page
143+        next
144+            the next page
145+        previous
146+            the previous page
147+        pages
148+            number of pages, total
149+        hits
150+            number of objects, total
151+        last_on_page
152+            the result number of the last of object in the
153+            object_list (1-indexed)
154+        first_on_page
155+            the result number of the first object in the
156+            object_list (1-indexed)
157     """
158     if extra_context is None: extra_context = {}
159     model = queryset.model
160@@ -79,6 +143,17 @@
161         object_list = queryset.filter(**lookup_kwargs).order_by(date_field)
162     else:
163         object_list = []
164+
165+    # Pagination.
166+    pagination_context = {'is_paginated': False}
167+    if paginate_by:
168+        paginator = ObjectPaginator(object_list, paginate_by)
169+        page = page or request.GET.get('page', 1)
170+        try:
171+            object_list, pagination_context = compute_pagination(paginator, page, paginate_by, allow_empty)
172+        except InvalidPage:
173+            raise Http404
174+
175     if not template_name:
176         template_name = "%s/%s_archive_year.html" % (model._meta.app_label, model._meta.object_name.lower())
177     t = template_loader.get_template(template_name)
178@@ -92,12 +167,16 @@
179             c[key] = value()
180         else:
181             c[key] = value
182+
183+    c.update(pagination_context)
184+
185     return HttpResponse(t.render(c), mimetype=mimetype)
186 
187 def archive_month(request, year, month, queryset, date_field,
188         month_format='%b', template_name=None, template_loader=loader,
189         extra_context=None, allow_empty=False, context_processors=None,
190-        template_object_name='object', mimetype=None, allow_future=False):
191+        template_object_name='object', mimetype=None, allow_future=False,
192+        paginate_by=None, page=None):
193     """
194     Generic monthly archive view.
195 
196@@ -111,6 +190,30 @@
197             (date) the first day of the previous month
198         object_list:
199             list of objects published in the given month
200+        is_paginated
201+            are the results paginated?
202+        results_per_page
203+            number of objects per page (if paginated)
204+        has_next
205+            is there a next page?
206+        has_previous
207+            is there a prev page?
208+        page
209+            the current page
210+        next
211+            the next page
212+        previous
213+            the previous page
214+        pages
215+            number of pages, total
216+        hits
217+            number of objects, total
218+        last_on_page
219+            the result number of the last of object in the
220+            object_list (1-indexed)
221+        first_on_page
222+            the result number of the first object in the
223+            object_list (1-indexed)
224     """
225     if extra_context is None: extra_context = {}
226     try:
227@@ -144,6 +247,16 @@
228     else:
229         next_month = None
230 
231+    # Pagination.
232+    pagination_context = {'is_paginated': False}
233+    if paginate_by:
234+        paginator = ObjectPaginator(object_list, paginate_by)
235+        page = page or request.GET.get('page', 1)
236+        try:
237+            object_list, pagination_context = compute_pagination(paginator, page, paginate_by, allow_empty)
238+        except InvalidPage:
239+            raise Http404
240+
241     if not template_name:
242         template_name = "%s/%s_archive_month.html" % (model._meta.app_label, model._meta.object_name.lower())
243     t = template_loader.get_template(template_name)
244@@ -158,12 +271,16 @@
245             c[key] = value()
246         else:
247             c[key] = value
248+
249+    c.update(pagination_context)
250+
251     return HttpResponse(t.render(c), mimetype=mimetype)
252 
253 def archive_week(request, year, week, queryset, date_field,
254         template_name=None, template_loader=loader,
255         extra_context=None, allow_empty=True, context_processors=None,
256-        template_object_name='object', mimetype=None, allow_future=False):
257+        template_object_name='object', mimetype=None, allow_future=False,
258+        paginate_by=None, page=None):
259     """
260     Generic weekly archive view.
261 
262@@ -173,6 +290,30 @@
263             (date) this week
264         object_list:
265             list of objects published in the given week
266+        is_paginated
267+            are the results paginated?
268+        results_per_page
269+            number of objects per page (if paginated)
270+        has_next
271+            is there a next page?
272+        has_previous
273+            is there a prev page?
274+        page
275+            the current page
276+        next
277+            the next page
278+        previous
279+            the previous page
280+        pages
281+            number of pages, total
282+        hits
283+            number of objects, total
284+        last_on_page
285+            the result number of the last of object in the
286+            object_list (1-indexed)
287+        first_on_page
288+            the result number of the first object in the
289+            object_list (1-indexed)
290     """
291     if extra_context is None: extra_context = {}
292     try:
293@@ -194,6 +335,17 @@
294     object_list = queryset.filter(**lookup_kwargs)
295     if not object_list and not allow_empty:
296         raise Http404
297+
298+    # Pagination.
299+    pagination_context = {'is_paginated': False}
300+    if paginate_by:
301+        paginator = ObjectPaginator(object_list, paginate_by)
302+        page = page or request.GET.get('page', 1)
303+        try:
304+            object_list, pagination_context = compute_pagination(paginator, page, paginate_by, allow_empty)
305+        except InvalidPage:
306+            raise Http404
307+
308     if not template_name:
309         template_name = "%s/%s_archive_week.html" % (model._meta.app_label, model._meta.object_name.lower())
310     t = template_loader.get_template(template_name)
311@@ -206,13 +358,16 @@
312             c[key] = value()
313         else:
314             c[key] = value
315+
316+    c.update(pagination_context)
317+
318     return HttpResponse(t.render(c), mimetype=mimetype)
319 
320 def archive_day(request, year, month, day, queryset, date_field,
321         month_format='%b', day_format='%d', template_name=None,
322         template_loader=loader, extra_context=None, allow_empty=False,
323         context_processors=None, template_object_name='object',
324-        mimetype=None, allow_future=False):
325+        mimetype=None, allow_future=False, paginate_by=None, page=None):
326     """
327     Generic daily archive view.
328 
329@@ -226,6 +381,30 @@
330             (datetime) the previous day
331         next_day
332             (datetime) the next day, or None if the current day is today
333+        is_paginated
334+            are the results paginated?
335+        results_per_page
336+            number of objects per page (if paginated)
337+        has_next
338+            is there a next page?
339+        has_previous
340+            is there a prev page?
341+        page
342+            the current page
343+        next
344+            the next page
345+        previous
346+            the previous page
347+        pages
348+            number of pages, total
349+        hits
350+            number of objects, total
351+        last_on_page
352+            the result number of the last of object in the
353+            object_list (1-indexed)
354+        first_on_page
355+            the result number of the first object in the
356+            object_list (1-indexed)
357     """
358     if extra_context is None: extra_context = {}
359     try:
360@@ -256,6 +435,16 @@
361     else:
362         next_day = None
363 
364+    # Pagination.
365+    pagination_context = {'is_paginated': False}
366+    if paginate_by:
367+        paginator = ObjectPaginator(object_list, paginate_by)
368+        page = page or request.GET.get('page', 1)
369+        try:
370+            object_list, pagination_context = compute_pagination(paginator, page, paginate_by, allow_empty)
371+        except InvalidPage:
372+            raise Http404
373+
374     if not template_name:
375         template_name = "%s/%s_archive_day.html" % (model._meta.app_label, model._meta.object_name.lower())
376     t = template_loader.get_template(template_name)
377@@ -270,6 +459,9 @@
378             c[key] = value()
379         else:
380             c[key] = value
381+
382+    c.update(pagination_context)
383+
384     return HttpResponse(t.render(c), mimetype=mimetype)
385 
386 def archive_today(request, **kwargs):
387@@ -288,7 +480,7 @@
388         month_format='%b', day_format='%d', object_id=None, slug=None,
389         slug_field='slug', template_name=None, template_name_field=None,
390         template_loader=loader, extra_context=None, context_processors=None,
391-        template_object_name='object', mimetype=None, allow_future=False):
392+        template_object_name='object', mimetype=None, allow_future=False, page=None):
393     """
394     Generic detail view from year/month/day/slug or year/month/day/id structure.
395 
396@@ -333,6 +525,7 @@
397         t = template_loader.get_template(template_name)
398     c = RequestContext(request, {
399         template_object_name: obj,
400+        page: page
401     }, context_processors)
402     for key, value in extra_context.items():
403         if callable(value):
404Index: django/views/generic/list_detail.py
405===================================================================
406--- django/views/generic/list_detail.py (revision 5992)
407+++ django/views/generic/list_detail.py (working copy)
408@@ -1,7 +1,7 @@
409 from django.template import loader, RequestContext
410 from django.http import Http404, HttpResponse
411 from django.core.xheaders import populate_xheaders
412-from django.core.paginator import ObjectPaginator, InvalidPage
413+from django.core.paginator import compute_pagination, ObjectPaginator, InvalidPage
414 from django.core.exceptions import ObjectDoesNotExist
415 
416 def object_list(request, queryset, paginate_by=None, page=None,
417@@ -42,44 +42,30 @@
418     """
419     if extra_context is None: extra_context = {}
420     queryset = queryset._clone()
421+
422+    if not allow_empty and queryset.count() == 0:
423+        raise Http404
424+
425+    # Pagination.
426+    pagination_context = {'is_paginated': False}
427     if paginate_by:
428         paginator = ObjectPaginator(queryset, paginate_by)
429-        if not page:
430-            page = request.GET.get('page', 1)
431+        page = page or request.GET.get('page', 1)
432         try:
433-            page = int(page)
434-            object_list = paginator.get_page(page - 1)
435-        except (InvalidPage, ValueError):
436-            if page == 1 and allow_empty:
437-                object_list = []
438-            else:
439-                raise Http404
440-        c = RequestContext(request, {
441-            '%s_list' % template_object_name: object_list,
442-            'is_paginated': paginator.pages > 1,
443-            'results_per_page': paginate_by,
444-            'has_next': paginator.has_next_page(page - 1),
445-            'has_previous': paginator.has_previous_page(page - 1),
446-            'page': page,
447-            'next': page + 1,
448-            'previous': page - 1,
449-            'last_on_page': paginator.last_on_page(page - 1),
450-            'first_on_page': paginator.first_on_page(page - 1),
451-            'pages': paginator.pages,
452-            'hits' : paginator.hits,
453-        }, context_processors)
454-    else:
455-        c = RequestContext(request, {
456-            '%s_list' % template_object_name: queryset,
457-            'is_paginated': False
458-        }, context_processors)
459-        if not allow_empty and len(queryset) == 0:
460+            queryset, pagination_context = compute_pagination(paginator, page, paginate_by, allow_empty)
461+        except InvalidPage:
462             raise Http404
463+    c = RequestContext(request, {
464+        '%s_list' % template_object_name: queryset,
465+    }, context_processors)
466     for key, value in extra_context.items():
467         if callable(value):
468             c[key] = value()
469         else:
470             c[key] = value
471+
472+    c.update(pagination_context)
473+
474     if not template_name:
475         model = queryset.model
476         template_name = "%s/%s_list.html" % (model._meta.app_label, model._meta.object_name.lower())