From 4f04eedff0178a927430194cf4c2c298cafb7a18 Mon Sep 17 00:00:00 2001 From: leahculver Date: Thu, 12 Feb 2009 05:00:39 +0800 Subject: [PATCH] InfinitePaginator (from Pownce) with tests! The InfinitePaginator does not count() the object_list and can be used for object_lists that are missing this method or for performance reasons. Signed-off-by: Eric Florenzano --- pagination/paginator.py | 95 +++++++++++++++++++++++++++++++++++++++++ pagination/tests.py | 28 ++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 pagination/paginator.py diff --git a/pagination/paginator.py b/pagination/paginator.py new file mode 100644 index 0000000..9a6ec83 --- /dev/null +++ b/pagination/paginator.py @@ -0,0 +1,95 @@ +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. + + Class name is pronounced verbally in a deep tone. + ''' + + 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 \ No newline at end of file diff --git a/pagination/tests.py b/pagination/tests.py index 647bbfd..c0a4a6c 100644 --- a/pagination/tests.py +++ b/pagination/tests.py @@ -58,4 +58,32 @@ u'\\n\\n