Browse Source

Add orphans support to MultipleObjectMixin

Fixes #7005
Chris Beaven 12 years ago
parent
commit
48e8b5e944

+ 16 - 3
django/views/generic/list.py

@@ -15,6 +15,7 @@ class MultipleObjectMixin(ContextMixin):
     queryset = None
     model = None
     paginate_by = None
+    paginate_orphans = 0
     context_object_name = None
     paginator_class = Paginator
     page_kwarg = 'page'
@@ -39,7 +40,9 @@ class MultipleObjectMixin(ContextMixin):
         """
         Paginate the queryset, if needed.
         """
-        paginator = self.get_paginator(queryset, page_size, allow_empty_first_page=self.get_allow_empty())
+        paginator = self.get_paginator(
+            queryset, page_size, orphans=self.get_paginate_orphans(),
+            allow_empty_first_page=self.get_allow_empty())
         page_kwarg = self.page_kwarg
         page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
         try:
@@ -64,11 +67,21 @@ class MultipleObjectMixin(ContextMixin):
         """
         return self.paginate_by
 
-    def get_paginator(self, queryset, per_page, orphans=0, allow_empty_first_page=True):
+    def get_paginator(self, queryset, per_page, orphans=0,
+                      allow_empty_first_page=True, **kwargs):
         """
         Return an instance of the paginator for this view.
         """
-        return self.paginator_class(queryset, per_page, orphans=orphans, allow_empty_first_page=allow_empty_first_page)
+        return self.paginator_class(
+            queryset, per_page, orphans=orphans,
+            allow_empty_first_page=allow_empty_first_page, **kwargs)
+
+    def get_paginate_orphans(self):
+        """
+        Returns the maximum number of orphans extend the last page by when
+        paginating.
+        """
+        return self.paginate_orphans
 
     def get_allow_empty(self):
         """

+ 7 - 0
docs/ref/class-based-views/flattened-index.txt

@@ -116,6 +116,7 @@ ListView
 * :attr:`~django.views.generic.base.View.http_method_names`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.model`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_orphans` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_orphans`]
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
@@ -290,6 +291,7 @@ ArchiveIndexView
 * :attr:`~django.views.generic.base.View.http_method_names`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.model`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_orphans` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_orphans`]
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
@@ -325,6 +327,7 @@ YearArchiveView
 * :attr:`~django.views.generic.dates.BaseYearArchiveView.make_object_list` [:meth:`~django.views.generic.dates.BaseYearArchiveView.get_make_object_list`]
 * :attr:`~django.views.generic.list.MultipleObjectMixin.model`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_orphans` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_orphans`]
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
@@ -363,6 +366,7 @@ MonthArchiveView
 * :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`]
 * :attr:`~django.views.generic.dates.MonthMixin.month_format` [:meth:`~django.views.generic.dates.MonthMixin.get_month_format`]
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_orphans` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_orphans`]
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
@@ -401,6 +405,7 @@ WeekArchiveView
 * :attr:`~django.views.generic.base.View.http_method_names`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.model`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_orphans` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_orphans`]
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
@@ -443,6 +448,7 @@ DayArchiveView
 * :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`]
 * :attr:`~django.views.generic.dates.MonthMixin.month_format` [:meth:`~django.views.generic.dates.MonthMixin.get_month_format`]
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_orphans` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_orphans`]
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
@@ -487,6 +493,7 @@ TodayArchiveView
 * :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`]
 * :attr:`~django.views.generic.dates.MonthMixin.month_format` [:meth:`~django.views.generic.dates.MonthMixin.get_month_format`]
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_orphans` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_orphans`]
 * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
 * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
 * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`

+ 18 - 0
docs/ref/class-based-views/mixins-multiple-object.txt

@@ -72,6 +72,16 @@ MultipleObjectMixin
         expect either a ``page`` query string parameter (via ``request.GET``)
         or a ``page`` variable specified in the URLconf.
 
+    .. attribute:: paginate_orphans
+
+        .. versionadded:: 1.6
+
+        An integer specifying the number of "overflow" objects the last page
+        can contain. This extends the :attr:`MultipleObjectMixin.paginate_by`
+        limit on the last page by up to
+        :attr:`MultipleObjectMixin.paginate_orphans`, in order to keep the last
+        page from having a very small number of objects.
+
     .. attribute:: page_kwarg
 
         .. versionadded:: 1.5
@@ -119,6 +129,14 @@ MultipleObjectMixin
         Returns an instance of the paginator to use for this view. By default,
         instantiates an instance of :attr:`paginator_class`.
 
+    .. method:: get_paginate_by()
+
+        .. versionadded:: 1.6
+
+        An integer specifying the number of "overflow" objects the last page
+        can contain. By default this simply returns the value of
+        :attr:`MultipleObjectMixin.paginate_orphans`.
+
     .. method:: get_allow_empty()
 
         Return a boolean specifying whether to display the page if no objects

+ 18 - 0
tests/regressiontests/generic_views/list.py

@@ -118,8 +118,26 @@ class ListViewTests(TestCase):
         # Custom pagination allows for 2 orphans on a page size of 5
         self.assertEqual(len(res.context['object_list']), 7)
 
+    def test_paginated_orphaned_queryset(self):
+        self._make_authors(92)
+        res = self.client.get('/list/authors/paginated-orphaned/')
+        self.assertEqual(res.status_code, 200)
+        self.assertEqual(res.context['page_obj'].number, 1)
+        res = self.client.get(
+            '/list/authors/paginated-orphaned/', {'page': 'last'})
+        self.assertEqual(res.status_code, 200)
+        self.assertEqual(res.context['page_obj'].number, 3)
+        res = self.client.get(
+            '/list/authors/paginated-orphaned/', {'page': '3'})
+        self.assertEqual(res.status_code, 200)
+        self.assertEqual(res.context['page_obj'].number, 3)
+        res = self.client.get(
+            '/list/authors/paginated-orphaned/', {'page': '4'})
+        self.assertEqual(res.status_code, 404)
+
     def test_paginated_non_queryset(self):
         res = self.client.get('/list/dict/paginated/')
+
         self.assertEqual(res.status_code, 200)
         self.assertEqual(len(res.context['object_list']), 1)
 

+ 2 - 0
tests/regressiontests/generic_views/urls.py

@@ -133,6 +133,8 @@ urlpatterns = patterns('',
         views.AuthorList.as_view(paginate_by=30)),
     (r'^list/authors/paginated/(?P<page>\d+)/$',
         views.AuthorList.as_view(paginate_by=30)),
+    (r'^list/authors/paginated-orphaned/$',
+        views.AuthorList.as_view(paginate_by=30, paginate_orphans=2)),
     (r'^list/authors/notempty/$',
         views.AuthorList.as_view(allow_empty=False)),
     (r'^list/authors/notempty/paginated/$',