From: Jan Szejko Date: Wed, 15 Feb 2017 15:58:06 +0000 (+0100) Subject: rebranding X-Git-Tag: 2.2.0 X-Git-Url: https://git.mdrn.pl/django-pagination.git/commitdiff_plain/abf535d21fd19b257e5d6fecfd60e470cc12de7f?ds=inline;hp=86caf150984733eb5a0bb07af26cd8fc0a8d8496 rebranding --- diff --git a/.travis.yml b/.travis.yml index 4d555f6..6f9a195 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ install: - pip install 'coverage<4' # coverage>=4 has issues with python3 - pip install -q Django==$DJANGO_VERSION coveralls - python setup.py egg_info -script: coverage run --source='linaro_django_pagination' --omit *runner*,*test_project* setup.py test +script: coverage run --source='fnp_django_pagination' --omit *runner*,*test_project* setup.py test after_success: - coveralls matrix: diff --git a/MANIFEST.in b/MANIFEST.in index 50282fe..d8d3609 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,6 @@ include CONTRIBUTORS.txt include COPYING include doc/conf.py recursive-include doc *.rst -recursive-include linaro_django_pagination/locale *.po -recursive-include linaro_django_pagination/templates/pagination *.html -recursive-include linaro_django_pagination/test_project/example/templates *.html +recursive-include fnp_django_pagination/locale *.po +recursive-include fnp_django_pagination/templates/pagination *.html +recursive-include fnp_django_pagination/test_project/example/templates *.html diff --git a/doc/conf.py b/doc/conf.py index 39a80cb..54900bc 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -47,10 +47,10 @@ copyright = u'2010-2011, Linaro Limited' # # The short X.Y version. import versiontools -import linaro_django_pagination -version = "%d.%d" % linaro_django_pagination.__version__[0:2] +import fnp_django_pagination +version = "%d.%d" % fnp_django_pagination.__version__[0:2] # The full version, including alpha/beta/rc tags. -release = versiontools.format_version(linaro_django_pagination.__version__) +release = versiontools.format_version(fnp_django_pagination.__version__) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/fnp_django_pagination/__init__.py b/fnp_django_pagination/__init__.py new file mode 100644 index 0000000..f3ce53d --- /dev/null +++ b/fnp_django_pagination/__init__.py @@ -0,0 +1,37 @@ +# Copyright (c) 2008, Eric Florenzano +# Copyright (c) 2010, 2011 Linaro Limited +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the author nor the names of other +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" +fnp-django-pagination is a set of utilities for creating robust pagination +tools throughout a django application. +""" + + +__version__ = (2, 2, 0, "final", 0) diff --git a/fnp_django_pagination/locale/de/LC_MESSAGES/django.po b/fnp_django_pagination/locale/de/LC_MESSAGES/django.po new file mode 100644 index 0000000..b610fe7 --- /dev/null +++ b/fnp_django_pagination/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,25 @@ +# Deutch translation of linaro-django-pagination +# Copyright (c) 2009, linaro-django-pagination team +# This file is distributed under the same license as the linaro-django-pagination package. +# +msgid "" +msgstr "" +"Project-Id-Version: linaro-django-pagination master\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-03-16 16:26+0100\n" +"PO-Revision-Date: 2012-01-26 13:27+0100\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: templates/pagination/pagination.html:5 +#: templates/pagination/pagination.html:7 +msgid "previous" +msgstr "Zurück" + +#: templates/pagination/pagination.html:21 +#: templates/pagination/pagination.html:23 +msgid "next" +msgstr "Weiter" diff --git a/fnp_django_pagination/locale/es/LC_MESSAGES/django.po b/fnp_django_pagination/locale/es/LC_MESSAGES/django.po new file mode 100644 index 0000000..5ce9ad4 --- /dev/null +++ b/fnp_django_pagination/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,27 @@ +# Spanish translation of django-pagination +# Copyright (c) 2010, Miguel Araujo +# This file is distributed under the same license as the django-pagination package. +# Miguel Araujo , 2010 +# +msgid "" +msgstr "" +"Project-Id-Version: linaro-django-pagination master\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-09-16 16:32+0200\n" +"PO-Revision-Date: 2012-01-26 13:24+0100\n" +"Last-Translator: Miguel Araujo \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: templates/pagination/pagination.html:5 +#: templates/pagination/pagination.html:7 +msgid "previous" +msgstr "anterior" + +#: templates/pagination/pagination.html:21 +#: templates/pagination/pagination.html:23 +msgid "next" +msgstr "siguiente" diff --git a/fnp_django_pagination/locale/fi/LC_MESSAGES/django.po b/fnp_django_pagination/locale/fi/LC_MESSAGES/django.po new file mode 100644 index 0000000..a0bdb27 --- /dev/null +++ b/fnp_django_pagination/locale/fi/LC_MESSAGES/django.po @@ -0,0 +1,29 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-07-11 12:51+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: templates/pagination/pagination.html:6 +#: templates/pagination/pagination.html:9 +msgid "previous" +msgstr "edellinen" + +#: templates/pagination/pagination.html:30 +#: templates/pagination/pagination.html:33 +msgid "next" +msgstr "seuraava" diff --git a/fnp_django_pagination/locale/fr/LC_MESSAGES/django.po b/fnp_django_pagination/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 0000000..014a001 --- /dev/null +++ b/fnp_django_pagination/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,26 @@ +# French translation of linaro-django-pagination +# Copyright (c) 2008, Julien Demoor +# This file is distributed under the same license as the linaro-django-pagination package. +# Julien Demoor , 2008 +# +msgid "" +msgstr "" +"Project-Id-Version: linaro-django-pagination master\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2008-10-24 00:41-0700\n" +"PO-Revision-Date: 2012-01-26 13:25+0100\n" +"Last-Translator: Julien Demoor \n" +"Language-Team: French\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: templates/pagination/pagination.html:5 +#: templates/pagination/pagination.html:7 +msgid "previous" +msgstr "précédente" + +#: templates/pagination/pagination.html:21 +#: templates/pagination/pagination.html:23 +msgid "next" +msgstr "suivante" diff --git a/fnp_django_pagination/locale/it/LC_MESSAGES/django.po b/fnp_django_pagination/locale/it/LC_MESSAGES/django.po new file mode 100644 index 0000000..550ac80 --- /dev/null +++ b/fnp_django_pagination/locale/it/LC_MESSAGES/django.po @@ -0,0 +1,24 @@ +# django-pagination Italian translation +# Copyright (c) 2010 Fabio Corneti +# This file is distributed under the same license as the django-pagination package. +# Fabio Corneti , 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: linaro-django-pagination-master\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-04-22 12:04+0200\n" +"PO-Revision-Date: 2012-01-26 11:26+0100\n" +"Last-Translator: Fabio Corneti \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: templates/pagination/default.html:6 templates/pagination/default.html:9 +msgid "previous" +msgstr "precedenti" + +#: templates/pagination/default.html:30 templates/pagination/default.html:33 +msgid "next" +msgstr "successive" diff --git a/fnp_django_pagination/locale/nn/LC_MESSAGES/django.po b/fnp_django_pagination/locale/nn/LC_MESSAGES/django.po new file mode 100644 index 0000000..4e40a31 --- /dev/null +++ b/fnp_django_pagination/locale/nn/LC_MESSAGES/django.po @@ -0,0 +1,25 @@ +# Norwegian translation of linaro-django-pagination. +# Copyright (c) 2008, linaro-django-pagination team +# This file is distributed under the same license as the linaro-django-pagination package. +# +msgid "" +msgstr "" +"Project-Id-Version: linaro-django-pagination master\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-03-28 10:48+0200\n" +"PO-Revision-Date: 2012-01-26 11:32+0100\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: templates/pagination/pagination.html:5 +#: templates/pagination/pagination.html:7 +msgid "previous" +msgstr "Førre" + +#: templates/pagination/pagination.html:21 +#: templates/pagination/pagination.html:23 +msgid "next" +msgstr "Neste" diff --git a/fnp_django_pagination/locale/no/LC_MESSAGES/django.po b/fnp_django_pagination/locale/no/LC_MESSAGES/django.po new file mode 100644 index 0000000..d476f3c --- /dev/null +++ b/fnp_django_pagination/locale/no/LC_MESSAGES/django.po @@ -0,0 +1,27 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (c) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-03-28 10:48+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: templates/pagination/pagination.html:5 +#: templates/pagination/pagination.html:7 +msgid "previous" +msgstr "Forrige" + +#: templates/pagination/pagination.html:21 +#: templates/pagination/pagination.html:23 +msgid "next" +msgstr "Neste" diff --git a/fnp_django_pagination/locale/pl/LC_MESSAGES/django.po b/fnp_django_pagination/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 0000000..5bb9800 --- /dev/null +++ b/fnp_django_pagination/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,26 @@ +# Polish translation of linaro-django-pagination. +# Copyright (c) 2008, linaro-django-pagination team +# This file is distributed under the same license as the linaro-django-pagination package. +# Jarek Zgoda , 2008. +# +msgid "" +msgstr "" +"Project-Id-Version: 1.0\n" +"Report-Msgid-Bugs-To: linaro-django-pagination master\n" +"POT-Creation-Date: 2008-10-24 00:41-0700\n" +"PO-Revision-Date: 2012-01-26 11:29+0100\n" +"Last-Translator: Jarek Zgoda \n" +"Language-Team: PL \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: templates/pagination/pagination.html:5 +#: templates/pagination/pagination.html:7 +msgid "previous" +msgstr "poprzednia" + +#: templates/pagination/pagination.html:21 +#: templates/pagination/pagination.html:23 +msgid "next" +msgstr "następna" diff --git a/fnp_django_pagination/locale/pt/LC_MESSAGES/django.po b/fnp_django_pagination/locale/pt/LC_MESSAGES/django.po new file mode 100644 index 0000000..17f545e --- /dev/null +++ b/fnp_django_pagination/locale/pt/LC_MESSAGES/django.po @@ -0,0 +1,26 @@ +# linaro-django-pagination Portuguese translation. +# Copyright (c) 2008, Alcides Fonseca +# http://alcidesfonseca.com +# This file is distributed under the WTFPL +# +msgid "" +msgstr "" +"Project-Id-Version: linaro-django-pagination master\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2008-10-24 00:41-0700\n" +"PO-Revision-Date: 2012-01-26 11:26+0100\n" +"Last-Translator: Alcides Fonseca \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: templates/pagination/pagination.html:5 +#: templates/pagination/pagination.html:7 +msgid "previous" +msgstr "anterior" + +#: templates/pagination/pagination.html:21 +#: templates/pagination/pagination.html:23 +msgid "next" +msgstr "próximo" diff --git a/fnp_django_pagination/locale/pt_BR/LC_MESSAGES/django.po b/fnp_django_pagination/locale/pt_BR/LC_MESSAGES/django.po new file mode 100644 index 0000000..9694574 --- /dev/null +++ b/fnp_django_pagination/locale/pt_BR/LC_MESSAGES/django.po @@ -0,0 +1,28 @@ +# Portuguese (Brazil) translation of django-pagination +# Copyright (c) 2011, Tiago Samaha Cordeiro +# This file is distributed under the same license as the django-pagination package. +# Tiago Samaha Cordeiro , 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: linaro-django-pagination master\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2011-01-16 22:06-0200\n" +"PO-Revision-Date: 2012-01-26 13:28+0100\n" +"Last-Translator: Tiago Samaha Cordeiro \n" +"Language-Team: LANGUAGE \n" +"Language: Portuguese Brazil\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: templates/pagination/pagination.html:5 +#: templates/pagination/pagination.html:7 +msgid "previous" +msgstr "anterior" + +#: templates/pagination/pagination.html:21 +#: templates/pagination/pagination.html:23 +msgid "next" +msgstr "próximo" diff --git a/fnp_django_pagination/locale/ru/LC_MESSAGES/django.po b/fnp_django_pagination/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 0000000..5ad44cf --- /dev/null +++ b/fnp_django_pagination/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,28 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (c) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-07-11 16:14+0600\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +#: templates/pagination/pagination.html:5 +#: templates/pagination/pagination.html:7 +msgid "previous" +msgstr "Предыдущая" + +#: templates/pagination/pagination.html:21 +#: templates/pagination/pagination.html:23 +msgid "next" +msgstr "Следующая" diff --git a/fnp_django_pagination/locale/tr/LC_MESSAGES/django.po b/fnp_django_pagination/locale/tr/LC_MESSAGES/django.po new file mode 100644 index 0000000..e71b513 --- /dev/null +++ b/fnp_django_pagination/locale/tr/LC_MESSAGES/django.po @@ -0,0 +1,25 @@ +# Turkish translation of linaro-django-pagination. +# Copyright (c) 2008, linaro-django-pagination team +# This file is distributed under the same license as the linaro-django-pagination package. +# +msgid "" +msgstr "" +"Project-Id-Version: linaro-django-pagination master\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-03-16 16:26+0100\n" +"PO-Revision-Date: 2012-01-26 11:29+0100\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: templates/pagination/pagination.html:5 +#: templates/pagination/pagination.html:7 +msgid "previous" +msgstr "Önceki" + +#: templates/pagination/pagination.html:21 +#: templates/pagination/pagination.html:23 +msgid "next" +msgstr "Sonraki" diff --git a/fnp_django_pagination/middleware.py b/fnp_django_pagination/middleware.py new file mode 100644 index 0000000..580be43 --- /dev/null +++ b/fnp_django_pagination/middleware.py @@ -0,0 +1,54 @@ +# Copyright (c) 2008, Eric Florenzano +# Copyright (c) 2010, 2011 Linaro Limited +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the author nor the names of other +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +def get_page(self, suffix): + """ + A function which will be monkeypatched onto the request to get the current + integer representing the current page. + """ + try: + # REQUEST is deprecated as of Django 1.7. + key = 'page%s' % suffix + value = self.POST.get(key) + if value is None: + value = self.GET.get(key) + return int(value) + except (KeyError, ValueError, TypeError): + return 1 + + +class PaginationMiddleware(object): + """ + Inserts a variable representing the current page onto the request object if + it exists in either **GET** or **POST** portions of the request. + """ + def process_request(self, request): + request.__class__.page = get_page diff --git a/fnp_django_pagination/models.py b/fnp_django_pagination/models.py new file mode 100644 index 0000000..e69de29 diff --git a/fnp_django_pagination/paginator.py b/fnp_django_pagination/paginator.py new file mode 100644 index 0000000..cd3a0f7 --- /dev/null +++ b/fnp_django_pagination/paginator.py @@ -0,0 +1,204 @@ +# Copyright (c) 2008, Eric Florenzano +# Copyright (c) 2010, 2011 Linaro Limited +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the author nor the names of other +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +from django.core.paginator import Paginator, Page, PageNotAnInteger, EmptyPage + + +class InfinitePaginator(Paginator): + """ + Paginator designed for cases when it's not important to know how many total + pages. This is useful for any object_list that has no count() method or + can be used to improve performance for MySQL by removing counts. + + The orphans parameter has been removed for simplicity and there's a link + template string for creating the links to the next and previous pages. + """ + + def __init__(self, object_list, per_page, allow_empty_first_page=True, + link_template='/page/%d/'): + orphans = 0 # no orphans + super(InfinitePaginator, self).__init__(object_list, per_page, orphans, + allow_empty_first_page) + # no count or num pages + del self._num_pages, self._count + # bonus links + self.link_template = link_template + + def validate_number(self, number): + """ + Validates the given 1-based page number. + """ + try: + number = int(number) + except ValueError: + raise PageNotAnInteger('That page number is not an integer') + if number < 1: + raise EmptyPage('That page number is less than 1') + return number + + def page(self, number): + """ + Returns a Page object for the given 1-based page number. + """ + number = self.validate_number(number) + bottom = (number - 1) * self.per_page + top = bottom + self.per_page + page_items = self.object_list[bottom:top] + # check moved from validate_number + if not page_items: + if number == 1 and self.allow_empty_first_page: + pass + else: + raise EmptyPage('That page contains no results') + return InfinitePage(page_items, number, self) + + def _get_count(self): + """ + Returns the total number of objects, across all pages. + """ + raise NotImplementedError + count = property(_get_count) + + def _get_num_pages(self): + """ + Returns the total number of pages. + """ + raise NotImplementedError + num_pages = property(_get_num_pages) + + def _get_page_range(self): + """ + Returns a 1-based range of pages for iterating through within + a template for loop. + """ + raise NotImplementedError + page_range = property(_get_page_range) + + +class InfinitePage(Page): + + def __repr__(self): + return '' % self.number + + def has_next(self): + """ + Checks for one more item than last on this page. + """ + try: + self.paginator.object_list[self.number * self.paginator.per_page] + except IndexError: + return False + return True + + def end_index(self): + """ + Returns the 1-based index of the last object on this page, + relative to total objects found (hits). + """ + return ((self.number - 1) * self.paginator.per_page + + len(self.object_list)) + + #Bonus methods for creating links + + def next_link(self): + if self.has_next(): + return self.paginator.link_template % (self.number + 1) + return None + + def previous_link(self): + if self.has_previous(): + return self.paginator.link_template % (self.number - 1) + return None + + +class FinitePaginator(InfinitePaginator): + """ + Paginator for cases when the list of items is already finite. + + A good example is a list generated from an API call. This is a subclass + of InfinitePaginator because we have no idea how many items exist in the + full collection. + + To accurately determine if the next page exists, a FinitePaginator MUST be + created with an object_list_plus that may contain more items than the + per_page count. Typically, you'll have an object_list_plus with one extra + item (if there's a next page). You'll also need to supply the offset from + the full collection in order to get the page start_index. + + This is a very silly class but useful if you love the Django pagination + conventions. + """ + + def __init__(self, object_list_plus, per_page, offset=None, + allow_empty_first_page=True, link_template='/page/%d/'): + super(FinitePaginator, self).__init__(object_list_plus, per_page, + allow_empty_first_page, link_template) + self.offset = offset + + def validate_number(self, number): + super(FinitePaginator, self).validate_number(number) + # check for an empty list to see if the page exists + if not self.object_list: + if number == 1 and self.allow_empty_first_page: + pass + else: + raise EmptyPage('That page contains no results') + return number + + def page(self, number): + """ + Returns a Page object for the given 1-based page number. + """ + number = self.validate_number(number) + # remove the extra item(s) when creating the page + page_items = self.object_list[:self.per_page] + return FinitePage(page_items, number, self) + + +class FinitePage(InfinitePage): + + def has_next(self): + """ + Checks for one more item than last on this page. + """ + try: + self.paginator.object_list[self.paginator.per_page] + except IndexError: + return False + return True + + def start_index(self): + """ + Returns the 1-based index of the first object on this page, + relative to total objects in the paginator. + """ + ## TODO should this holler if you haven't defined the offset? + return self.paginator.offset diff --git a/fnp_django_pagination/settings.py b/fnp_django_pagination/settings.py new file mode 100644 index 0000000..24a04e0 --- /dev/null +++ b/fnp_django_pagination/settings.py @@ -0,0 +1,56 @@ +# Copyright (c) 2008, Eric Florenzano +# Copyright (c) 2010, 2011 Linaro Limited +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the author nor the names of other +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +from django.conf import settings + + +DEFAULT_PAGINATION = getattr( + settings, 'PAGINATION_DEFAULT_PAGINATION', 20) +DEFAULT_WINDOW = getattr( + settings, 'PAGINATION_DEFAULT_WINDOW', 4) +DEFAULT_MARGIN = getattr( + settings, 'PAGINATION_DEFAULT_MARGIN', DEFAULT_WINDOW) +DEFAULT_ORPHANS = getattr( + settings, 'PAGINATION_DEFAULT_ORPHANS', 0) +INVALID_PAGE_RAISES_404 = getattr( + settings, 'PAGINATION_INVALID_PAGE_RAISES_404', False) +DISPLAY_PAGE_LINKS = getattr( + settings, 'PAGINATION_DISPLAY_PAGE_LINKS', True) +PREVIOUS_LINK_DECORATOR = getattr( + settings, 'PAGINATION_PREVIOUS_LINK_DECORATOR', "‹‹ ") +NEXT_LINK_DECORATOR = getattr( + settings, 'PAGINATION_NEXT_LINK_DECORATOR', " ››") +DISPLAY_DISABLED_PREVIOUS_LINK = getattr( + settings, 'PAGINATION_DISPLAY_DISABLED_PREVIOUS_LINK', False) +DISPLAY_DISABLED_NEXT_LINK = getattr( + settings, 'PAGINATION_DISPLAY_DISABLED_NEXT_LINK', False) +DISABLE_LINK_FOR_FIRST_PAGE = getattr( + settings, 'PAGINATION_DISABLE_LINK_FOR_FIRST_PAGE', True) diff --git a/fnp_django_pagination/templates/pagination/pagination.html b/fnp_django_pagination/templates/pagination/pagination.html new file mode 100644 index 0000000..ea01b61 --- /dev/null +++ b/fnp_django_pagination/templates/pagination/pagination.html @@ -0,0 +1,46 @@ +{% if is_paginated %} +{% load i18n %} + +{% endif %} diff --git a/fnp_django_pagination/templatetags/__init__.py b/fnp_django_pagination/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fnp_django_pagination/templatetags/pagination_tags.py b/fnp_django_pagination/templatetags/pagination_tags.py new file mode 100644 index 0000000..7d41052 --- /dev/null +++ b/fnp_django_pagination/templatetags/pagination_tags.py @@ -0,0 +1,346 @@ +# Copyright (c) 2008, Eric Florenzano +# Copyright (c) 2010, 2011 Linaro Limited +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the author nor the names of other +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.core.paginator import Paginator, InvalidPage +from django.http import Http404 +from django.template import ( + Context, + Library, + Node, + TemplateSyntaxError, + Variable, + loader, +) + +try: + from django.template.base import TOKEN_BLOCK +except ImportError: # Django < 1.8 + from django.template import TOKEN_BLOCK + +from django.template.loader import select_template +from django.utils.text import unescape_string_literal + +# TODO, import this normally later on +from fnp_django_pagination.settings import * + + +def do_autopaginate(parser, token): + """ + Splits the arguments to the autopaginate tag and formats them correctly. + + Syntax is: + + autopaginate QUERYSET [PAGINATE_BY] [ORPHANS] [as NAME] + """ + # Check whether there are any other autopaginations are later in this template + expr = lambda obj: (obj.token_type == TOKEN_BLOCK and \ + len(obj.split_contents()) > 0 and obj.split_contents()[0] == "autopaginate") + multiple_paginations = len([tok for tok in parser.tokens if expr(tok)]) > 0 + + i = iter(token.split_contents()) + paginate_by = None + queryset_var = None + context_var = None + orphans = None + word = None + try: + word = next(i) + assert word == "autopaginate" + queryset_var = next(i) + word = next(i) + if word != "as": + paginate_by = word + try: + paginate_by = int(paginate_by) + except ValueError: + pass + word = next(i) + if word != "as": + orphans = word + try: + orphans = int(orphans) + except ValueError: + pass + word = next(i) + assert word == "as" + context_var = next(i) + except StopIteration: + pass + if queryset_var is None: + raise TemplateSyntaxError( + "Invalid syntax. Proper usage of this tag is: " + "{% autopaginate QUERYSET [PAGINATE_BY] [ORPHANS]" + " [as CONTEXT_VAR_NAME] %}") + return AutoPaginateNode(queryset_var, multiple_paginations, paginate_by, orphans, context_var) + + +class AutoPaginateNode(Node): + """ + Emits the required objects to allow for Digg-style pagination. + + First, it looks in the current context for the variable specified, and using + that object, it emits a simple ``Paginator`` and the current page object + into the context names ``paginator`` and ``page_obj``, respectively. + + It will then replace the variable specified with only the objects for the + current page. + + .. note:: + + It is recommended to use *{% paginate %}* after using the autopaginate + tag. If you choose not to use *{% paginate %}*, make sure to display the + list of available pages, or else the application may seem to be buggy. + """ + def __init__(self, queryset_var, multiple_paginations, paginate_by=None, + orphans=None, context_var=None): + if paginate_by is None: + paginate_by = DEFAULT_PAGINATION + if orphans is None: + orphans = DEFAULT_ORPHANS + self.queryset_var = Variable(queryset_var) + if isinstance(paginate_by, int): + self.paginate_by = paginate_by + else: + self.paginate_by = Variable(paginate_by) + if isinstance(orphans, int): + self.orphans = orphans + else: + self.orphans = Variable(orphans) + self.context_var = context_var + self.multiple_paginations = multiple_paginations + + def render(self, context): + # Save multiple_paginations state in context + if self.multiple_paginations and 'multiple_paginations' not in context: + context['multiple_paginations'] = True + + if context.get('multiple_paginations') or getattr(context, "paginator", None): + page_suffix = '_%s' % self.queryset_var + else: + page_suffix = '' + + key = self.queryset_var.var + value = self.queryset_var.resolve(context) + if isinstance(self.paginate_by, int): + paginate_by = self.paginate_by + else: + paginate_by = self.paginate_by.resolve(context) + if isinstance(self.orphans, int): + orphans = self.orphans + else: + orphans = self.orphans.resolve(context) + paginator = Paginator(value, paginate_by, orphans) + try: + request = context['request'] + except KeyError: + raise ImproperlyConfigured( + "You need to enable 'django.core.context_processors.request'." + " See linaro-django-pagination/README file for TEMPLATE_CONTEXT_PROCESSORS details") + try: + page_obj = paginator.page(request.page(page_suffix)) + except InvalidPage: + if INVALID_PAGE_RAISES_404: + raise Http404('Invalid page requested. If DEBUG were set to ' + + 'False, an HTTP 404 page would have been shown instead.') + context[key] = [] + context['invalid_page'] = True + return '' + if self.context_var is not None: + context[self.context_var] = page_obj.object_list + else: + context[key] = page_obj.object_list + context['paginator'] = paginator + context['page_obj'] = page_obj + context['page_suffix'] = page_suffix + return '' + + +class PaginateNode(Node): + + def __init__(self, template=None): + self.template = template + + def render(self, context): + template_list = ['pagination/pagination.html'] + new_context = paginate(context) + if self.template: + template_list.insert(0, self.template) + return loader.render_to_string(template_list, new_context, + context_instance = context) + + + +def do_paginate(parser, token): + """ + Emits the pagination control for the most recent autopaginate list + + Syntax is: + + paginate [using "TEMPLATE"] + + Where TEMPLATE is a quoted template name. If missing the default template + is used (paginate/pagination.html). + """ + argv = token.split_contents() + argc = len(argv) + if argc == 1: + template = None + elif argc == 3 and argv[1] == 'using': + template = unescape_string_literal(argv[2]) + else: + raise TemplateSyntaxError( + "Invalid syntax. Proper usage of this tag is: " + "{% paginate [using \"TEMPLATE\"] %}") + return PaginateNode(template) + + +def paginate(context, window=DEFAULT_WINDOW, margin=DEFAULT_MARGIN): + """ + Renders the ``pagination/pagination.html`` template, resulting in a + Digg-like display of the available pages, given the current page. If there + are too many pages to be displayed before and after the current page, then + elipses will be used to indicate the undisplayed gap between page numbers. + + Requires one argument, ``context``, which should be a dictionary-like data + structure and must contain the following keys: + + ``paginator`` + A ``Paginator`` or ``QuerySetPaginator`` object. + + ``page_obj`` + This should be the result of calling the page method on the + aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given + the current page. + + This same ``context`` dictionary-like data structure may also include: + + ``getvars`` + A dictionary of all of the **GET** parameters in the current request. + This is useful to maintain certain types of state, even when requesting + a different page. + + Argument ``window`` is number to pages before/after current page. If window + exceeds pagination border (1 and end), window is moved to left or right. + + Argument ``margin``` is number of pages on start/end of pagination. + Example: + window=2, margin=1, current=6 1 ... 4 5 [6] 7 8 ... 11 + window=2, margin=0, current=1 [1] 2 3 4 5 ... + window=2, margin=0, current=5 ... 3 4 [5] 6 7 ... + window=2, margin=0, current=11 ... 7 8 9 10 [11] + """ + + if window < 0: + raise ValueError('Parameter "window" cannot be less than zero') + if margin < 0: + raise ValueError('Parameter "margin" cannot be less than zero') + try: + paginator = context['paginator'] + page_obj = context['page_obj'] + page_suffix = context.get('page_suffix', '') + page_range = list(paginator.page_range) + # Calculate the record range in the current page for display. + records = {'first': 1 + (page_obj.number - 1) * paginator.per_page} + records['last'] = records['first'] + paginator.per_page - 1 + if records['last'] + paginator.orphans >= paginator.count: + records['last'] = paginator.count + + # figure window + window_start = page_obj.number - window - 1 + window_end = page_obj.number + window + + # solve if window exceeded page range + if window_start < 0: + window_end = window_end - window_start + window_start = 0 + if window_end > paginator.num_pages: + window_start = max(0, window_start - (window_end - paginator.num_pages)) + window_end = paginator.num_pages + pages = page_range[window_start:window_end] + + # figure margin and add elipses + if margin > 0: + # figure margin + tmp_pages = set(pages) + tmp_pages = tmp_pages.union(page_range[:margin]) + tmp_pages = tmp_pages.union(page_range[-margin:]) + tmp_pages = list(tmp_pages) + tmp_pages.sort() + pages = [] + pages.append(tmp_pages[0]) + for i in range(1, len(tmp_pages)): + # figure gap size => add elipses or fill in gap + gap = tmp_pages[i] - tmp_pages[i - 1] + if gap >= 3: + pages.append(None) + elif gap == 2: + pages.append(tmp_pages[i] - 1) + pages.append(tmp_pages[i]) + else: + if pages[0] != 1: + pages.insert(0, None) + if pages[-1] != paginator.num_pages: + pages.append(None) + + new_context = { + 'MEDIA_URL': settings.MEDIA_URL, + 'STATIC_URL': getattr(settings, "STATIC_URL", None), + 'disable_link_for_first_page': DISABLE_LINK_FOR_FIRST_PAGE, + 'display_disabled_next_link': DISPLAY_DISABLED_NEXT_LINK, + 'display_disabled_previous_link': DISPLAY_DISABLED_PREVIOUS_LINK, + 'display_page_links': DISPLAY_PAGE_LINKS, + 'is_paginated': paginator.count > paginator.per_page, + 'next_link_decorator': NEXT_LINK_DECORATOR, + 'page_obj': page_obj, + 'page_suffix': page_suffix, + 'pages': pages, + 'paginator': paginator, + 'previous_link_decorator': PREVIOUS_LINK_DECORATOR, + 'records': records, + } + if 'request' in context: + getvars = context['request'].GET.copy() + if 'page%s' % page_suffix in getvars: + del getvars['page%s' % page_suffix] + if len(getvars.keys()) > 0: + new_context['getvars'] = "&%s" % getvars.urlencode() + else: + new_context['getvars'] = '' + return new_context + except (KeyError, AttributeError): + return {} + + +register = Library() +register.tag('paginate', do_paginate) +register.tag('autopaginate', do_autopaginate) diff --git a/fnp_django_pagination/test_project/__init__.py b/fnp_django_pagination/test_project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fnp_django_pagination/test_project/example/__init__.py b/fnp_django_pagination/test_project/example/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fnp_django_pagination/test_project/example/models.py b/fnp_django_pagination/test_project/example/models.py new file mode 100644 index 0000000..e69de29 diff --git a/fnp_django_pagination/test_project/example/templates/example/list.html b/fnp_django_pagination/test_project/example/templates/example/list.html new file mode 100644 index 0000000..d81a42c --- /dev/null +++ b/fnp_django_pagination/test_project/example/templates/example/list.html @@ -0,0 +1,13 @@ +{% extends "django_testproject/base.html" %} +{% load pagination_tags %} + + +{% block content %} +

