123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- # -*- coding: utf-8 -*-
- """
- flaskbb.utils.query
- ~~~~~~~~~~~~~~~~~~~
- This module holds the query classes that are used by flaskbb
- :copyright: (c) 2013 by the FlaskBB Team.
- :license: BSD, see LICENSE for more details.
- """
- from math import ceil
- from flask import abort
- from flask.ext.sqlalchemy import BaseQuery
- class Pagination(object):
- """Internal helper class returned by :meth:`BaseQuery.paginate`. You
- can also construct it from any other SQLAlchemy query object if you are
- working with other libraries. Additionally it is possible to pass `None`
- as query object in which case the :meth:`prev` and :meth:`next` will
- no longer work.
- """
- def __init__(self, query, page, per_page, total, items, add_none):
- #: the unlimited query object that was used to create this
- #: pagination object.
- self.query = query
- #: the current page number (1 indexed)
- self.page = page
- #: the number of items to be displayed on a page.
- self.per_page = per_page
- #: the total number of items matching the query
- self.total = total
- #: the items for the current page
- if add_none:
- self.items = [(item, None) for item in items]
- else:
- self.items = items
- @property
- def pages(self):
- """The total number of pages"""
- if self.per_page == 0:
- pages = 0
- else:
- pages = int(ceil(self.total / float(self.per_page)))
- return pages
- def prev(self, error_out=False):
- """Returns a :class:`Pagination` object for the previous page."""
- assert self.query is not None, 'a query object is required ' \
- 'for this method to work'
- return self.query.paginate(self.page - 1, self.per_page, error_out,
- add_none)
- @property
- def prev_num(self):
- """Number of the previous page."""
- return self.page - 1
- @property
- def has_prev(self):
- """True if a previous page exists"""
- return self.page > 1
- def next(self, error_out=False):
- """Returns a :class:`Pagination` object for the next page."""
- assert self.query is not None, 'a query object is required ' \
- 'for this method to work'
- return self.query.paginate(self.page + 1, self.per_page, error_out,
- add_none)
- @property
- def has_next(self):
- """True if a next page exists."""
- return self.page < self.pages
- @property
- def next_num(self):
- """Number of the next page"""
- return self.page + 1
- def iter_pages(self, left_edge=2, left_current=2,
- right_current=5, right_edge=2):
- """Iterates over the page numbers in the pagination. The four
- parameters control the thresholds how many numbers should be produced
- from the sides. Skipped page numbers are represented as `None`.
- This is how you could render such a pagination in the templates:
- .. sourcecode:: html+jinja
- {% macro render_pagination(pagination, endpoint) %}
- <div class=pagination>
- {%- for page in pagination.iter_pages() %}
- {% if page %}
- {% if page != pagination.page %}
- <a href="{{ url_for(endpoint, page=page) }}">{{ page }}</a>
- {% else %}
- <strong>{{ page }}</strong>
- {% endif %}
- {% else %}
- <span class=ellipsis>…</span>
- {% endif %}
- {%- endfor %}
- </div>
- {% endmacro %}
- """
- last = 0
- for num in xrange(1, self.pages + 1):
- if num <= left_edge or \
- (num > self.page - left_current - 1 and
- num < self.page + right_current) or \
- num > self.pages - right_edge:
- if last + 1 != num:
- yield None
- yield num
- last = num
- class TopicQuery(BaseQuery):
- def paginate(self, page, per_page=20, error_out=True, add_none=False):
- """Returns `per_page` items from page `page`. By default it will
- abort with 404 if no items were found and the page was larger than
- 1. This behavor can be disabled by setting `error_out` to `False`.
- Returns an :class:`Pagination` object.
- """
- if error_out and page < 1:
- abort(404)
- items = self.limit(per_page).offset((page - 1) * per_page).all()
- if not items and page != 1 and error_out:
- abort(404)
- # No need to count if we're on the first page and there are fewer
- # items than we expected.
- if page == 1 and len(items) < per_page:
- total = len(items)
- else:
- total = self.order_by(None).count()
- return Pagination(self, page, per_page, total, items, add_none)
|