--- /dev/null
- from django import template
+# 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.template import TOKEN_BLOCK
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.paginator import Paginator, InvalidPage
+from django.http import Http404
- raise template.TemplateSyntaxError(
++from django.template import (
++ Context,
++ Library,
++ Node,
++ TOKEN_BLOCK,
++ TemplateSyntaxError,
++ Variable,
++)
++from django.template.loader import select_template
+
+# TODO, import this normally later on
+from linaro_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(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:
- class AutoPaginateNode(template.Node):
++ 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)
+
+
- self.queryset_var = template.Variable(queryset_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.paginate_by = template.Variable(paginate_by)
++ self.queryset_var = Variable(queryset_var)
+ if isinstance(paginate_by, int):
+ self.paginate_by = paginate_by
+ else:
- self.orphans = template.Variable(orphans)
++ self.paginate_by = Variable(paginate_by)
+ if isinstance(orphans, int):
+ self.orphans = orphans
+ else:
-
- ``pagination_template``
- A custom template to include in place of the default ``pagination/default.html``
- contents.
++ self.orphans = Variable(orphans)
+ self.context_var = context_var
+ self.multiple_paginations = multiple_paginations
+
+ def render(self, context):
+ if self.multiple_paginations or "paginator" in context:
+ 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 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''
+
+
++class PaginateNode(Node):
++
++ def __init__(self, template=None):
++ self.template = template
++
++ def render(self, context):
++ template_list = ['pagination/pagination.html']
++ to_return = paginate(context)
++ if self.template:
++ template_list.insert(0, self.template)
++ t = select_template(template_list)
++ if not t:
++ return None
++ context = Context(to_return)
++ return t.render(context)
++
++
++def do_paginate(parser, token):
++ """
++ {% paginate [using] [template] %}
++
++ {% paginate %}
++ {% paginate using paginations/custom_pagination.html %}
++ """
++ argv = token.contents.split()
++ argc = len(argv)
++ if argc > 3:
++ raise TemplateSyntaxError("Tag %s takes at most 2 argument." % argv[0])
++ if argc == 1:
++ return PaginateNode()
++ if argc == 3 and argv[1] == 'using':
++ return PaginateNode(template=argv[2])
++ raise TemplateSyntaxError("Tag %s is invalid. Please check the syntax" % argv[0])
++
++
+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.
- pagination_template = context.get('pagination_template', 'pagination/default.html')
+
+ 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 = paginator.page_range
- 'pagination_template': pagination_template,
+ # 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 = 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)
+
+ to_return = {
+ 'MEDIA_URL': settings.MEDIA_URL,
+ 'STATIC_URL': getattr(settings, "STATIC_URL", None),
+ '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,
- register = template.Library()
- register.inclusion_tag(
- 'pagination/pagination.html', takes_context=True)(paginate)
+ '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:
+ to_return['getvars'] = "&%s" % getvars.urlencode()
+ else:
+ to_return['getvars'] = ''
+ return to_return
+ except (KeyError, AttributeError):
+ return {}
+
+
++register = Library()
++register.tag('paginate', do_paginate)
+register.tag('autopaginate', do_autopaginate)
-from setuptools import setup, find_packages
-
-version = '1.1.0'
-
-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.
-
+#!/usr/bin/env python
+# 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.
-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.
+from setuptools import setup, find_packages
-``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.
+version = "1.1"
-``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.
-"""
setup(
- name='django-pagination',
+ name='linaro-django-pagination',
version=version,
- description="django-pagination",
- long_description=LONG_DESCRIPTION,
- classifiers=[
- "Programming Language :: Python",
- "Topic :: Software Development :: Libraries :: Python Modules",
- "Framework :: Django",
- "Environment :: Web Environment",
- ],
+ author='Zygmunt Krynicki',
+ author_email='zygmunt.krynicki@linaro.org',
+ description="linaro-django-pagination",
+ long_description=open("README").read(),
keywords='pagination,django',
- author='Eric Florenzano',
- author_email='floguy@gmail.com',
- url='http://django-pagination.googlecode.com/',
+ url='https://github.com/zyga/django-pagination',
+ test_suite='linaro_django_pagination.test_project.tests.run_tests',
license='BSD',
packages=find_packages(),
+ classifiers=[
+ "Development Status :: 4 - Beta",
+ "Environment :: Web Environment",
+ "Framework :: Django",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: BSD License",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 2.6",
+ "Programming Language :: Python :: 2.7",
+ ],
+ install_requires=[
+ 'django >= 1.2',
+ ],
+ tests_require=[
+ 'django-testproject >= 0.1',
+ ],
include_package_data=True,
- zip_safe=False,
)