This page demonstrates how autopaginate behaves under certain conditions

+

Below you can see a list of {{ item_list|length }} items.

+{% autopaginate item_list %} +{% for item in item_list %} +

Item {{item}}

+{% endfor %} +{% paginate %} +{% endblock %} diff --git a/fnp_django_pagination/test_project/example/templates/example/two_lists.html b/fnp_django_pagination/test_project/example/templates/example/two_lists.html new file mode 100644 index 0000000..cdb44d9 --- /dev/null +++ b/fnp_django_pagination/test_project/example/templates/example/two_lists.html @@ -0,0 +1,21 @@ +{% extends "django_testproject/base.html" %} +{% load pagination_tags %} + + +{% block content %} +

This page demonstrates how autopaginate behaves under certain conditions

+

Below you can see two lists.

+

First list has {{ first_item_list|length }} items.

+{% autopaginate first_item_list 5 %} +{% for item in first_item_list %} +

{{item}}

+{% endfor %} +{% paginate using "pagination/custom_pagination.html" %} + +

Second list has {{ second_item_list|length}} items.

+{% autopaginate second_item_list 5 %} +{% paginate %} +{% for item in second_item_list %} +

{{item}}

+{% endfor %} +{% endblock %} diff --git a/fnp_django_pagination/test_project/example/templates/pagination/custom_pagination.html b/fnp_django_pagination/test_project/example/templates/pagination/custom_pagination.html new file mode 100644 index 0000000..49320b9 --- /dev/null +++ b/fnp_django_pagination/test_project/example/templates/pagination/custom_pagination.html @@ -0,0 +1,7 @@ +{% extends "pagination/pagination.html" %} + + +{% block previouslink %} +

