Repository: ericflo/django-pagination Branch: master Commit: d1a8f0f9fb21 Files: 33 Total size: 39.4 KB Directory structure: gitextract_lcedyun1/ ├── .gitignore ├── CONTRIBUTORS.txt ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── docs/ │ ├── index.txt │ ├── install.txt │ └── usage.txt ├── pagination/ │ ├── __init__.py │ ├── locale/ │ │ ├── cs/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── django.mo │ │ │ └── django.po │ │ ├── de/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── django.mo │ │ │ └── django.po │ │ ├── es/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── django.mo │ │ │ └── django.po │ │ ├── fr/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── django.mo │ │ │ └── django.po │ │ ├── pl/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── django.mo │ │ │ └── django.po │ │ ├── pt/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── django.mo │ │ │ └── django.po │ │ └── pt_BR/ │ │ └── LC_MESSAGES/ │ │ ├── django.mo │ │ └── django.po │ ├── middleware.py │ ├── models.py │ ├── paginator.py │ ├── templates/ │ │ └── pagination/ │ │ └── pagination.html │ ├── templatetags/ │ │ ├── __init__.py │ │ └── pagination_tags.py │ └── tests.py ├── setup.py └── tests/ ├── runtests.py └── settings.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.pyc dist *.egg-info ================================================ FILE: CONTRIBUTORS.txt ================================================ Eric Florenzano Martin Mahner James Tauber ================================================ FILE: LICENSE.txt ================================================ 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. ================================================ FILE: MANIFEST.in ================================================ include LICENSE.txt include CONTRIBUTORS.txt recursive-include docs * recursive-include pagination/templates/pagination *.html recursive-include pagination/locale *.mo *.po ================================================ FILE: README.md ================================================ This project is no longer maintained. A maintained fork of this project is available at: https://github.com/pydanny/dj-pagination ================================================ FILE: docs/index.txt ================================================ ################# django-pagination ################# Django-pagination is a set of utilities for creating robust pagination tools throughout a django application. Contents: .. toctree:: install.txt usage.txt ================================================ FILE: docs/install.txt ================================================ Installing the latest development version of 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 Now, link the inner ``pagination`` project to your Python path: sudo ln -s `pwd`/pagination SITE_PACKAGES_DIR/pagination If you don't know the location of your site packages directory, this hack might do the trick for you: sudo ln -s `pwd`/pagination `python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()"`/pagination Now it's installed! Please see usage.txt for information on how to use this application in your projects. Installing via setup.py ----------------------- Included with this application is a file named ``setup.py``. It's possible to use this file to install this application to your system, by invoking the following command: sudo python setup.py install Once that's done, you should be able to begin using django-pagination at will. Installing via setuptools ------------------------- If you have setuptools_ installed, you can simply run the following command to install django-pagination: sudo easy_install django-pagination .. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools ================================================ FILE: docs/usage.txt ================================================ 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. A Note About Uploads -------------------- It is important, when using 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 as late as possible in 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. ================================================ FILE: pagination/__init__.py ================================================ ================================================ FILE: pagination/locale/cs/LC_MESSAGES/django.po ================================================ # 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: 2009-03-16 16:26+0100\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 "předchozí" #: templates/pagination/pagination.html:21 #: templates/pagination/pagination.html:23 msgid "next" msgstr "další" ================================================ FILE: pagination/locale/de/LC_MESSAGES/django.po ================================================ # 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: 2009-03-16 16:26+0100\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 "Zurück" #: templates/pagination/pagination.html:21 #: templates/pagination/pagination.html:23 msgid "next" msgstr "Weiter" ================================================ FILE: pagination/locale/es/LC_MESSAGES/django.po ================================================ # django-pagination Spanish translation # 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: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-09-16 16:32+0200\n" "PO-Revision-Date: 2010-09-16 16:38+0200\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" ================================================ FILE: pagination/locale/fr/LC_MESSAGES/django.po ================================================ # django-pagination French translation. # Copyright (C) 2008, Julien Demoor # This file is distributed under the same license as the django-pagination package. # Julien Demoor , 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 \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 "précédente" #: templates/pagination/pagination.html:21 #: templates/pagination/pagination.html:23 msgid "next" msgstr "suivante" ================================================ FILE: pagination/locale/pl/LC_MESSAGES/django.po ================================================ # 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 , 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 \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" ================================================ FILE: pagination/locale/pt/LC_MESSAGES/django.po ================================================ # 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 \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" ================================================ FILE: pagination/locale/pt_BR/LC_MESSAGES/django.po ================================================ # django-pagination in Portuguese (Brazil). # Copyright (C) 2011, Tiago Samaha Cordeiro # This file is distributed under the same license as the django-pagination package. # Tiago Samaha Cordeiro , 2011. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: 1.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-01-16 22:06-0200\n" "PO-Revision-Date: 2011-01-16 22:15-3\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" ================================================ FILE: pagination/middleware.py ================================================ 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 ================================================ FILE: pagination/models.py ================================================ ================================================ FILE: pagination/paginator.py ================================================ 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: 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 ================================================ FILE: pagination/templates/pagination/pagination.html ================================================ {% if is_paginated %} {% load i18n %} {% endif %} ================================================ FILE: pagination/templatetags/__init__.py ================================================ ================================================ FILE: pagination/templatetags/pagination_tags.py ================================================ 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, hashtag=''): """ 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 # 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 # 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 = { 'MEDIA_URL': settings.MEDIA_URL, 'request': context['request'], 'pages': pages, 'records': records, 'page_obj': page_obj, 'paginator': paginator, 'hashtag': hashtag, '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) ================================================ FILE: pagination/tests.py ================================================ """ >>> from django.core.paginator import Paginator >>> from pagination.templatetags.pagination_tags import paginate >>> from django.template import Template, Context >>> p = Paginator(range(15), 2) >>> pg = paginate({'paginator': p, 'page_obj': p.page(1)}) >>> pg['pages'] [1, 2, 3, 4, 5, 6, 7, 8] >>> pg['records']['first'] 1 >>> pg['records']['last'] 2 >>> p = Paginator(range(15), 2) >>> pg = paginate({'paginator': p, 'page_obj': p.page(8)}) >>> pg['pages'] [1, 2, 3, 4, 5, 6, 7, 8] >>> pg['records']['first'] 15 >>> pg['records']['last'] 15 >>> 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) >>> pg = paginate({'paginator': p, 'page_obj': p.page(1)}) >>> pg['pages'] [1, 2, 3, 4, None, 7, 8, 9, 10] >>> pg['records']['first'] 1 >>> pg['records']['last'] 2 >>> p = Paginator(range(21), 2, 1) >>> pg = paginate({'paginator': p, 'page_obj': p.page(10)}) >>> pg['pages'] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> pg['records']['first'] 19 >>> pg['records']['last'] 21 >>> 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