"""Ephemeral models used to represent a page and a list of pages.""" from __future__ import unicode_literals from django.template import loader from django.utils.encoding import iri_to_uri from simple_pagination import settings from simple_pagination import utils # Page templates cache. _template_cache = {} class EndlessPage(utils.UnicodeMixin): """A page link representation. Interesting attributes: - *self.number*: the page number; - *self.label*: the label of the link (usually the page number as string); - *self.url*: the url of the page (starting with "?"); - *self.path*: the path of the page; - *self.is_current*: return True if page is the current page displayed; - *self.is_first*: return True if page is the first page; - *self.is_last*: return True if page is the last page. """ def __init__(self, request, number, current_number, *args, **kwargs): total_number = kwargs.get('total_number') querystring_key = kwargs.get('querystring_key', 'page') label = kwargs.get('label', None) default_number = kwargs.get('default_number', 1) override_path = kwargs.get('override_path', None) self._request = request self.number = number self.label = utils.text(number) if label is None else label self.querystring_key = querystring_key self.is_current = number == current_number self.is_first = number == 1 self.is_last = number == total_number self.url = utils.get_querystring_for_page( request, number, self.querystring_key, default_number=default_number) path = iri_to_uri(override_path or request.path) self.path = '{0}{1}'.format(path, self.url) def __unicode__(self): """Render the page as a link.""" context = { 'add_nofollow': False, 'page': self, 'querystring_key': self.querystring_key, } if self.is_current: template_name = 'simple/current_link.html' else: template_name = 'simple/page_link.html' template = _template_cache.setdefault( template_name, loader.get_template(template_name)) return template.render(context) class PageList(utils.UnicodeMixin): """A sequence of endless pages.""" def __init__(self, request, page, querystring_key, **kwargs): default_number = kwargs.get('default_number', None) override_path = kwargs.get('override_path', None) self._request = request self._page = page if default_number is None: self._default_number = 1 else: self._default_number = int(default_number) self._querystring_key = querystring_key self._override_path = override_path def _endless_page(self, number, label=None): """Factory function that returns a *EndlessPage* instance. This method works just like a partial constructor. """ return EndlessPage( self._request, number, self._page.number, len(self), self._querystring_key, label=label, default_number=self._default_number, override_path=self._override_path, ) def __getitem__(self, value): # The type conversion is required here because in templates Django # performs a dictionary lookup before the attribute lokups # (when a dot is encountered). try: value = int(value) except (TypeError, ValueError): # A TypeError says to django to continue with an attribute lookup. raise TypeError if 1 <= value <= len(self): return self._endless_page(value) raise IndexError('page list index out of range') def __len__(self): """The length of the sequence is the total number of pages.""" return self._page.paginator.num_pages def __iter__(self): """Iterate over all the endless pages (from first to last).""" for i in range(len(self)): yield self[i + 1] def __unicode__(self): """Return a rendered Digg-style pagination (by default). The callable *settings.PAGE_LIST_CALLABLE* can be used to customize how the pages are displayed. The callable takes the current page number and the total number of pages, and must return a sequence of page numbers that will be displayed. The sequence can contain other values: - *'previous'*: will display the previous page in that position; - *'next'*: will display the next page in that position; - *'first'*: will display the first page as an arrow; - *'last'*: will display the last page as an arrow; - *None*: a separator will be displayed in that position. Here is an example of custom calable that displays the previous page, then the first page, then a separator, then the current page, and finally the last page:: def get_page_numbers(current_page, num_pages): return ('previous', 1, None, current_page, 'last') If *settings.PAGE_LIST_CALLABLE* is None an internal callable is used, generating a Digg-style pagination. The value of *settings.PAGE_LIST_CALLABLE* can also be a dotted path to a callable. """ if len(self) > 1: pages_callable = utils.get_page_numbers pages = [] for item in pages_callable(self._page.number, len(self)): if item is None: pages.append(None) elif item == 'previous': pages.append(self.previous()) elif item == 'next': pages.append(self.next()) elif item == 'first': pages.append(self.first_as_arrow()) elif item == 'last': pages.append(self.last_as_arrow()) else: pages.append(self[item]) return loader.render_to_string('simple/show_pages.html', {'pages': pages}) return '' def current(self): """Return the current page.""" return self._endless_page(self._page.number) def current_start_index(self): """Return the 1-based index of the first item on the current page.""" return self._page.start_index() def current_end_index(self): """Return the 1-based index of the last item on the current page.""" return self._page.end_index() def total_count(self): """Return the total number of objects, across all pages.""" return self._page.paginator.count def first(self, label=None): """Return the first page.""" return self._endless_page(1, label=label) def last(self, label=None): """Return the last page.""" return self._endless_page(len(self), label=label) def first_as_arrow(self): """Return the first page as an arrow. The page label (arrow) is defined in ``settings.FIRST_LABEL``. """ return self.first(label=settings.FIRST_LABEL) def last_as_arrow(self): """Return the last page as an arrow. The page label (arrow) is defined in ``settings.LAST_LABEL``. """ return self.last(label=settings.LAST_LABEL) def previous(self): """Return the previous page. The page label is defined in ``settings.PREVIOUS_LABEL``. Return an empty string if current page is the first. """ if self._page.has_previous(): return self._endless_page( self._page.previous_page_number(), label=settings.PREVIOUS_LABEL) return '' def next(self): """Return the next page. The page label is defined in ``settings.NEXT_LABEL``. Return an empty string if current page is the last. """ if self._page.has_next(): return self._endless_page( self._page.next_page_number(), label=settings.NEXT_LABEL) return '' def paginated(self): """Return True if this page list contains more than one page.""" return len(self) > 1 class ShowItems(utils.UnicodeMixin): """A page link representation. Interesting attributes: - *self.number*: the page number; - *self.label*: the label of the link (usually the page number as string); - *self.url*: the url of the page (starting with "?"); - *self.path*: the path of the page; - *self.is_current*: return True if page is the current page displayed; - *self.is_first*: return True if page is the first page; - *self.is_last*: return True if page is the last page. """ def __init__(self, request, page, querystring_key, **kwargs): default_number = kwargs.get('default_number', None) override_path = kwargs.get('override_path', None) self._request = request self._page = page if default_number is None: self._default_number = 1 else: self._default_number = int(default_number) self._querystring_key = querystring_key self._override_path = override_path def __unicode__(self): """Render the page as a link.""" str_data = "Showing " if self._page.paginator.count == 1: str_data += str(1) str_data = str_data + " to " + str(len(self._page.object_list)) + " of " + str(len(self._page.object_list)) else: if self._page.number == 1: str_data += str(1) if self._page.paginator.per_page == str(self._page.paginator.count): str_data = str_data + " to " + str(self._page.paginator.per_page) + " of " + str(self._page.paginator.count) else: str_data = str_data + " to " + str(len(self._page.object_list)) + " of " + str(self._page.paginator.count) else: if self._page.has_next(): str_data += "".join(map(str, [ (self._page.paginator.per_page * self._page.previous_page_number()) + 1, " to ", self._page.paginator.per_page * self._page.number, " of ", self._page.paginator.count ])) else: str_data += "".join(map(str, [ self._page.paginator.per_page * self._page.previous_page_number() + 1, " to ", self._page.paginator.count, " of ", self._page.paginator.count ])) return str_data + " items"