diff --git a/django/views/generic/list.py b/django/views/generic/list.py index ef90d75618..d90842fedc 100644 --- a/django/views/generic/list.py +++ b/django/views/generic/list.py @@ -10,6 +10,7 @@ class MultipleObjectMixin(object): model = None paginate_by = None context_object_name = None + paginator_class = Paginator def get_queryset(self): """ @@ -32,7 +33,7 @@ class MultipleObjectMixin(object): Paginate the queryset, if needed. """ if queryset.count() > page_size: - paginator = Paginator(queryset, page_size, allow_empty_first_page=self.get_allow_empty()) + paginator = self.get_paginator(queryset, page_size, allow_empty_first_page=self.get_allow_empty()) page = self.kwargs.get('page', None) or self.request.GET.get('page', 1) try: page_number = int(page) @@ -55,6 +56,12 @@ class MultipleObjectMixin(object): """ return self.paginate_by + def get_paginator(self, queryset, per_page, orphans=0, allow_empty_first_page=True): + """ + 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) + def get_allow_empty(self): """ Returns ``True`` if the view should display empty lists, and ``False`` diff --git a/docs/ref/class-based-views.txt b/docs/ref/class-based-views.txt index 28ce024483..1b9a9f99ea 100644 --- a/docs/ref/class-based-views.txt +++ b/docs/ref/class-based-views.txt @@ -305,6 +305,14 @@ MultipleObjectMixin expect either a ``page`` query string parameter (via ``GET``) or a ``page`` variable specified in the URLconf. + .. attribute:: paginator_class + + The paginator class to be used for pagination. By default, + :class:`django.core.paginator.Paginator` is used. If the custom paginator + class doesn't have the same constructor interface as + :class:`django.core.paginator.Paginator`, you will also need to + provide an implementation for :meth:`MultipleObjectMixin.get_paginator`. + .. attribute:: context_object_name Designates the name of the variable to use in the context. @@ -329,6 +337,11 @@ MultipleObjectMixin pagination. By default this simply returns the value of :attr:`MultipleObjectMixin.paginate_by`. + .. method:: get_paginator(queryset, queryset, per_page, orphans=0, allow_empty_first_page=True) + + Returns an instance of the paginator to use for this view. By default, + instantiates an instance of :attr:`paginator_class`. + .. method:: get_allow_empty() Return a boolean specifying whether to display the page if no objects diff --git a/tests/regressiontests/generic_views/list.py b/tests/regressiontests/generic_views/list.py index 8f6af741f7..bde741ef26 100644 --- a/tests/regressiontests/generic_views/list.py +++ b/tests/regressiontests/generic_views/list.py @@ -2,7 +2,7 @@ from django.core.exceptions import ImproperlyConfigured from django.test import TestCase from regressiontests.generic_views.models import Author - +from regressiontests.generic_views.views import CustomPaginator class ListViewTests(TestCase): fixtures = ['generic-views-test-data.json'] @@ -86,6 +86,21 @@ class ListViewTests(TestCase): res = self.client.get('/list/authors/paginated/?page=frog') self.assertEqual(res.status_code, 404) + def test_paginated_custom_paginator_class(self): + self._make_authors(7) + res = self.client.get('/list/authors/paginated/custom_class/') + self.assertEqual(res.status_code, 200) + self.assertIsInstance(res.context['paginator'], CustomPaginator) + # Custom pagination allows for 2 orphans on a page size of 5 + self.assertEqual(len(res.context['object_list']), 7) + + def test_paginated_custom_paginator_constructor(self): + self._make_authors(7) + res = self.client.get('/list/authors/paginated/custom_constructor/') + self.assertEqual(res.status_code, 200) + # Custom pagination allows for 2 orphans on a page size of 5 + self.assertEqual(len(res.context['object_list']), 7) + def test_allow_empty_false(self): res = self.client.get('/list/authors/notempty/') self.assertEqual(res.status_code, 200) diff --git a/tests/regressiontests/generic_views/urls.py b/tests/regressiontests/generic_views/urls.py index 0643b400ff..db09d63692 100644 --- a/tests/regressiontests/generic_views/urls.py +++ b/tests/regressiontests/generic_views/urls.py @@ -117,6 +117,10 @@ urlpatterns = patterns('', views.AuthorList.as_view(context_object_name='object_list')), (r'^list/authors/invalid/$', views.AuthorList.as_view(queryset=None)), + (r'^list/authors/paginated/custom_class/$', + views.AuthorList.as_view(paginate_by=5, paginator_class=views.CustomPaginator)), + (r'^list/authors/paginated/custom_constructor/$', + views.AuthorListCustomPaginator.as_view()), # YearArchiveView # Mixing keyword and possitional captures below is intentional; the views diff --git a/tests/regressiontests/generic_views/views.py b/tests/regressiontests/generic_views/views.py index 62cced6aab..e3a3c40694 100644 --- a/tests/regressiontests/generic_views/views.py +++ b/tests/regressiontests/generic_views/views.py @@ -1,4 +1,5 @@ from django.contrib.auth.decorators import login_required +from django.core.paginator import Paginator from django.core.urlresolvers import reverse from django.utils.decorators import method_decorator from django.views import generic @@ -50,6 +51,23 @@ class AuthorList(generic.ListView): queryset = Author.objects.all() +class CustomPaginator(Paginator): + def __init__(self, queryset, page_size, orphans=0, allow_empty_first_page=True): + super(CustomPaginator, self).__init__( + queryset, + page_size, + orphans=2, + allow_empty_first_page=allow_empty_first_page) + +class AuthorListCustomPaginator(AuthorList): + paginate_by = 5; + + def get_paginator(self, queryset, page_size, orphans=0, allow_empty_first_page=True): + return super(AuthorListCustomPaginator, self).get_paginator( + queryset, + page_size, + orphans=2, + allow_empty_first_page=allow_empty_first_page) class ArtistCreate(generic.CreateView): model = Artist