From 6bfb6c2b5579a57906316be9ef0abda21042c173 Mon Sep 17 00:00:00 2001 From: nathanbegbie Date: Wed, 29 Apr 2015 11:04:21 +0200 Subject: [PATCH 1/6] Copied tests for paginator into utils from unicore-cms Conflicts: springboard/tests/test_utils.py --- springboard/tests/test_utils.py | 83 ++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/springboard/tests/test_utils.py b/springboard/tests/test_utils.py index 35faa83..72cc3c7 100644 --- a/springboard/tests/test_utils.py +++ b/springboard/tests/test_utils.py @@ -1,6 +1,7 @@ from unittest import TestCase -from springboard.utils import parse_repo_name, config_list, config_dict +from springboard.utils import ( + parse_repo_name, config_list, config_dict, Paginator) class TestUtils(TestCase): @@ -36,3 +37,83 @@ def test_config_dict(self): 'elastic': 'search', }, config_dict('foo=bar\ngum=tree\nelastic=search\n')) self.assertEqual({}, config_dict('')) + + +class TestPaginator(TestCase): + + def test_first_page(self): + paginator = Paginator(range(100), 0) + self.assertTrue(paginator.has_next_page()) + self.assertFalse(paginator.has_previous_page()) + self.assertEqual(paginator.total_pages(), 10) + self.assertEqual(paginator.page_numbers(), [0, 1, 2, 3, 4]) + self.assertFalse(paginator.needs_start_ellipsis()) + self.assertTrue(paginator.needs_end_ellipsis()) + self.assertEqual(paginator.page_numbers_left(), []) + self.assertEqual(paginator.page_numbers_right(), [1, 2, 3, 4]) + + def test_last_page(self): + paginator = Paginator(range(100), 9) + self.assertFalse(paginator.has_next_page()) + self.assertTrue(paginator.has_previous_page()) + self.assertEqual(paginator.total_pages(), 10) + self.assertEqual(paginator.page_numbers(), [5, 6, 7, 8, 9]) + self.assertTrue(paginator.needs_start_ellipsis()) + self.assertFalse(paginator.needs_end_ellipsis()) + self.assertEqual(paginator.page_numbers_left(), [5, 6, 7, 8]) + self.assertEqual(paginator.page_numbers_right(), []) + + def test_middle_page(self): + paginator = Paginator(range(100), 4) + self.assertTrue(paginator.has_next_page()) + self.assertTrue(paginator.has_previous_page()) + self.assertEqual(paginator.total_pages(), 10) + self.assertEqual(paginator.page_numbers(), [2, 3, 4, 5, 6]) + self.assertTrue(paginator.needs_start_ellipsis()) + self.assertTrue(paginator.needs_end_ellipsis()) + self.assertEqual(paginator.page_numbers_left(), [2, 3]) + self.assertEqual(paginator.page_numbers_right(), [5, 6]) + + def test_show_start(self): + paginator = Paginator(range(100), 3) + self.assertTrue(paginator.show_start()) + self.assertFalse(paginator.needs_start_ellipsis()) + self.assertEqual(paginator.page_numbers_left(), [1, 2]) + self.assertEqual(paginator.page_numbers_right(), [4, 5]) + + def test_show_end(self): + paginator = Paginator(range(100), 7) + self.assertTrue(paginator.show_start()) + self.assertTrue(paginator.needs_start_ellipsis()) + self.assertEqual(paginator.page_numbers(), [5, 6, 7, 8, 9]) + self.assertEqual(paginator.page_numbers_left(), [5, 6]) + self.assertEqual(paginator.page_numbers_right(), [8, 9]) + self.assertFalse(paginator.show_end()) + self.assertFalse(paginator.needs_end_ellipsis()) + + def test_show_end_not_ellipsis(self): + paginator = Paginator(range(100), 6) + self.assertTrue(paginator.show_start()) + self.assertTrue(paginator.needs_start_ellipsis()) + self.assertEqual(paginator.page_numbers(), [4, 5, 6, 7, 8]) + self.assertEqual(paginator.page_numbers_left(), [4, 5]) + self.assertEqual(paginator.page_numbers_right(), [7, 8]) + self.assertTrue(paginator.show_end()) + self.assertFalse(paginator.needs_end_ellipsis()) + + def test_small_result_set(self): + paginator = Paginator(range(39), 0) + self.assertFalse(paginator.show_start()) + self.assertFalse(paginator.needs_start_ellipsis()) + self.assertFalse(paginator.show_end()) + self.assertFalse(paginator.needs_end_ellipsis()) + self.assertEqual(paginator.page_numbers_left(), []) + self.assertEqual(paginator.page_numbers_right(), [1, 2, 3]) + + def test_large_end_result_set(self): + paginator = Paginator(range(133), 12) + self.assertEqual(paginator.page_numbers(), [9, 10, 11, 12, 13]) + self.assertEqual(paginator.page_numbers_left(), [9, 10, 11]) + self.assertEqual(paginator.page_numbers_right(), [13]) + self.assertFalse(paginator.show_end()) + self.assertFalse(paginator.needs_end_ellipsis()) From d548ebff1d78c05c4abc8f1f68cd531c2b391c69 Mon Sep 17 00:00:00 2001 From: Rizmari Versfeld Date: Mon, 1 Jun 2015 11:04:05 +0200 Subject: [PATCH 2/6] port Paginator classes from unicore-cms --- springboard/utils.py | 108 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/springboard/utils.py b/springboard/utils.py index e881f59..6a9690c 100644 --- a/springboard/utils.py +++ b/springboard/utils.py @@ -1,6 +1,7 @@ import re from functools import wraps from urlparse import urlparse +import math def parse_repo_name(repo_url): @@ -73,3 +74,110 @@ def config_dict(data): """ lines = config_list(data) return dict(re.split('\s*=\s*', value) for value in lines) + + +class Paginator(object): + """ + A thing that helps us page through result sets + + """ + + def __init__(self, results, page, results_per_page=10, slider_value=5): + self.results = results + self.page = page + self.results_per_page = results_per_page + self.slider_value = slider_value + self.buffer_value = self.slider_value / 2 + + def total_count(self): + return len(self.results) + + def get_page(self): + return self.results[self.page * self.results_per_page: + (self.page + 1) * self.results_per_page] + + def has_next_page(self): + return ((self.page + 1) * self.results_per_page) < self.total_count() + + def has_previous_page(self): + return self.page + + def total_pages(self): + return int( + math.ceil( + float(self.total_count()) / float(self.results_per_page))) + + def page_numbers(self): + if (self.page - self.buffer_value) < 0: + return [page_number + for page_number in range( + 0, min([self.slider_value, self.total_pages()]))] + elif (self.page + self.buffer_value) >= self.total_pages(): + return [page_number + for page_number in range( + max((self.total_pages() - self.slider_value), 0), + self.total_pages()) + ] + else: + return range(self.page - self.buffer_value, + self.page + self.buffer_value + 1) + + def page_numbers_left(self): + page_numbers = self.page_numbers() + if not any(page_numbers): + return False + return page_numbers[:page_numbers.index(self.page)] + + def page_numbers_right(self): + page_numbers = self.page_numbers() + if not any(page_numbers): + return False + return page_numbers[page_numbers.index(self.page) + 1:] + + def needs_start_ellipsis(self): + page_numbers = self.page_numbers() + if not any(page_numbers): + return False + return page_numbers[0] > 1 + + def needs_end_ellipsis(self): + page_numbers = self.page_numbers() + if not any(page_numbers): + return False + return page_numbers[-1] < (self.total_pages() - 2) + + def show_start(self): + page_numbers = self.page_numbers() + if not any(page_numbers): + return False + return page_numbers[0] > 0 + + def show_end(self): + page_numbers = self.page_numbers() + if not any(page_numbers): + return False + return page_numbers[-1] < self.total_pages() - 1 + + +class EGPaginator(Paginator): + + class generator(object): + def __init__(self, es_results): + self.es_results = es_results + + def __iter__(self): + return (obj.to_object() for obj in self.es_results) + + def __len__(self): + return self.es_results.__len__() + + def __getitem__(self, k): + if isinstance(k, slice): + return self.__class__(self.es_results.__getitem__(k)) + return self.es_results.__getitem__(k).to_object() + + def total_count(self): + return self.results.count() + + def get_page(self): + return self.__class__.generator(super(EGPaginator, self).get_page()) From bd3f55475eadbd73a5f86874b16c5a4faa6de35b Mon Sep 17 00:00:00 2001 From: Rizmari Versfeld Date: Mon, 1 Jun 2015 14:26:08 +0200 Subject: [PATCH 3/6] add tests for EGPaginator --- springboard/tests/test_utils.py | 51 +++++++++++++++++++++++++++------ springboard/utils.py | 25 +++++----------- 2 files changed, 49 insertions(+), 27 deletions(-) diff --git a/springboard/tests/test_utils.py b/springboard/tests/test_utils.py index 72cc3c7..dbb01b4 100644 --- a/springboard/tests/test_utils.py +++ b/springboard/tests/test_utils.py @@ -1,7 +1,14 @@ from unittest import TestCase +from mock import patch, Mock + +from unicore.content.models import Category +from elasticgit.search import SM + +from springboard.tests.base import SpringboardTestCase from springboard.utils import ( - parse_repo_name, config_list, config_dict, Paginator) + parse_repo_name, config_list, config_dict, Paginator, + EGPaginator) class TestUtils(TestCase): @@ -41,8 +48,11 @@ def test_config_dict(self): class TestPaginator(TestCase): + def mk_paginator(self, results, page, **kwargs): + return Paginator(results, page, **kwargs) + def test_first_page(self): - paginator = Paginator(range(100), 0) + paginator = self.mk_paginator(range(100), 0) self.assertTrue(paginator.has_next_page()) self.assertFalse(paginator.has_previous_page()) self.assertEqual(paginator.total_pages(), 10) @@ -53,7 +63,7 @@ def test_first_page(self): self.assertEqual(paginator.page_numbers_right(), [1, 2, 3, 4]) def test_last_page(self): - paginator = Paginator(range(100), 9) + paginator = self.mk_paginator(range(100), 9) self.assertFalse(paginator.has_next_page()) self.assertTrue(paginator.has_previous_page()) self.assertEqual(paginator.total_pages(), 10) @@ -64,7 +74,7 @@ def test_last_page(self): self.assertEqual(paginator.page_numbers_right(), []) def test_middle_page(self): - paginator = Paginator(range(100), 4) + paginator = self.mk_paginator(range(100), 4) self.assertTrue(paginator.has_next_page()) self.assertTrue(paginator.has_previous_page()) self.assertEqual(paginator.total_pages(), 10) @@ -75,14 +85,14 @@ def test_middle_page(self): self.assertEqual(paginator.page_numbers_right(), [5, 6]) def test_show_start(self): - paginator = Paginator(range(100), 3) + paginator = self.mk_paginator(range(100), 3) self.assertTrue(paginator.show_start()) self.assertFalse(paginator.needs_start_ellipsis()) self.assertEqual(paginator.page_numbers_left(), [1, 2]) self.assertEqual(paginator.page_numbers_right(), [4, 5]) def test_show_end(self): - paginator = Paginator(range(100), 7) + paginator = self.mk_paginator(range(100), 7) self.assertTrue(paginator.show_start()) self.assertTrue(paginator.needs_start_ellipsis()) self.assertEqual(paginator.page_numbers(), [5, 6, 7, 8, 9]) @@ -92,7 +102,7 @@ def test_show_end(self): self.assertFalse(paginator.needs_end_ellipsis()) def test_show_end_not_ellipsis(self): - paginator = Paginator(range(100), 6) + paginator = self.mk_paginator(range(100), 6) self.assertTrue(paginator.show_start()) self.assertTrue(paginator.needs_start_ellipsis()) self.assertEqual(paginator.page_numbers(), [4, 5, 6, 7, 8]) @@ -102,7 +112,7 @@ def test_show_end_not_ellipsis(self): self.assertFalse(paginator.needs_end_ellipsis()) def test_small_result_set(self): - paginator = Paginator(range(39), 0) + paginator = self.mk_paginator(range(39), 0) self.assertFalse(paginator.show_start()) self.assertFalse(paginator.needs_start_ellipsis()) self.assertFalse(paginator.show_end()) @@ -111,9 +121,32 @@ def test_small_result_set(self): self.assertEqual(paginator.page_numbers_right(), [1, 2, 3]) def test_large_end_result_set(self): - paginator = Paginator(range(133), 12) + paginator = self.mk_paginator(range(133), 12) self.assertEqual(paginator.page_numbers(), [9, 10, 11, 12, 13]) self.assertEqual(paginator.page_numbers_left(), [9, 10, 11]) self.assertEqual(paginator.page_numbers_right(), [13]) self.assertFalse(paginator.show_end()) self.assertFalse(paginator.needs_end_ellipsis()) + + +class TestEGPaginator(TestPaginator, SpringboardTestCase): + + def mk_paginator(self, results, page, **kwargs): + workspace = self.mk_workspace() + patch_count = patch.object( + SM, 'count', return_value=len(results)) + patch_count.start() + self.addCleanup(patch_count.stop) + results = SM(Category, in_=[workspace.working_dir]) + return EGPaginator(results, page, **kwargs) + + def test_cache_total_count(self): + paginator = self.mk_paginator(range(10), 0) + self.assertEqual(paginator.total_count(), 10) + self.assertEqual(paginator.total_count(), 10) + self.assertEqual(paginator.results.count.call_count, 1) + + def test_get_page(self): + paginator = self.mk_paginator(range(10), 0) + page = paginator.get_page() + self.assertIsInstance(page, SM) diff --git a/springboard/utils.py b/springboard/utils.py index 6a9690c..b0aeff5 100644 --- a/springboard/utils.py +++ b/springboard/utils.py @@ -161,23 +161,12 @@ def show_end(self): class EGPaginator(Paginator): - class generator(object): - def __init__(self, es_results): - self.es_results = es_results - - def __iter__(self): - return (obj.to_object() for obj in self.es_results) - - def __len__(self): - return self.es_results.__len__() - - def __getitem__(self, k): - if isinstance(k, slice): - return self.__class__(self.es_results.__getitem__(k)) - return self.es_results.__getitem__(k).to_object() + def __init__(self, *args, **kwargs): + super(EGPaginator, self).__init__(*args, **kwargs) + # caches results.count() value + self._total_count = None def total_count(self): - return self.results.count() - - def get_page(self): - return self.__class__.generator(super(EGPaginator, self).get_page()) + if self._total_count is None: + self._total_count = self.results.count() + return self._total_count From 7e95ab1b5414b2794d21be00ce89ff8f5405504b Mon Sep 17 00:00:00 2001 From: Rizmari Versfeld Date: Mon, 1 Jun 2015 14:42:36 +0200 Subject: [PATCH 4/6] documentation, remove EGPaginator and make Paginator handle S correctly --- springboard/tests/test_utils.py | 15 ++++----------- springboard/utils.py | 26 +++++++++++++------------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/springboard/tests/test_utils.py b/springboard/tests/test_utils.py index dbb01b4..997186f 100644 --- a/springboard/tests/test_utils.py +++ b/springboard/tests/test_utils.py @@ -1,14 +1,13 @@ from unittest import TestCase -from mock import patch, Mock +from mock import patch from unicore.content.models import Category from elasticgit.search import SM from springboard.tests.base import SpringboardTestCase from springboard.utils import ( - parse_repo_name, config_list, config_dict, Paginator, - EGPaginator) + parse_repo_name, config_list, config_dict, Paginator) class TestUtils(TestCase): @@ -129,7 +128,7 @@ def test_large_end_result_set(self): self.assertFalse(paginator.needs_end_ellipsis()) -class TestEGPaginator(TestPaginator, SpringboardTestCase): +class TestPaginatorWithESResults(TestPaginator, SpringboardTestCase): def mk_paginator(self, results, page, **kwargs): workspace = self.mk_workspace() @@ -138,13 +137,7 @@ def mk_paginator(self, results, page, **kwargs): patch_count.start() self.addCleanup(patch_count.stop) results = SM(Category, in_=[workspace.working_dir]) - return EGPaginator(results, page, **kwargs) - - def test_cache_total_count(self): - paginator = self.mk_paginator(range(10), 0) - self.assertEqual(paginator.total_count(), 10) - self.assertEqual(paginator.total_count(), 10) - self.assertEqual(paginator.results.count.call_count, 1) + return Paginator(results, page, **kwargs) def test_get_page(self): paginator = self.mk_paginator(range(10), 0) diff --git a/springboard/utils.py b/springboard/utils.py index b0aeff5..cbf9df5 100644 --- a/springboard/utils.py +++ b/springboard/utils.py @@ -3,6 +3,8 @@ from urlparse import urlparse import math +from elasticutils import S + def parse_repo_name(repo_url): pr = urlparse(repo_url) @@ -80,6 +82,15 @@ class Paginator(object): """ A thing that helps us page through result sets + :param iterable results: + The iterable of objects to paginate. + :param int page: + The page number, zero-based. + :param int results_per_page: + The number of objects in each page. + :param int slider_value: + The number of page numbers to display, excluding the current page. + """ def __init__(self, results, page, results_per_page=10, slider_value=5): @@ -90,6 +101,8 @@ def __init__(self, results, page, results_per_page=10, slider_value=5): self.buffer_value = self.slider_value / 2 def total_count(self): + if isinstance(self.results, S): + return self.results.count() return len(self.results) def get_page(self): @@ -157,16 +170,3 @@ def show_end(self): if not any(page_numbers): return False return page_numbers[-1] < self.total_pages() - 1 - - -class EGPaginator(Paginator): - - def __init__(self, *args, **kwargs): - super(EGPaginator, self).__init__(*args, **kwargs) - # caches results.count() value - self._total_count = None - - def total_count(self): - if self._total_count is None: - self._total_count = self.results.count() - return self._total_count From 353d39d29aa8db299eeb8f64c4f67a1f56da9e70 Mon Sep 17 00:00:00 2001 From: Rizmari Versfeld Date: Mon, 1 Jun 2015 14:52:00 +0200 Subject: [PATCH 5/6] pagination filter --- springboard/defaults.ini | 1 + springboard/filters.py | 6 ++++++ springboard/tests/test_filters.py | 9 ++++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/springboard/defaults.ini b/springboard/defaults.ini index 1282bc3..53de605 100644 --- a/springboard/defaults.ini +++ b/springboard/defaults.ini @@ -20,6 +20,7 @@ jinja2.filters = markdown = springboard.filters:markdown_filter display_language_name = springboard.filters:display_language_name_filter language_direction = springboard.filters:language_direction_filter + paginate = springboard.filters:paginate_filter unicorehub.host = unicorehub.app_id = diff --git a/springboard/filters.py b/springboard/filters.py index 081dcd5..7cb62e1 100644 --- a/springboard/filters.py +++ b/springboard/filters.py @@ -11,6 +11,8 @@ from babel import Locale from pycountry import languages +from springboard.utils import Paginator + # known right-to-left language codes KNOWN_RTL_LANGUAGES = {"urd", "ara", "arc", "per", "heb", "kur", "yid"} @@ -53,3 +55,7 @@ def language_direction_filter(locale): if language_code in KNOWN_RTL_LANGUAGES: return 'rtl' return 'ltr' + + +def paginate_filter(results, page, results_per_page=None, slider_value=None): + return Paginator(results, page, results_per_page, slider_value) diff --git a/springboard/tests/test_filters.py b/springboard/tests/test_filters.py index 4177f50..6b51aa7 100644 --- a/springboard/tests/test_filters.py +++ b/springboard/tests/test_filters.py @@ -7,7 +7,7 @@ from springboard.tests import SpringboardTestCase from springboard.filters import ( format_date_filter, thumbor_filter, markdown_filter, - language_direction_filter) + language_direction_filter, paginate_filter) class TestFilters(SpringboardTestCase): @@ -58,3 +58,10 @@ def test_markdown_filter_none(self): def test_language_direction_filter(self): self.assertEqual(language_direction_filter('eng_GB'), 'ltr') self.assertEqual(language_direction_filter('urd_PK'), 'rtl') + + def test_paginate_filter(self): + paginator = paginate_filter([1, 2, 3, 4, 5], 2, + results_per_page=1, + slider_value=2) + self.assertEqual(paginator.get_page(), [3]) + self.assertEqual(paginator.page_numbers(), [1, 2, 3]) From 3c9c546fe0d44f3538704a91477b9f9afc77b22c Mon Sep 17 00:00:00 2001 From: Rizmari Versfeld Date: Mon, 1 Jun 2015 14:55:59 +0200 Subject: [PATCH 6/6] fix defaults --- springboard/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/springboard/filters.py b/springboard/filters.py index 7cb62e1..0aed351 100644 --- a/springboard/filters.py +++ b/springboard/filters.py @@ -57,5 +57,5 @@ def language_direction_filter(locale): return 'ltr' -def paginate_filter(results, page, results_per_page=None, slider_value=None): +def paginate_filter(results, page, results_per_page=10, slider_value=5): return Paginator(results, page, results_per_page, slider_value)