--- /dev/null
+Copyright (c) 2008, Eric Florenzano
+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.
+
+++ /dev/null
-Copyright (c) 2008, Eric Florenzano
-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.
-
--- /dev/null
+About the fork
+--------------
+
+This project is a fork of apparently dead "django-pagination" project
+originally written by 'Eric Florenzano'. It is maintained by the Linaro
+Validation/Infrastructure team. Latest releases can be found on launchpad and
+pypi.
+
+
+How to use linaro-django-pagination
+-----------------------------------
+
+``linaro-django-pagination`` allows for easy Digg-style pagination without
+modifying your views.
+
+There are really 5 steps to setting it up with your projects (not including
+installation, which is covered in INSTALL.txt in this same directory.)
+
+1. List this application in the ``INSTALLED_APPS`` portion of your settings
+ file. Your settings file might look something like::
+
+ INSTALLED_APPS = (
+ # ...
+ 'linaro_django_pagination',
+ )
+
+
+2. Install the pagination middleware. Your settings file might look something
+ like::
+
+ MIDDLEWARE_CLASSES = (
+ # ...
+ 'linaro_django_pagination.middleware.PaginationMiddleware',
+ )
+
+3. If it's not already added in your setup, add the request context processor.
+ Note that context processors are set by default implicitly, so to set them
+ explicitly, you need to copy and paste this code into your under
+ the value TEMPLATE_CONTEXT_PROCESSORS::
+
+ ("django.core.context_processors.auth",
+ "django.core.context_processors.debug",
+ "django.core.context_processors.i18n",
+ "django.core.context_processors.media",
+ "django.core.context_processors.request")
+
+4. Add this line at the top of your template to load the pagination tags:
+
+ {% load pagination_tags %}
+
+
+5. Decide on a variable that you would like to paginate, and use the
+ autopaginate tag on that variable before iterating over it. This could
+ take one of two forms (using the canonical ``object_list`` as an example
+ variable):
+
+ {% autopaginate object_list %}
+
+ This assumes that you would like to have the default 20 results per page.
+ If you would like to specify your own amount of results per page, you can
+ specify that like so:
+
+ {% autopaginate object_list 10 %}
+
+ Note that this replaces ``object_list`` with the list for the current page, so
+ you can iterate over the ``object_list`` like you normally would.
+
+
+6. Now you want to display the current page and the available pages, so
+ somewhere after having used autopaginate, use the paginate inclusion tag:
+
+ {% paginate %}
+
+ This does not take any arguments, but does assume that you have already
+ called autopaginate, so make sure to do so first.
+
+
+That's it! You have now paginated ``object_list`` and given users of the site
+a way to navigate between the different pages--all without touching your views.
+
+
+Optional Settings
+-----------------
+
+In linaro-django-pagination, there are no required settings. There are,
+however, a small set of optional settings useful for changing the default
+behavior of the pagination tags. Here's an overview:
+
+``PAGINATION_DEFAULT_PAGINATION``
+ The default amount of items to show on a page if no number is specified.
+
+``PAGINATION_DEFAULT_WINDOW``
+ The number of items to the left and to the right of the current page to
+ display (accounting for ellipses).
+
+``PAGINATION_DEFAULT_ORPHANS``
+ The number of orphans allowed. According to the Django documentation,
+ orphans are defined as::
+
+ The minimum number of items allowed on the last page, defaults to zero.
+
+``PAGINATION_INVALID_PAGE_RAISES_404``
+ Determines whether an invalid page raises an ``Http404`` or just sets the
+ ``invalid_page`` context variable. ``True`` does the former and ``False``
+ does the latter.
#################
-django-pagination
+linaro-django-pagination
#################
Django-pagination is a set of utilities for creating robust pagination tools
-Installing the latest development version of django-pagination
+Installing the latest development version of linaro-linaro-django-pagination
---------------------------------------------------------------
To install, first check out the latest version of the application from
subversion:
- svn co http://django-pagination.googlecode.com/svn/trunk django-pagination
+ svn co http://linaro-django-pagination.googlecode.com/svn/trunk linaro-django-pagination
Now, link the inner ``pagination`` project to your Python path:
sudo python setup.py install
-Once that's done, you should be able to begin using django-pagination at will.
+Once that's done, you should be able to begin using linaro-django-pagination at will.
Installing via setuptools
-------------------------
If you have setuptools_ installed, you can simply run the following command
-to install django-pagination:
+to install linaro-django-pagination:
- sudo easy_install django-pagination
+ sudo easy_install linaro-django-pagination
-.. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools
\ No newline at end of file
+.. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools
-How to use django-pagination
+How to use linaro-django-pagination
----------------------------
-``django-pagination`` allows for easy Digg-style pagination without modifying
+``linaro-django-pagination`` allows for easy Digg-style pagination without modifying
your views.
There are really 5 steps to setting it up with your projects (not including
A Note About Uploads
--------------------
-It is important, when using django-pagination in conjunction with file uploads,
+It is important, when using linaro-django-pagination in conjunction with file uploads,
to be aware of when ``request.page`` is accessed. As soon as ``request.page``
is accessed, ``request.upload_handlers`` is frozen and cannot be altered in any
way. It's a good idea to access the ``page`` attribute on the request object
Optional Settings
------------------
-In django-pagination, there are no required settings. There are, however, a
+In linaro-django-pagination, there are no required settings. There are, however, a
small set of optional settings useful for changing the default behavior of the
pagination tags. Here's an overview:
--- /dev/null
+# 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 <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-03-16 16:26+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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"
--- /dev/null
+# linaro-django-pagination French translation.
+# Copyright (C) 2008, Julien Demoor
+# This file is distributed under the same license as the linaro-django-pagination package.
+# Julien Demoor <julien@jdemoor.com>, 2008
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-24 00:41-0700\n"
+"PO-Revision-Date: 2008-10-19 10:19+0200\n"
+"Last-Translator: Julien Demoor <julien@jdemoor.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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"
--- /dev/null
+# 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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"
--- /dev/null
+# 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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"
--- /dev/null
+# 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 <jarek.zgoda@gmail.com>, 2008.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: 1.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-24 00:41-0700\n"
+"PO-Revision-Date: 2008-10-20 20:52+0200\n"
+"Last-Translator: Jarek Zgoda <jarek.zgoda@gmail.com>\n"
+"Language-Team: PL <pl@li.org>\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"
--- /dev/null
+# 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: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-24 00:41-0700\n"
+"PO-Revision-Date: 2008-10-19 10:19+0200\n"
+"Last-Translator: Alcides Fonseca <me@alcidesfonseca.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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"
--- /dev/null
+# 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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 "Следующая"
--- /dev/null
+# 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 <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-03-16 16:26+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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"
--- /dev/null
+def get_page(self, suffix):
+ """
+ A function which will be monkeypatched onto the request to get the current
+ integer representing the current page.
+ """
+ try:
+ return int(self.REQUEST['page%s' % suffix])
+ 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
\ No newline at end of file
--- /dev/null
+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 '<Page %s>' % self.number
+
+ def has_next(self):
+ """
+ Checks for one more item than last on this page.
+ """
+ try:
+ next_item = 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:
+ next_item = 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
\ No newline at end of file
--- /dev/null
+{% if is_paginated %}
+{% load i18n %}
+<div class="pagination">
+ {% if page_obj.has_previous %}
+ <a href="?page{{ page_suffix }}={{ page_obj.previous_page_number }}{{ getvars }}" class="prev">‹‹ {% trans "previous" %}</a>
+ {% else %}
+ <span class="disabled prev">‹‹ {% trans "previous" %}</span>
+ {% endif %}
+ {% for page in pages %}
+ {% if page %}
+ {% ifequal page page_obj.number %}
+ <span class="current page">{{ page }}</span>
+ {% else %}
+ <a href="?page{{ page_suffix }}={{ page }}{{ getvars }}" class="page">{{ page }}</a>
+ {% endifequal %}
+ {% else %}
+ ...
+ {% endif %}
+ {% endfor %}
+ {% if page_obj.has_next %}
+ <a href="?page{{ page_suffix }}={{ page_obj.next_page_number }}{{ getvars }}" class="next">{% trans "next" %} ››</a>
+ {% else %}
+ <span class="disabled next">{% trans "next" %} ››</span>
+ {% endif %}
+</div>
+{% endif %}
--- /dev/null
+try:
+ set
+except NameError:
+ from sets import Set as set
+
+from django import template
+from django.template import TOKEN_BLOCK
+from django.http import Http404
+from django.core.paginator import Paginator, InvalidPage
+from django.conf import settings
+
+register = template.Library()
+
+DEFAULT_PAGINATION = getattr(settings, 'PAGINATION_DEFAULT_PAGINATION', 20)
+DEFAULT_WINDOW = getattr(settings, 'PAGINATION_DEFAULT_WINDOW', 4)
+DEFAULT_ORPHANS = getattr(settings, 'PAGINATION_DEFAULT_ORPHANS', 0)
+INVALID_PAGE_RAISES_404 = getattr(settings,
+ 'PAGINATION_INVALID_PAGE_RAISES_404', False)
+
+def do_autopaginate(parser, token):
+ """
+ Splits the arguments to the autopaginate tag and formats them correctly.
+
+ Syntax is:
+
+ autopaginate SOMETHING [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(filter(expr, parser.tokens)) > 0
+
+ i = iter(token.split_contents())
+ paginate_by = None
+ queryset_var = None
+ context_var = None
+ orphans = None
+ word = None
+ try:
+ word = i.next()
+ assert word == "autopaginate"
+ queryset_var = i.next()
+ word = i.next()
+ if word != "as":
+ paginate_by = word
+ try:
+ paginate_by = int(paginate_by)
+ except ValueError:
+ pass
+ word = i.next()
+ if word != "as":
+ orphans = word
+ try:
+ orphans = int(orphans)
+ except ValueError:
+ pass
+ word = i.next()
+ assert word == "as"
+ context_var = i.next()
+ except StopIteration:
+ pass
+ if queryset_var is None:
+ raise template.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(template.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 = template.Variable(queryset_var)
+ if isinstance(paginate_by, int):
+ self.paginate_by = paginate_by
+ else:
+ self.paginate_by = template.Variable(paginate_by)
+ if isinstance(orphans, int):
+ self.orphans = orphans
+ else:
+ self.orphans = template.Variable(orphans)
+ self.context_var = context_var
+ self.multiple_paginations = multiple_paginations
+
+ def render(self, context):
+ if self.multiple_paginations or context.has_key('paginator'):
+ 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:
+ page_obj = paginator.page(context['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 u''
+ 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 u''
+
+
+def paginate(context, window=DEFAULT_WINDOW):
+ """
+ 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.
+ """
+ try:
+ paginator = context['paginator']
+ page_obj = context['page_obj']
+ page_suffix = context.get('page_suffix', '')
+ page_range = paginator.page_range
+ # First and last are simply the first *n* pages and the last *n* pages,
+ # where *n* is the current window size.
+ first = set(page_range[:window])
+ last = set(page_range[-window:])
+ # Now we look around our current page, making sure that we don't wrap
+ # around.
+ current_start = page_obj.number-1-window
+ if current_start < 0:
+ current_start = 0
+ current_end = page_obj.number-1+window
+ if current_end < 0:
+ current_end = 0
+ current = set(page_range[current_start:current_end])
+ pages = []
+ # If there's no overlap between the first set of pages and the current
+ # set of pages, then there's a possible need for elusion.
+ if len(first.intersection(current)) == 0:
+ first_list = list(first)
+ first_list.sort()
+ second_list = list(current)
+ second_list.sort()
+ pages.extend(first_list)
+ diff = second_list[0] - first_list[-1]
+ # If there is a gap of two, between the last page of the first
+ # set and the first page of the current set, then we're missing a
+ # page.
+ if diff == 2:
+ pages.append(second_list[0] - 1)
+ # If the difference is just one, then there's nothing to be done,
+ # as the pages need no elusion and are correct.
+ elif diff == 1:
+ pass
+ # Otherwise, there's a bigger gap which needs to be signaled for
+ # elusion, by pushing a None value to the page list.
+ else:
+ pages.append(None)
+ pages.extend(second_list)
+ else:
+ unioned = list(first.union(current))
+ unioned.sort()
+ pages.extend(unioned)
+ # If there's no overlap between the current set of pages and the last
+ # set of pages, then there's a possible need for elusion.
+ if len(current.intersection(last)) == 0:
+ second_list = list(last)
+ second_list.sort()
+ diff = second_list[0] - pages[-1]
+ # If there is a gap of two, between the last page of the current
+ # set and the first page of the last set, then we're missing a
+ # page.
+ if diff == 2:
+ pages.append(second_list[0] - 1)
+ # If the difference is just one, then there's nothing to be done,
+ # as the pages need no elusion and are correct.
+ elif diff == 1:
+ pass
+ # Otherwise, there's a bigger gap which needs to be signaled for
+ # elusion, by pushing a None value to the page list.
+ else:
+ pages.append(None)
+ pages.extend(second_list)
+ else:
+ differenced = list(last.difference(current))
+ differenced.sort()
+ pages.extend(differenced)
+ to_return = {
+ 'pages': pages,
+ 'page_obj': page_obj,
+ 'paginator': paginator,
+ 'is_paginated': paginator.count > paginator.per_page,
+ 'page_suffix': page_suffix,
+ }
+ 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:
+ to_return['getvars'] = "&%s" % getvars.urlencode()
+ else:
+ to_return['getvars'] = ''
+ return to_return
+ except KeyError, AttributeError:
+ return {}
+
+
+register.inclusion_tag(
+ 'pagination/pagination.html', takes_context=True)(paginate)
+
+register.tag('autopaginate', do_autopaginate)
--- /dev/null
+"""
+>>> from django.core.paginator import Paginator
+>>> from linaro_django_pagination.templatetags.pagination_tags import paginate
+>>> from django.template import Template, Context
+
+>>> p = Paginator(range(15), 2)
+>>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
+[1, 2, 3, 4, 5, 6, 7, 8]
+
+>>> p = Paginator(range(17), 2)
+>>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
+[1, 2, 3, 4, 5, 6, 7, 8, 9]
+
+>>> p = Paginator(range(19), 2)
+>>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
+[1, 2, 3, 4, None, 7, 8, 9, 10]
+
+>>> p = Paginator(range(21), 2)
+>>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
+[1, 2, 3, 4, None, 8, 9, 10, 11]
+
+# Testing orphans
+>>> p = Paginator(range(5), 2, 1)
+>>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
+[1, 2]
+
+>>> p = Paginator(range(21), 2, 1)
+>>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
+[1, 2, 3, 4, None, 7, 8, 9, 10]
+
+>>> p = Paginator(range(21), 2, 1)
+>>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
+[1, 2, 3, 4, None, 7, 8, 9, 10]
+
+>>> t = Template("{% load pagination_tags %}{% autopaginate var 2 %}{% paginate %}")
+
+>>> from django.http import HttpRequest as DjangoHttpRequest
+>>> class HttpRequest(DjangoHttpRequest):
+... page = lambda self, suffix: 1
+
+>>> t.render(Context({'var': range(21), 'request': HttpRequest()}))
+u'\\n\\n<div class="pagination">...
+>>>
+>>> t = Template("{% load pagination_tags %}{% autopaginate var %}{% paginate %}")
+>>> t.render(Context({'var': range(21), 'request': HttpRequest()}))
+u'\\n\\n<div class="pagination">...
+>>> t = Template("{% load pagination_tags %}{% autopaginate var 20 %}{% paginate %}")
+>>> t.render(Context({'var': range(21), 'request': HttpRequest()}))
+u'\\n\\n<div class="pagination">...
+>>> t = Template("{% load pagination_tags %}{% autopaginate var by %}{% paginate %}")
+>>> t.render(Context({'var': range(21), 'by': 20, 'request': HttpRequest()}))
+u'\\n\\n<div class="pagination">...<a href="?page=2"...
+>>> t = Template("{% load pagination_tags %}{% autopaginate var by as foo %}{{ foo }}")
+>>> t.render(Context({'var': range(21), 'by': 20, 'request': HttpRequest()}))
+u'[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]'
+>>>
+>>> t = Template("{% load pagination_tags %}{% autopaginate var2 by as foo2 %}{% paginate %}{% autopaginate var by as foo %}{% paginate %}")
+>>> t.render(Context({'var': range(21), 'var2': range(50, 121), 'by': 20, 'request': HttpRequest()}))
+u'\\n\\n<div class="pagination">...<a href="?page_var2=2"...<a href="?page_var=2"...
+>>>
+
+# Testing InfinitePaginator
+
+>>> from paginator import InfinitePaginator
+
+>>> InfinitePaginator
+<class 'linaro_django_pagination.paginator.InfinitePaginator'>
+>>> p = InfinitePaginator(range(20), 2, link_template='/bacon/page/%d')
+>>> p.validate_number(2)
+2
+>>> p.orphans
+0
+>>> p3 = p.page(3)
+>>> p3
+<Page 3>
+>>> p3.end_index()
+6
+>>> p3.has_next()
+True
+>>> p3.has_previous()
+True
+>>> p.page(10).has_next()
+False
+>>> p.page(1).has_previous()
+False
+>>> p3.next_link()
+'/bacon/page/4'
+>>> p3.previous_link()
+'/bacon/page/2'
+
+# Testing FinitePaginator
+
+>>> from paginator import FinitePaginator
+
+>>> FinitePaginator
+<class 'linaro_django_pagination.paginator.FinitePaginator'>
+>>> p = FinitePaginator(range(20), 2, offset=10, link_template='/bacon/page/%d')
+>>> p.validate_number(2)
+2
+>>> p.orphans
+0
+>>> p3 = p.page(3)
+>>> p3
+<Page 3>
+>>> p3.start_index()
+10
+>>> p3.end_index()
+6
+>>> p3.has_next()
+True
+>>> p3.has_previous()
+True
+>>> p3.next_link()
+'/bacon/page/4'
+>>> p3.previous_link()
+'/bacon/page/2'
+
+>>> p = FinitePaginator(range(20), 20, offset=10, link_template='/bacon/page/%d')
+>>> p2 = p.page(2)
+>>> p2
+<Page 2>
+>>> p2.has_next()
+False
+>>> p3.has_previous()
+True
+>>> p2.next_link()
+
+>>> p2.previous_link()
+'/bacon/page/1'
+
+>>> from linaro_django_pagination.middleware import PaginationMiddleware
+>>> from django.core.handlers.wsgi import WSGIRequest
+>>> from StringIO import StringIO
+>>> middleware = PaginationMiddleware()
+>>> request = WSGIRequest({'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'multipart', 'wsgi.input': StringIO()})
+>>> middleware.process_request(request)
+>>> request.upload_handlers.append('asdf')
+"""
+++ /dev/null
-# 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 <EMAIL@ADDRESS>, YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-03-16 16:26+0100\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\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"
+++ /dev/null
-# django-pagination French translation.
-# Copyright (C) 2008, Julien Demoor
-# This file is distributed under the same license as the django-pagination package.
-# Julien Demoor <julien@jdemoor.com>, 2008
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-10-24 00:41-0700\n"
-"PO-Revision-Date: 2008-10-19 10:19+0200\n"
-"Last-Translator: Julien Demoor <julien@jdemoor.com>\n"
-"Language-Team: LANGUAGE <LL@li.org>\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"
+++ /dev/null
-# Polish translation of django-pagination.
-# Copyright (C) 2008, django-pagination team
-# This file is distributed under the same license as the django-pagination package.
-# Jarek Zgoda <jarek.zgoda@gmail.com>, 2008.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: 1.0\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-10-24 00:41-0700\n"
-"PO-Revision-Date: 2008-10-20 20:52+0200\n"
-"Last-Translator: Jarek Zgoda <jarek.zgoda@gmail.com>\n"
-"Language-Team: PL <pl@li.org>\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"
+++ /dev/null
-# django-pagination Portuguese translation.
-# Copyright (C) 2008, Alcides Fonseca
-# http://alcidesfonseca.com
-# This file is distributed under the WTFPL
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-10-24 00:41-0700\n"
-"PO-Revision-Date: 2008-10-19 10:19+0200\n"
-"Last-Translator: Alcides Fonseca <me@alcidesfonseca.com>\n"
-"Language-Team: LANGUAGE <LL@li.org>\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"
+++ /dev/null
-# 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\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 "Следующая"
+++ /dev/null
-def get_page(self, suffix):
- """
- A function which will be monkeypatched onto the request to get the current
- integer representing the current page.
- """
- try:
- return int(self.REQUEST['page%s' % suffix])
- 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
\ No newline at end of file
+++ /dev/null
-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 '<Page %s>' % self.number
-
- def has_next(self):
- """
- Checks for one more item than last on this page.
- """
- try:
- next_item = 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:
- next_item = 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
\ No newline at end of file
+++ /dev/null
-{% if is_paginated %}
-{% load i18n %}
-<div class="pagination">
- {% if page_obj.has_previous %}
- <a href="?page{{ page_suffix }}={{ page_obj.previous_page_number }}{{ getvars }}" class="prev">‹‹ {% trans "previous" %}</a>
- {% else %}
- <span class="disabled prev">‹‹ {% trans "previous" %}</span>
- {% endif %}
- {% for page in pages %}
- {% if page %}
- {% ifequal page page_obj.number %}
- <span class="current page">{{ page }}</span>
- {% else %}
- <a href="?page{{ page_suffix }}={{ page }}{{ getvars }}" class="page">{{ page }}</a>
- {% endifequal %}
- {% else %}
- ...
- {% endif %}
- {% endfor %}
- {% if page_obj.has_next %}
- <a href="?page{{ page_suffix }}={{ page_obj.next_page_number }}{{ getvars }}" class="next">{% trans "next" %} ››</a>
- {% else %}
- <span class="disabled next">{% trans "next" %} ››</span>
- {% endif %}
-</div>
-{% endif %}
+++ /dev/null
-try:
- set
-except NameError:
- from sets import Set as set
-
-from django import template
-from django.template import TOKEN_BLOCK
-from django.http import Http404
-from django.core.paginator import Paginator, InvalidPage
-from django.conf import settings
-
-register = template.Library()
-
-DEFAULT_PAGINATION = getattr(settings, 'PAGINATION_DEFAULT_PAGINATION', 20)
-DEFAULT_WINDOW = getattr(settings, 'PAGINATION_DEFAULT_WINDOW', 4)
-DEFAULT_ORPHANS = getattr(settings, 'PAGINATION_DEFAULT_ORPHANS', 0)
-INVALID_PAGE_RAISES_404 = getattr(settings,
- 'PAGINATION_INVALID_PAGE_RAISES_404', False)
-
-def do_autopaginate(parser, token):
- """
- Splits the arguments to the autopaginate tag and formats them correctly.
- """
-
- # 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(filter(expr, parser.tokens)) > 0
-
- split = token.split_contents()
- as_index = None
- context_var = None
- for i, bit in enumerate(split):
- if bit == 'as':
- as_index = i
- break
- if as_index is not None:
- try:
- context_var = split[as_index + 1]
- except IndexError:
- raise template.TemplateSyntaxError("Context variable assignment " +
- "must take the form of {%% %r object.example_set.all ... as " +
- "context_var_name %%}" % split[0])
- del split[as_index:as_index + 2]
- if len(split) == 2:
- return AutoPaginateNode(split[1], multiple_paginations=multiple_paginations)
- elif len(split) == 3:
- return AutoPaginateNode(split[1], paginate_by=split[2],
- context_var=context_var, multiple_paginations=multiple_paginations)
- elif len(split) == 4:
- try:
- orphans = int(split[3])
- except ValueError:
- raise template.TemplateSyntaxError(u'Got %s, but expected integer.'
- % split[3])
- return AutoPaginateNode(split[1], paginate_by=split[2], orphans=orphans,
- context_var=context_var, multiple_paginations=multiple_paginations)
- else:
- raise template.TemplateSyntaxError('%r tag takes one required ' +
- 'argument and one optional argument' % split[0])
-
-class AutoPaginateNode(template.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=DEFAULT_PAGINATION,
- orphans=DEFAULT_ORPHANS, context_var=None):
- self.queryset_var = template.Variable(queryset_var)
- if isinstance(paginate_by, int):
- self.paginate_by = paginate_by
- else:
- self.paginate_by = template.Variable(paginate_by)
- self.orphans = orphans
- self.context_var = context_var
- self.multiple_paginations = multiple_paginations
-
- def render(self, context):
- if self.multiple_paginations or context.has_key('paginator'):
- 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)
- paginator = Paginator(value, paginate_by, self.orphans)
- try:
- page_obj = paginator.page(context['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 u''
- 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 u''
-
-def paginate(context, window=DEFAULT_WINDOW):
- """
- 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.
- """
- try:
- paginator = context['paginator']
- page_obj = context['page_obj']
- page_suffix = context.get('page_suffix', '')
- page_range = paginator.page_range
- # First and last are simply the first *n* pages and the last *n* pages,
- # where *n* is the current window size.
- first = set(page_range[:window])
- last = set(page_range[-window:])
- # Now we look around our current page, making sure that we don't wrap
- # around.
- current_start = page_obj.number-1-window
- if current_start < 0:
- current_start = 0
- current_end = page_obj.number-1+window
- if current_end < 0:
- current_end = 0
- current = set(page_range[current_start:current_end])
- pages = []
- # If there's no overlap between the first set of pages and the current
- # set of pages, then there's a possible need for elusion.
- if len(first.intersection(current)) == 0:
- first_list = list(first)
- first_list.sort()
- second_list = list(current)
- second_list.sort()
- pages.extend(first_list)
- diff = second_list[0] - first_list[-1]
- # If there is a gap of two, between the last page of the first
- # set and the first page of the current set, then we're missing a
- # page.
- if diff == 2:
- pages.append(second_list[0] - 1)
- # If the difference is just one, then there's nothing to be done,
- # as the pages need no elusion and are correct.
- elif diff == 1:
- pass
- # Otherwise, there's a bigger gap which needs to be signaled for
- # elusion, by pushing a None value to the page list.
- else:
- pages.append(None)
- pages.extend(second_list)
- else:
- unioned = list(first.union(current))
- unioned.sort()
- pages.extend(unioned)
- # If there's no overlap between the current set of pages and the last
- # set of pages, then there's a possible need for elusion.
- if len(current.intersection(last)) == 0:
- second_list = list(last)
- second_list.sort()
- diff = second_list[0] - pages[-1]
- # If there is a gap of two, between the last page of the current
- # set and the first page of the last set, then we're missing a
- # page.
- if diff == 2:
- pages.append(second_list[0] - 1)
- # If the difference is just one, then there's nothing to be done,
- # as the pages need no elusion and are correct.
- elif diff == 1:
- pass
- # Otherwise, there's a bigger gap which needs to be signaled for
- # elusion, by pushing a None value to the page list.
- else:
- pages.append(None)
- pages.extend(second_list)
- else:
- differenced = list(last.difference(current))
- differenced.sort()
- pages.extend(differenced)
- to_return = {
- 'pages': pages,
- 'page_obj': page_obj,
- 'paginator': paginator,
- 'is_paginated': paginator.count > paginator.per_page,
- 'page_suffix': page_suffix,
- }
- 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:
- to_return['getvars'] = "&%s" % getvars.urlencode()
- else:
- to_return['getvars'] = ''
- return to_return
- except KeyError, AttributeError:
- return {}
-
-register.inclusion_tag('pagination/pagination.html', takes_context=True)(
- paginate)
-register.tag('autopaginate', do_autopaginate)
+++ /dev/null
-"""
->>> from django.core.paginator import Paginator
->>> from pagination.templatetags.pagination_tags import paginate
->>> from django.template import Template, Context
-
->>> p = Paginator(range(15), 2)
->>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
-[1, 2, 3, 4, 5, 6, 7, 8]
-
->>> p = Paginator(range(17), 2)
->>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
-[1, 2, 3, 4, 5, 6, 7, 8, 9]
-
->>> p = Paginator(range(19), 2)
->>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
-[1, 2, 3, 4, None, 7, 8, 9, 10]
-
->>> p = Paginator(range(21), 2)
->>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
-[1, 2, 3, 4, None, 8, 9, 10, 11]
-
-# Testing orphans
->>> p = Paginator(range(5), 2, 1)
->>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
-[1, 2]
-
->>> p = Paginator(range(21), 2, 1)
->>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
-[1, 2, 3, 4, None, 7, 8, 9, 10]
-
->>> p = Paginator(range(21), 2, 1)
->>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
-[1, 2, 3, 4, None, 7, 8, 9, 10]
-
->>> t = Template("{% load pagination_tags %}{% autopaginate var 2 %}{% paginate %}")
-
->>> from django.http import HttpRequest as DjangoHttpRequest
->>> class HttpRequest(DjangoHttpRequest):
-... page = lambda self, suffix: 1
-
->>> t.render(Context({'var': range(21), 'request': HttpRequest()}))
-u'\\n\\n<div class="pagination">...
->>>
->>> t = Template("{% load pagination_tags %}{% autopaginate var %}{% paginate %}")
->>> t.render(Context({'var': range(21), 'request': HttpRequest()}))
-u'\\n\\n<div class="pagination">...
->>> t = Template("{% load pagination_tags %}{% autopaginate var 20 %}{% paginate %}")
->>> t.render(Context({'var': range(21), 'request': HttpRequest()}))
-u'\\n\\n<div class="pagination">...
->>> t = Template("{% load pagination_tags %}{% autopaginate var by %}{% paginate %}")
->>> t.render(Context({'var': range(21), 'by': 20, 'request': HttpRequest()}))
-u'\\n\\n<div class="pagination">...<a href="?page=2"...
->>> t = Template("{% load pagination_tags %}{% autopaginate var by as foo %}{{ foo }}")
->>> t.render(Context({'var': range(21), 'by': 20, 'request': HttpRequest()}))
-u'[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]'
->>>
->>> t = Template("{% load pagination_tags %}{% autopaginate var2 by as foo2 %}{% paginate %}{% autopaginate var by as foo %}{% paginate %}")
->>> t.render(Context({'var': range(21), 'var2': range(50, 121), 'by': 20, 'request': HttpRequest()}))
-u'\\n\\n<div class="pagination">...<a href="?page_var2=2"...<a href="?page_var=2"...
->>>
-
-# Testing InfinitePaginator
-
->>> from paginator import InfinitePaginator
-
->>> InfinitePaginator
-<class 'pagination.paginator.InfinitePaginator'>
->>> p = InfinitePaginator(range(20), 2, link_template='/bacon/page/%d')
->>> p.validate_number(2)
-2
->>> p.orphans
-0
->>> p3 = p.page(3)
->>> p3
-<Page 3>
->>> p3.end_index()
-6
->>> p3.has_next()
-True
->>> p3.has_previous()
-True
->>> p.page(10).has_next()
-False
->>> p.page(1).has_previous()
-False
->>> p3.next_link()
-'/bacon/page/4'
->>> p3.previous_link()
-'/bacon/page/2'
-
-# Testing FinitePaginator
-
->>> from paginator import FinitePaginator
-
->>> FinitePaginator
-<class 'pagination.paginator.FinitePaginator'>
->>> p = FinitePaginator(range(20), 2, offset=10, link_template='/bacon/page/%d')
->>> p.validate_number(2)
-2
->>> p.orphans
-0
->>> p3 = p.page(3)
->>> p3
-<Page 3>
->>> p3.start_index()
-10
->>> p3.end_index()
-6
->>> p3.has_next()
-True
->>> p3.has_previous()
-True
->>> p3.next_link()
-'/bacon/page/4'
->>> p3.previous_link()
-'/bacon/page/2'
-
->>> p = FinitePaginator(range(20), 20, offset=10, link_template='/bacon/page/%d')
->>> p2 = p.page(2)
->>> p2
-<Page 2>
->>> p2.has_next()
-False
->>> p3.has_previous()
-True
->>> p2.next_link()
-
->>> p2.previous_link()
-'/bacon/page/1'
-
->>> from pagination.middleware import PaginationMiddleware
->>> from django.core.handlers.wsgi import WSGIRequest
->>> from StringIO import StringIO
->>> middleware = PaginationMiddleware()
->>> request = WSGIRequest({'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'multipart', 'wsgi.input': StringIO()})
->>> middleware.process_request(request)
->>> request.upload_handlers.append('asdf')
-"""
\ No newline at end of file
+#!/usr/bin/env python
from setuptools import setup, find_packages
-version = '1.0.7'
-
-LONG_DESCRIPTION = """
-How to use django-pagination
-----------------------------
-
-``django-pagination`` allows for easy Digg-style pagination without modifying
-your views.
-
-There are really 5 steps to setting it up with your projects (not including
-installation, which is covered in INSTALL.txt in this same directory.)
-
-1. List this application in the ``INSTALLED_APPS`` portion of your settings
- file. Your settings file might look something like::
-
- INSTALLED_APPS = (
- # ...
- 'pagination',
- )
-
-
-2. Install the pagination middleware. Your settings file might look something
- like::
-
- MIDDLEWARE_CLASSES = (
- # ...
- 'pagination.middleware.PaginationMiddleware',
- )
-
-3. If it's not already added in your setup, add the request context processor.
- Note that context processors are set by default implicitly, so to set them
- explicitly, you need to copy and paste this code into your under
- the value TEMPLATE_CONTEXT_PROCESSORS::
-
- ("django.core.context_processors.auth",
- "django.core.context_processors.debug",
- "django.core.context_processors.i18n",
- "django.core.context_processors.media",
- "django.core.context_processors.request")
-
-4. Add this line at the top of your template to load the pagination tags:
-
- {% load pagination_tags %}
-
-
-5. Decide on a variable that you would like to paginate, and use the
- autopaginate tag on that variable before iterating over it. This could
- take one of two forms (using the canonical ``object_list`` as an example
- variable):
-
- {% autopaginate object_list %}
-
- This assumes that you would like to have the default 20 results per page.
- If you would like to specify your own amount of results per page, you can
- specify that like so:
-
- {% autopaginate object_list 10 %}
-
- Note that this replaces ``object_list`` with the list for the current page, so
- you can iterate over the ``object_list`` like you normally would.
-
-
-6. Now you want to display the current page and the available pages, so
- somewhere after having used autopaginate, use the paginate inclusion tag:
-
- {% paginate %}
-
- This does not take any arguments, but does assume that you have already
- called autopaginate, so make sure to do so first.
-
-
-That's it! You have now paginated ``object_list`` and given users of the site
-a way to navigate between the different pages--all without touching your views.
-
-
-Optional Settings
-------------------
-
-In django-pagination, there are no required settings. There are, however, a
-small set of optional settings useful for changing the default behavior of the
-pagination tags. Here's an overview:
-
-``PAGINATION_DEFAULT_PAGINATION``
- The default amount of items to show on a page if no number is specified.
-
-``PAGINATION_DEFAULT_WINDOW``
- The number of items to the left and to the right of the current page to
- display (accounting for ellipses).
-
-``PAGINATION_DEFAULT_ORPHANS``
- The number of orphans allowed. According to the Django documentation,
- orphans are defined as::
-
- The minimum number of items allowed on the last page, defaults to zero.
-
-``PAGINATION_INVALID_PAGE_RAISES_404``
- Determines whether an invalid page raises an ``Http404`` or just sets the
- ``invalid_page`` context variable. ``True`` does the former and ``False``
- does the latter.
-"""
+version = '1.0.7.1'
setup(
- name='django-pagination',
+ name='linaro-django-pagination',
version=version,
- description="django-pagination",
- long_description=LONG_DESCRIPTION,
+ description="linaro-django-pagination",
+ long_description=open("README").read(),
classifiers=[
"Programming Language :: Python",
"Topic :: Software Development :: Libraries :: Python Modules",
],
keywords='pagination,django',
author='Eric Florenzano',
- author_email='floguy@gmail.com',
- url='http://django-pagination.googlecode.com/',
+ author_email='zygmunt.krynicki@linaro.org',
+ url='http://launchpad.net/linaro-django-pagination/',
license='BSD',
packages=find_packages(),
include_package_data=True,
from django.test.simple import run_tests
if __name__ == "__main__":
- failures = run_tests(['pagination',], verbosity=9)
+ failures = run_tests(['linaro_django_pagination',], verbosity=9)
if failures:
sys.exit(failures)
# Reset the DJANGO_SETTINGS_MODULE to what it was before running tests.
ROOT_URLCONF = ''
SITE_ID = 1
INSTALLED_APPS = (
- 'pagination',
+ 'linaro_django_pagination',
)