From ac8f4b08e7736bd54cebf78c527eaa6c4c26988b Mon Sep 17 00:00:00 2001 From: Aurélien Bompard Date: Nov 16 2017 16:57:58 +0000 Subject: [PATCH 1/2] Move the halp widget's pagination function to a shared lib --- diff --git a/hubs/tests/utils/test_pagination.py b/hubs/tests/utils/test_pagination.py new file mode 100644 index 0000000..acad042 --- /dev/null +++ b/hubs/tests/utils/test_pagination.py @@ -0,0 +1,99 @@ +from __future__ import unicode_literals, absolute_import + +from hubs.app import app +from hubs.utils.pagination import paginate # circular +from hubs.tests import APPTest + + +class PaginateTestCase(APPTest): + + def setUp(self): + super(PaginateTestCase, self).setUp() + self.paginate = paginate + self.values = ["A", "B", "C", "D"] + + def test_base(self): + with app.test_request_context('/'): + result = self.paginate(self.values, 3) + self.assertListEqual(result[0], self.values[:3]) + self.assertDictEqual(result[1], { + 'has_next': True, + 'has_prev': False, + 'nr': 1, + 'total_entries': len(self.values), + 'total_pages': 2, + }) + + def test_page_2(self): + with app.test_request_context('/?page=2'): + result = self.paginate(self.values, 3) + self.assertListEqual(result[0], self.values[3:]) + self.assertDictEqual(result[1], { + 'has_next': False, + 'has_prev': True, + 'nr': 2, + 'total_entries': len(self.values), + 'total_pages': 2, + }) + + def test_page_0(self): + with app.test_request_context('/?page=0'): + result = self.paginate(self.values, 3) + self.assertListEqual(result[0], self.values[:3]) + self.assertDictEqual(result[1], { + 'has_next': True, + 'has_prev': False, + 'nr': 1, + 'total_entries': len(self.values), + 'total_pages': 2, + }) + + def test_invalid_page(self): + with app.test_request_context('/?page=blah'): + result = self.paginate(self.values, 3) + self.assertListEqual(result[0], self.values[:3]) + self.assertDictEqual(result[1], { + 'has_next': True, + 'has_prev': False, + 'nr': 1, + 'total_entries': len(self.values), + 'total_pages': 2, + }) + + def test_page_too_high(self): + with app.test_request_context('/?page=3'): + result = self.paginate(self.values, 3) + self.assertListEqual(result[0], self.values[3:]) + self.assertDictEqual(result[1], { + 'has_next': False, + 'has_prev': True, + 'nr': 2, + 'total_entries': len(self.values), + 'total_pages': 2, + }) + + def test_single_page(self): + values = self.values[:3] + with app.test_request_context('/'): + result = self.paginate(values, 3) + self.assertListEqual(result[0], values) + self.assertDictEqual(result[1], { + 'has_next': False, + 'has_prev': False, + 'nr': 1, + 'total_entries': len(values), + 'total_pages': 1, + }) + + def test_no_value(self): + values = [] + with app.test_request_context('/'): + result = self.paginate(values, 3) + self.assertListEqual(result[0], []) + self.assertDictEqual(result[1], { + 'has_next': False, + 'has_prev': False, + 'nr': 1, + 'total_entries': 0, + 'total_pages': 1, + }) diff --git a/hubs/tests/widgets/test_halp.py b/hubs/tests/widgets/test_halp.py index 0dc8341..72db810 100644 --- a/hubs/tests/widgets/test_halp.py +++ b/hubs/tests/widgets/test_halp.py @@ -390,98 +390,3 @@ class HalpFunctionsTestCase(WidgetTest): } result = self.func.should_invalidate(msg) self.assertTrue(result) - - -class PaginateTestCase(WidgetTest): - - def setUp(self): - super(PaginateTestCase, self).setUp() - from hubs.widgets.halp.utils import paginate # circular - self.paginate = paginate - self.values = ["A", "B", "C", "D"] - - def test_base(self): - with app.test_request_context('/'): - result = self.paginate(self.values, 3) - self.assertListEqual(result[0], self.values[:3]) - self.assertDictEqual(result[1], { - 'has_next': True, - 'has_prev': False, - 'nr': 1, - 'total_entries': len(self.values), - 'total_pages': 2, - }) - - def test_page_2(self): - with app.test_request_context('/?page=2'): - result = self.paginate(self.values, 3) - self.assertListEqual(result[0], self.values[3:]) - self.assertDictEqual(result[1], { - 'has_next': False, - 'has_prev': True, - 'nr': 2, - 'total_entries': len(self.values), - 'total_pages': 2, - }) - - def test_page_0(self): - with app.test_request_context('/?page=0'): - result = self.paginate(self.values, 3) - self.assertListEqual(result[0], self.values[:3]) - self.assertDictEqual(result[1], { - 'has_next': True, - 'has_prev': False, - 'nr': 1, - 'total_entries': len(self.values), - 'total_pages': 2, - }) - - def test_invalid_page(self): - with app.test_request_context('/?page=blah'): - result = self.paginate(self.values, 3) - self.assertListEqual(result[0], self.values[:3]) - self.assertDictEqual(result[1], { - 'has_next': True, - 'has_prev': False, - 'nr': 1, - 'total_entries': len(self.values), - 'total_pages': 2, - }) - - def test_page_too_high(self): - with app.test_request_context('/?page=3'): - result = self.paginate(self.values, 3) - self.assertListEqual(result[0], self.values[3:]) - self.assertDictEqual(result[1], { - 'has_next': False, - 'has_prev': True, - 'nr': 2, - 'total_entries': len(self.values), - 'total_pages': 2, - }) - - def test_single_page(self): - values = self.values[:3] - with app.test_request_context('/'): - result = self.paginate(values, 3) - self.assertListEqual(result[0], values) - self.assertDictEqual(result[1], { - 'has_next': False, - 'has_prev': False, - 'nr': 1, - 'total_entries': len(values), - 'total_pages': 1, - }) - - def test_no_value(self): - values = [] - with app.test_request_context('/'): - result = self.paginate(values, 3) - self.assertListEqual(result[0], []) - self.assertDictEqual(result[1], { - 'has_next': False, - 'has_prev': False, - 'nr': 1, - 'total_entries': 0, - 'total_pages': 1, - }) diff --git a/hubs/utils/pagination.py b/hubs/utils/pagination.py new file mode 100644 index 0000000..f6a0b79 --- /dev/null +++ b/hubs/utils/pagination.py @@ -0,0 +1,60 @@ +from __future__ import unicode_literals + +import flask + + +def paginate(values, per_page): + """Paginate the values. + + The requested page will be extracted from the ``page`` query string + element. + + The returned value will be a tuple with two elements: + + - the paginated values + - the page information + + The page information is a dictionary with the following keys: + + - ``nr``: the page number + - ``has_prev``: ``True`` if there is a previous page, ``False`` otherwise + - ``has_next``: ``True`` if there is a following page, ``False`` otherwise + - ``total_entries``: the total number of entries in the ``values`` argument + - ``total_pages``: the total number of pages + + Args: + values (list or query): The list or SQLAlchemy query object to + paginate. + per_page (int): the number of elements per page. + + Returns: + tuple: a tuple containing the paginated values and the page + information. + """ + try: + total = values.count() + except TypeError: + total = len(values) + try: + page = flask.request.values.get("page", 1, int) + except ValueError: + page = 1 + last_page = int(total / per_page) + if total > last_page * per_page: + last_page += 1 + if last_page < 1: + last_page = 1 + if page < 1: + page = 1 + if page > last_page: + page = last_page + start = (page - 1) * per_page + end = start + per_page + page_data = { + "nr": page, + "has_prev": page > 1, + "has_next": page < last_page, + "total_entries": total, + "total_pages": last_page, + } + return values[start:end], page_data diff --git a/hubs/widgets/halp/utils.py b/hubs/widgets/halp/utils.py index 4d4c210..7f29e0d 100644 --- a/hubs/widgets/halp/utils.py +++ b/hubs/widgets/halp/utils.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals -import flask from hubs.models import Hub, HubConfig @@ -21,60 +20,3 @@ def find_hubs_for_msg(msg): HubConfig.chat_channel == msg["channel"] ).values(Hub.name) ] - - -def paginate(values, per_page): - """Paginate the values. - - The requested page will be extracted from the ``page`` query string - element. - - The returned value will be a tuple with two elements: - - - the paginated values - - the page information - - The page information is a dictionary with the following keys: - - - ``nr``: the page number - - ``has_prev``: ``True`` if there is a previous page, ``False`` otherwise - - ``has_next``: ``True`` if there is a following page, ``False`` otherwise - - ``total_entries``: the total number of entries in the ``values`` argument - - ``total_pages``: the total number of pages - - Args: - values (list or query): The list or SQLAlchemy query object to - paginate. - per_page (int): the number of elements per page. - - Returns: - tuple: a tuple containing the paginated values and the page - information. - """ - try: - total = values.count() - except TypeError: - total = len(values) - try: - page = flask.request.values.get("page", 1, int) - except ValueError: - page = 1 - last_page = int(total / per_page) - if total > last_page * per_page: - last_page += 1 - if last_page < 1: - last_page = 1 - if page < 1: - page = 1 - if page > last_page: - page = last_page - start = (page - 1) * per_page - end = start + per_page - page_data = { - "nr": page, - "has_prev": page > 1, - "has_next": page < last_page, - "total_entries": total, - "total_pages": last_page, - } - return values[start:end], page_data diff --git a/hubs/widgets/halp/views.py b/hubs/widgets/halp/views.py index d8a0c41..2ef464c 100644 --- a/hubs/widgets/halp/views.py +++ b/hubs/widgets/halp/views.py @@ -6,9 +6,10 @@ import time import flask from hubs.models import Hub +from hubs.utils.pagination import paginate from hubs.widgets.view import WidgetView from .functions import GetRequests -from .utils import find_hubs_for_msg, paginate +from .utils import find_hubs_for_msg try: from flask.signals import before_render_template From 6681ed7b7e618ebd396f8120fbb542f9c36e2078 Mon Sep 17 00:00:00 2001 From: Aurélien Bompard Date: Nov 16 2017 16:57:58 +0000 Subject: [PATCH 2/2] Share the Pagination Javascript component --- diff --git a/hubs/static/client/app/components/Pagination.js b/hubs/static/client/app/components/Pagination.js new file mode 100644 index 0000000..d4fd045 --- /dev/null +++ b/hubs/static/client/app/components/Pagination.js @@ -0,0 +1,79 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { defineMessages, FormattedMessage } from 'react-intl'; + + +const messages = defineMessages({ + pager_previous: { + id: "hubs.widgets.halp.pager.previous", + defaultMessage: "Previous" + }, + pager_next: { + id: "hubs.widgets.halp.pager.next", + defaultMessage: "Next" + }, + pager_status: { + id: "hubs.widgets.halp.pager.status", + defaultMessage: "Page {page} / {total}" + }, +}); + + +export default class Pagination extends React.Component { + + constructor(props) { + super(props); + this.pageChange = this.pageChange.bind(this); + this.onPagePrevious = this.pageChange.bind(this, -1); + this.onPageNext = this.pageChange.bind(this, 1); + } + + pageChange(inc, e) { + e.preventDefault(); + const requestedPage = this.props.page.nr + inc; + this.props.onPageChange(requestedPage); + } + + render() { + return ( + + ); + } +} +Pagination.propTypes = { + className: PropTypes.string, + previousText: PropTypes.node, + nextText: PropTypes.node, +} +Pagination.defaultProps = { + className: "", + previousText: ( + + ), + nextText: ( + + ), +} diff --git a/hubs/static/client/app/widgets/halp/ModalAllRequests.js b/hubs/static/client/app/widgets/halp/ModalAllRequests.js index e129860..fde1719 100644 --- a/hubs/static/client/app/widgets/halp/ModalAllRequests.js +++ b/hubs/static/client/app/widgets/halp/ModalAllRequests.js @@ -3,6 +3,7 @@ import { defineMessages, FormattedMessage } from 'react-intl'; import CompletionInput from '../../components/CompletionInput'; import Spinner from "../../components/Spinner"; import Modal from '../../components/Modal'; +import Pagination from "../../components/Pagination"; import Request from './Request'; @@ -60,7 +61,7 @@ export default class ModalAllRequests extends React.Component { }; this.onSubmit = this.onSubmit.bind(this); this.loadFromServer = this.loadFromServer.bind(this); - this.onPageChange = this.onPageChange.bind(this); + this.changePage = this.changePage.bind(this); this.handleChange = this.handleChange.bind(this); } @@ -81,10 +82,8 @@ export default class ModalAllRequests extends React.Component { this.loadFromServer(); } - onPageChange(e) { - const inc = parseInt(e.target.value); - const requestedPage = this.state.page.nr + inc; - this.loadFromServer(requestedPage); + changePage(pageNr) { + this.loadFromServer(pageNr); } loadFromServer(requestedPage) { @@ -128,27 +127,17 @@ export default class ModalAllRequests extends React.Component { footer = null; } else { footer = ( - + + } + nextText={ + + } + /> ); }