From ee48da24050a4f447f6ba153430c59b93a1522b3 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 5 Dec 2010 04:32:36 +0000 Subject: [PATCH] Fixed #14773 -- Modified MultipleObjectMixin to allow for custom paginators. Thanks to piquadrat for the report and initial patch. git-svn-id: http://code.djangoproject.com/svn/django/trunk@14828 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/views/generic/list.py | 9 ++++++++- docs/ref/class-based-views.txt | 13 +++++++++++++ tests/regressiontests/generic_views/list.py | 17 ++++++++++++++++- tests/regressiontests/generic_views/urls.py | 4 ++++ tests/regressiontests/generic_views/views.py | 18 ++++++++++++++++++ 5 files changed, 59 insertions(+), 2 deletions(-) diff --git a/django/views/generic/list.py b/django/views/generic/list.py index ef90d75618348..d90842fedc703 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 @@ def paginate_queryset(self, queryset, page_size): 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 @@ def get_paginate_by(self, queryset): """ 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 28ce0244831ba..1b9a9f99ea01c 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 8f6af741f7772..bde741ef26a40 100644 --- a/tests/regressiontests/generic_views/list.py +++ b/tests/regressiontests/generic_views/list.py @@ -2,7 +2,7 @@ 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 @@ def test_paginated_invalid_page(self): 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 0643b400ff686..db09d63692075 100644 --- a/tests/regressiontests/generic_views/urls.py +++ b/tests/regressiontests/generic_views/urls.py @@ -117,6 +117,10 @@ 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 62cced6aabb48..e3a3c4069414a 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