Add orphans support to MultipleObjectMixin

Fixes #7005
This commit is contained in:
Chris Beaven 2012-11-21 16:45:38 +13:00
parent fbfa654a15
commit 48e8b5e944
5 changed files with 61 additions and 3 deletions

View File

@ -15,6 +15,7 @@ class MultipleObjectMixin(ContextMixin):
queryset = None queryset = None
model = None model = None
paginate_by = None paginate_by = None
paginate_orphans = 0
context_object_name = None context_object_name = None
paginator_class = Paginator paginator_class = Paginator
page_kwarg = 'page' page_kwarg = 'page'
@ -39,7 +40,9 @@ class MultipleObjectMixin(ContextMixin):
""" """
Paginate the queryset, if needed. 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_kwarg = self.page_kwarg
page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1 page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
try: try:
@ -64,11 +67,21 @@ class MultipleObjectMixin(ContextMixin):
""" """
return self.paginate_by 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 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): def get_allow_empty(self):
""" """

View File

@ -116,6 +116,7 @@ ListView
* :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.base.View.http_method_names`
* :attr:`~django.views.generic.list.MultipleObjectMixin.model` * :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_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.paginator_class`
* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` * :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.base.View.http_method_names`
* :attr:`~django.views.generic.list.MultipleObjectMixin.model` * :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_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.paginator_class`
* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` * :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.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.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_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.paginator_class`
* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` * :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` [: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.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_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.paginator_class`
* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` * :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.base.View.http_method_names`
* :attr:`~django.views.generic.list.MultipleObjectMixin.model` * :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_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.paginator_class`
* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` * :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` [: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.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_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.paginator_class`
* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` * :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` [: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.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_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.paginator_class`
* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`

View File

@ -72,6 +72,16 @@ MultipleObjectMixin
expect either a ``page`` query string parameter (via ``request.GET``) expect either a ``page`` query string parameter (via ``request.GET``)
or a ``page`` variable specified in the URLconf. 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 .. attribute:: page_kwarg
.. versionadded:: 1.5 .. versionadded:: 1.5
@ -119,6 +129,14 @@ MultipleObjectMixin
Returns an instance of the paginator to use for this view. By default, Returns an instance of the paginator to use for this view. By default,
instantiates an instance of :attr:`paginator_class`. 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() .. method:: get_allow_empty()
Return a boolean specifying whether to display the page if no objects Return a boolean specifying whether to display the page if no objects

View File

@ -118,8 +118,26 @@ class ListViewTests(TestCase):
# Custom pagination allows for 2 orphans on a page size of 5 # Custom pagination allows for 2 orphans on a page size of 5
self.assertEqual(len(res.context['object_list']), 7) 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): def test_paginated_non_queryset(self):
res = self.client.get('/list/dict/paginated/') res = self.client.get('/list/dict/paginated/')
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertEqual(len(res.context['object_list']), 1) self.assertEqual(len(res.context['object_list']), 1)

View File

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