diff options
Diffstat (limited to 'django/core/paginator.py')
-rw-r--r-- | django/core/paginator.py | 204 |
1 files changed, 145 insertions, 59 deletions
diff --git a/django/core/paginator.py b/django/core/paginator.py index 71a5479fd5..dabd20dfc0 100644 --- a/django/core/paginator.py +++ b/django/core/paginator.py @@ -1,46 +1,149 @@ class InvalidPage(Exception): pass -class ObjectPaginator(object): +class Paginator(object): + def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True): + self.object_list = object_list + self.per_page = per_page + self.orphans = orphans + self.allow_empty_first_page = allow_empty_first_page + self._num_pages = self._count = None + + def validate_number(self, number): + "Validates the given 1-based page number." + try: + number = int(number) + except ValueError: + raise InvalidPage('That page number is not an integer') + if number < 1: + raise InvalidPage('That page number is less than 1') + if number > self.num_pages: + if number == 1 and self.allow_empty_first_page: + pass + else: + raise InvalidPage('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) + bottom = (number - 1) * self.per_page + top = bottom + self.per_page + if top + self.orphans >= self.count: + top = self.count + return Page(self.object_list[bottom:top], number, self) + + def _get_count(self): + "Returns the total number of objects, across all pages." + if self._count is None: + self._count = len(self.object_list) + return self._count + count = property(_get_count) + + def _get_num_pages(self): + "Returns the total number of pages." + if self._num_pages is None: + hits = self.count - 1 - self.orphans + if hits < 1: + hits = 0 + if hits == 0 and not self.allow_empty_first_page: + self._num_pages = 0 + else: + self._num_pages = hits // self.per_page + 1 + return self._num_pages + 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. + """ + return range(1, self.num_pages + 1) + page_range = property(_get_page_range) + +class QuerySetPaginator(Paginator): """ - This class makes pagination easy. Feed it a QuerySet or list, plus the number - of objects you want on each page. Then read the hits and pages properties to - see how many pages it involves. Call get_page with a page number (starting - at 0) to get back a list of objects for that page. - - Finally, check if a page number has a next/prev page using - has_next_page(page_number) and has_previous_page(page_number). - - Use orphans to avoid small final pages. For example: - 13 records, num_per_page=10, orphans=2 --> pages==2, len(self.get_page(0))==10 - 12 records, num_per_page=10, orphans=2 --> pages==1, len(self.get_page(0))==12 + Like Paginator, but works on QuerySets. + """ + def _get_count(self): + if self._count is None: + self._count = self.object_list.count() + return self._count + count = property(_get_count) + +class Page(object): + def __init__(self, object_list, number, paginator): + self.object_list = object_list + self.number = number + self.paginator = paginator + + def __repr__(self): + return '<Page %s of %s>' % (self.number, self.paginator.num_pages) + + def has_next(self): + return self.number < self.paginator.num_pages + + def has_previous(self): + return self.number > 1 + + def has_other_pages(self): + return self.has_previous() or self.has_next() + + def next_page_number(self): + return self.number + 1 + + def previous_page_number(self): + return self.number - 1 + + def start_index(self): + """ + Returns the 1-based index of the first object on this page, + relative to total objects in the paginator. + """ + return (self.paginator.per_page * (self.number - 1)) + 1 + + def end_index(self): + """ + Returns the 1-based index of the last object on this page, + relative to total objects found (hits). + """ + if self.number == self.paginator.num_pages: + return self.paginator.count + return self.number * self.paginator.per_page + +class ObjectPaginator(Paginator): + """ + Legacy ObjectPaginator class, for backwards compatibility. + + Note that each method on this class that takes page_number expects a + zero-based page number, whereas the new API (Paginator/Page) uses one-based + page numbers. """ def __init__(self, query_set, num_per_page, orphans=0): + Paginator.__init__(self, query_set, num_per_page, orphans) + import warnings + warnings.warn("The ObjectPaginator is deprecated. Use django.core.paginator.Paginator instead.", DeprecationWarning) + + # Keep these attributes around for backwards compatibility. self.query_set = query_set self.num_per_page = num_per_page - self.orphans = orphans self._hits = self._pages = None - self._page_range = None def validate_page_number(self, page_number): try: - page_number = int(page_number) + page_number = int(page_number) + 1 except ValueError: raise InvalidPage - if page_number < 0 or page_number > self.pages - 1: - raise InvalidPage - return page_number + return self.validate_number(page_number) def get_page(self, page_number): - page_number = self.validate_page_number(page_number) - bottom = page_number * self.num_per_page - top = bottom + self.num_per_page - if top + self.orphans >= self.hits: - top = self.hits - return self.query_set[bottom:top] + try: + page_number = int(page_number) + 1 + except ValueError: + raise InvalidPage + return self.page(page_number).object_list def has_next_page(self, page_number): - "Does page $page_number have a 'next' page?" return page_number < self.pages - 1 def has_previous_page(self, page_number): @@ -52,7 +155,7 @@ class ObjectPaginator(object): relative to total objects found (hits). """ page_number = self.validate_page_number(page_number) - return (self.num_per_page * page_number) + 1 + return (self.num_per_page * (page_number - 1)) + 1 def last_on_page(self, page_number): """ @@ -60,40 +163,23 @@ class ObjectPaginator(object): relative to total objects found (hits). """ page_number = self.validate_page_number(page_number) - page_number += 1 # 1-base - if page_number == self.pages: - return self.hits + if page_number == self.num_pages: + return self.count return page_number * self.num_per_page - def _get_hits(self): - if self._hits is None: - # Try .count() or fall back to len(). + def _get_count(self): + # The old API allowed for self.object_list to be either a QuerySet or a + # list. Here, we handle both. + if self._count is None: try: - self._hits = int(self.query_set.count()) - except (AttributeError, TypeError, ValueError): - # AttributeError if query_set has no object count. - # TypeError if query_set.count() required arguments. - # ValueError if int() fails. - self._hits = len(self.query_set) - return self._hits - - def _get_pages(self): - if self._pages is None: - hits = (self.hits - 1 - self.orphans) - if hits < 1: - hits = 0 - self._pages = hits // self.num_per_page + 1 - return self._pages - - def _get_page_range(self): - """ - Returns a 1-based range of pages for iterating through within - a template for loop. - """ - if self._page_range is None: - self._page_range = range(1, self.pages + 1) - return self._page_range + self._count = self.object_list.count() + except AttributeError: + self._count = len(self.object_list) + return self._count + count = property(_get_count) - hits = property(_get_hits) - pages = property(_get_pages) - page_range = property(_get_page_range) + # The old API called it "hits" instead of "count". + hits = count + + # The old API called it "pages" instead of "num_pages". + pages = Paginator.num_pages |