import typing import flask from flask_paginate import Pagination from sqlalchemy.orm import Query from werkzeug.utils import cached_property class QueryPagination(Pagination): """ Customized flask-pagination paginator. Updated default params and added some shortcuts for qs logic. """ PER_PAGE_OPTIONS = (10, 20, 30) def __init__( self, inner_window: int = 2, outer_window: int = 0, display_msg: str = "Showing {start} - {end} of {total} results", # noqa prev_label: str = '', next_label: str = '', css_framework: str = "bootstrap3", **kwargs, ): self.per_page_options = ( kwargs.pop("per_page_options", None) or self.PER_PAGE_OPTIONS ) total = kwargs.pop("total", 0) self.query = kwargs.pop("query", None) if self.query is not None: try: total = self.query.count() except TypeError: total = len(self.query) found = total if kwargs.get("search") else 0 else: found = 0 super().__init__( found=found, inner_window=inner_window, outer_window=outer_window, display_msg=display_msg, prev_label=prev_label, next_label=next_label, total=total, css_framework=css_framework, **kwargs, ) @property def items(self) -> typing.Iterable: if self.query: return self.query.paginate(self.page, self.per_page).items return [] def __eq__(self, other): try: return self.items == other.items except AttributeError: return False @property def per_page_args(self) -> dict: all_args = {**flask.request.view_args, **flask.request.args} return { k: v for k, v in all_args.items() if k not in (self.page_parameter, self.per_page_parameter) } @property def go_to_page_params(self) -> dict: return { k: v for k, v in flask.request.args.items() if k != self.page_parameter } class APIQueryPagination: """ Limit/offset paginator. Paginates query with explicitly passed limit/offset params or HTTP GET retrieved params. """ DEFAULT_LIMIT = 100 MAX_LIMIT = 1000 def __init__( self, query: Query, limit: typing.Optional[int] = None, offset: typing.Optional[int] = None, ): self._query = query self.limit = limit or flask.request.args.get( "limit", default=self.DEFAULT_LIMIT, type=int ) self.limit = self.limit if self.limit >= 0 else self.DEFAULT_LIMIT self.limit = min(self.limit, self.MAX_LIMIT) self.offset = offset or flask.request.args.get( "offset", default=0, type=int ) self.offset = max(self.offset, 0) def __eq__(self, other): try: return self.items == other.items except AttributeError: return False @property def items(self) -> typing.Iterable: return self.query.all() @property def query(self) -> Query: return self._query.limit(self.limit).offset(self.offset) @cached_property def count(self) -> int: return self._query.count() @property def previous(self) -> str: if self.offset > 0: args = flask.request.args.copy() args["offset"] = max(self.offset - self.limit, 0) return flask.url_for( flask.request.endpoint, **flask.request.view_args, **args ) @property def next(self) -> str: if self.offset + 1 < self.count and self.count > self.limit: args = flask.request.args.copy() args["offset"] = self.offset + self.limit return flask.url_for( flask.request.endpoint, **flask.request.view_args, **args )