This part is custom

+ {{ block.super }} +{% endblock previouslink %} diff --git a/fnp_django_pagination/test_project/example/urls.py b/fnp_django_pagination/test_project/example/urls.py new file mode 100644 index 0000000..f3fc5ae --- /dev/null +++ b/fnp_django_pagination/test_project/example/urls.py @@ -0,0 +1,37 @@ +# Copyright (c) 2010, 2011 Linaro Limited +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the author nor the names of other +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +from django.conf.urls.defaults import ( + patterns, url, include, handler500, handler404) + +urlpatterns = patterns( + 'example.views', + url(r'^list/$', 'list'), + url(r'^complex-list/$', 'complex_list')) diff --git a/fnp_django_pagination/test_project/example/views.py b/fnp_django_pagination/test_project/example/views.py new file mode 100644 index 0000000..a2284d0 --- /dev/null +++ b/fnp_django_pagination/test_project/example/views.py @@ -0,0 +1,45 @@ +# Copyright (c) 2010, 2011 Linaro Limited +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the author nor the names of other +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +from django.shortcuts import render_to_response +from django.template import RequestContext + + +def list(request): + return render_to_response("example/list.html", { + 'item_list': range(1000), + }, RequestContext(request)) + + +def complex_list(request): + return render_to_response("example/two_lists.html", { + 'first_item_list': ["first list item %d" % item for item in range(1000)], + 'second_item_list': ["second list item %d" % item for item in range(1000)], + }, RequestContext(request)) diff --git a/fnp_django_pagination/test_project/manage.py b/fnp_django_pagination/test_project/manage.py new file mode 100755 index 0000000..bfb13d7 --- /dev/null +++ b/fnp_django_pagination/test_project/manage.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# Copyright (c) 2010, 2011 Linaro Limited +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the author nor the names of other +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +import logging + +from django.core.management import execute_manager + +try: + from fnp_django_pagination.test_project import settings +except ImportError as ex: + logging.exception("Unable to import application settings") + raise SystemExit(ex) + + +if __name__ == "__main__": + execute_manager(settings) diff --git a/fnp_django_pagination/test_project/settings.py b/fnp_django_pagination/test_project/settings.py new file mode 100644 index 0000000..7791775 --- /dev/null +++ b/fnp_django_pagination/test_project/settings.py @@ -0,0 +1,54 @@ +# Copyright (c) 2010, 2011 Linaro Limited +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the author nor the names of other +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import sys +import os + +# Add this directory to path so that 'example' can be imported later +# below. Without this the runner will fail when started via ``setup.py +# test`` +sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) + +from django_testproject.settings import gen_settings + + +locals().update( + gen_settings( + INSTALLED_APPS=[ + 'example', + 'fnp_django_pagination'], + MIDDLEWARE_CLASSES=[ + 'fnp_django_pagination.middleware.PaginationMiddleware'], + TEMPLATE_CONTEXT_PROCESSORS=[ + # Request processor needs to be enabled + 'django.core.context_processors.request'], + ROOT_URLCONF="fnp_django_pagination.test_project.urls"), + TEMPLATE_LOADERS = ['django.template.loaders.app_directories.Loader'], + SECRET_KEY = 'not for production', + ) diff --git a/fnp_django_pagination/test_project/tests.py b/fnp_django_pagination/test_project/tests.py new file mode 100644 index 0000000..02ba73b --- /dev/null +++ b/fnp_django_pagination/test_project/tests.py @@ -0,0 +1,37 @@ +# Copyright (c) 2010, 2011 Linaro Limited +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the author nor the names of other +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +def run_tests(): + from django_testproject.tests import run_tests_for + run_tests_for("fnp_django_pagination.test_project.settings") + + +if __name__ == '__main__': + run_tests() diff --git a/fnp_django_pagination/test_project/urls.py b/fnp_django_pagination/test_project/urls.py new file mode 100644 index 0000000..cfdf466 --- /dev/null +++ b/fnp_django_pagination/test_project/urls.py @@ -0,0 +1,35 @@ +# Copyright (c) 2010, 2011 Linaro Limited +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the author nor the names of other +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +from django.conf.urls.defaults import ( + patterns, url, include, handler500, handler404) + +# Empty patterns so that test project can be started and would work normally +urlpatterns = patterns('', url('', include('example.urls'))) diff --git a/fnp_django_pagination/tests/__init__.py b/fnp_django_pagination/tests/__init__.py new file mode 100644 index 0000000..7ac2628 --- /dev/null +++ b/fnp_django_pagination/tests/__init__.py @@ -0,0 +1 @@ +from .test_main import * diff --git a/fnp_django_pagination/tests/runner.py b/fnp_django_pagination/tests/runner.py new file mode 100755 index 0000000..843a5a5 --- /dev/null +++ b/fnp_django_pagination/tests/runner.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +import django +import os +import sys + +from django.core.management import call_command + + +def runtests(): + os.environ['DJANGO_SETTINGS_MODULE'] = 'fnp_django_pagination.tests.settings' + + try: + django.setup() + except AttributeError: # for Django 1.6 compatible + pass + + failures = call_command('test', 'fnp_django_pagination') + sys.exit(bool(failures)) + +if __name__ == '__main__': + runtests() diff --git a/fnp_django_pagination/tests/settings.py b/fnp_django_pagination/tests/settings.py new file mode 100644 index 0000000..10243b8 --- /dev/null +++ b/fnp_django_pagination/tests/settings.py @@ -0,0 +1,12 @@ +DATABASES = { + 'default': { + 'NAME': ':memory:', + 'ENGINE': 'django.db.backends.sqlite3', + } +} + +SECRET_KEY = 'fake-key' + +INSTALLED_APPS = ( + 'fnp_django_pagination', +) diff --git a/fnp_django_pagination/tests/test_main.py b/fnp_django_pagination/tests/test_main.py new file mode 100644 index 0000000..8885c16 --- /dev/null +++ b/fnp_django_pagination/tests/test_main.py @@ -0,0 +1,399 @@ +# Copyright (c) 2008, Eric Florenzano +# Copyright (c) 2010, 2011 Linaro Limited +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the author nor the names of other +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from django.core.paginator import Paginator +from django.http import HttpRequest as DjangoHttpRequest +from django.template import Template, Context + +try: + from django.test import SimpleTestCase +except ImportError: # Django 1.2 compatible + from django.test import TestCase as SimpleTestCase + +from fnp_django_pagination.paginator import InfinitePaginator, FinitePaginator +from fnp_django_pagination.templatetags.pagination_tags import paginate +from fnp_django_pagination.middleware import PaginationMiddleware + + +class HttpRequest(DjangoHttpRequest): + page = lambda self, suffix: 1 + + +class CommonTestCase(SimpleTestCase): + def test_records_for_the_first_page(self): + p = Paginator(range(15), 2) + pg = paginate({'paginator': p, 'page_obj': p.page(1)}) + self.assertListEqual(pg['pages'], [1, 2, 3, 4, 5, 6, 7, 8]) + self.assertEqual(pg['records']['first'], 1) + self.assertEqual(pg['records']['last'], 2) + + def test_records_for_the_last_page(self): + p = Paginator(range(15), 2) + pg = paginate({'paginator': p, 'page_obj': p.page(8)}) + self.assertListEqual(pg['pages'], [1, 2, 3, 4, 5, 6, 7, 8]) + self.assertEqual(pg['records']['first'], 15) + self.assertEqual(pg['records']['last'], 15) + + def test_pages_list(self): + p = Paginator(range(17), 2) + self.assertEqual(paginate({'paginator': p, 'page_obj': p.page(1)})['pages'], [1, 2, 3, 4, 5, 6, 7, 8, 9]) + + def test_page_with_empty_objects_list(self): + p = Paginator(range(0), 2) + self.assertListEqual(paginate({'paginator': p, 'page_obj': p.page(1)})['pages'], [1]) + + +class DefaultWindowTestCase(SimpleTestCase): + """ + Test paginate using default window setting + moving the window from 1 ... to end + window size = 2, margin = 2 + window = 2 -> show 5 pages + """ + def setUp(self): + self.p = Paginator(range(31), 2) + + def test_on_start_page_1(self): + # [1] 2 3 4 5 ... 15, 16 + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(1)}, 2, 2)['pages'], + [1, 2, 3, 4, 5, None, 15, 16] + ) + + def test_on_start_page_2(self): + # 1 [2] 3 4 5 ... 15, 16 + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(2)}, 2, 2)['pages'], + [1, 2, 3, 4, 5, None, 15, 16] + ) + + def test_on_start_page_3(self): + # 1 2 [3] 4 5 ... 15, 16 + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(3)}, 2, 2)['pages'], + [1, 2, 3, 4, 5, None, 15, 16] + ) + + def test_on_start_page_4(self): + # 1 2 3 [4] 5 6 ... 15, 16 + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(4)}, 2, 2)['pages'], + [1, 2, 3, 4, 5, 6, None, 15, 16]) + + def test_on_start_page_5(self): + # 1 2 3 4 [5] 6 7 ... 15, 16 + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(5)}, 2, 2)['pages'], + [1, 2, 3, 4, 5, 6, 7, None, 15, 16] + ) + + def test_in_middle(self): + # 1 2 ... 5 6 [7] 8 9 ... 15, 16 + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(7)}, 2, 2)['pages'], + [1, 2, None, 5, 6, 7, 8, 9, None, 15, 16] + ) + + def test_on_end_page_13(self): + # 1 2 ... 12 [13] 14 15 16 + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(13)}, 2, 2)['pages'], + [1, 2, None, 11, 12, 13, 14, 15, 16], + ) + + def test_on_end(self): + # 1 2 ... 12 13 14 15 [16 + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(16)}, 2, 2)['pages'], + [1, 2, None, 12, 13, 14, 15, 16] + ) + + +class NoMarginTestCase(SimpleTestCase): + def setUp(self): + self.p = Paginator(range(31), 2) + + def test_on_start(self): + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(3)}, 2, 0)['pages'], + [1, 2, 3, 4, 5, None], + ) + + def test_in_middle(self): + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(5)}, 2, 0)['pages'], + [None, 3, 4, 5, 6, 7, None], + ) + + def test_on_end(self): + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(16)}, 2, 0)['pages'], + [None, 12, 13, 14, 15, 16], + ) + + +class ZeroWindowZeroMarginTestCase(SimpleTestCase): + """ + Test paginate using window=0 and margin=0 + """ + def setUp(self): + self.p = Paginator(range(31), 2) + + def test_on_start_page_1(self): + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(1)}, 0, 0)['pages'], + [1, None], + ) + + def test_in_middle_page_2(self): + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(2)}, 0, 0)['pages'], + [None, 2, None], + ) + + def test_in_middle_page_3(self): + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(3)}, 0, 0)['pages'], + [None, 3, None], + ) + + def test_in_middle_page_10(self): + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(10)}, 0, 0)['pages'], + [None, 10, None], + ) + + def test_in_middle_page_14(self): + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(14)}, 0, 0)['pages'], + [None, 14, None], + ) + + def test_in_middle_page_15(self): + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(15)}, 0, 0)['pages'], + [None, 15, None], + ) + + def test_on_end_page_16(self): + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(16)}, 0, 0)['pages'], + [None, 16], + ) + + +class NoEllipsisTestCase(SimpleTestCase): + """ + Tests a case where should be no any ellipsis pages. + """ + def setUp(self): + self.p = Paginator(range(100), 25) + + def test_on_start(self): + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(1)}, 2, 0)['pages'], + [1, 2, 3, 4], + ) + + def test_in_middle_page_2(self): + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(2)}, 2, 0)['pages'], + [1, 2, 3, 4], + ) + + def test_in_middle_page_3(self): + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(3)}, 2, 0)['pages'], + [1, 2, 3, 4], + ) + + def test_on_end(self): + self.assertListEqual( + paginate({'paginator': self.p, 'page_obj': self.p.page(4)}, 2, 0)['pages'], + [1, 2, 3, 4], + ) + + +class SpecialTestCase(SimpleTestCase): + def test_middle_with_no_window_and_margin_1(self): + p = Paginator(range(31), 2) + self.assertListEqual( + paginate({'paginator': p, 'page_obj': p.page(5)}, 0, 1)['pages'], + [1, None, 5, None, 16], + ) + + def test_middle_with_no_window_and_margin_4(self): + p = Paginator(range(21), 2, 1) + self.assertListEqual( + paginate({'paginator': p, 'page_obj': p.page(1)}, 0, 4)['pages'], + [1, 2, 3, 4, None, 7, 8, 9, 10], + ) + + +class TemplateRenderingTestCase(SimpleTestCase): + def test_default_tag_options(self): + t = Template("{% load pagination_tags %}{% autopaginate var %}{% paginate %}") + self.assertIn( + '