Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions springboard/defaults.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
6 changes: 6 additions & 0 deletions springboard/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down Expand Up @@ -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=10, slider_value=5):
return Paginator(results, page, results_per_page, slider_value)
9 changes: 8 additions & 1 deletion springboard/tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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])
109 changes: 108 additions & 1 deletion springboard/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from unittest import TestCase

from springboard.utils import parse_repo_name, config_list, config_dict
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)


class TestUtils(TestCase):
Expand Down Expand Up @@ -36,3 +43,103 @@ def test_config_dict(self):
'elastic': 'search',
}, config_dict('foo=bar\ngum=tree\nelastic=search\n'))
self.assertEqual({}, config_dict(''))


class TestPaginator(TestCase):

def mk_paginator(self, results, page, **kwargs):
return Paginator(results, page, **kwargs)

def test_first_page(self):
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)
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 = self.mk_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 = self.mk_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 = 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 = 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])
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 = 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])
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 = self.mk_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 = 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 TestPaginatorWithESResults(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 Paginator(results, page, **kwargs)

def test_get_page(self):
paginator = self.mk_paginator(range(10), 0)
page = paginator.get_page()
self.assertIsInstance(page, SM)
97 changes: 97 additions & 0 deletions springboard/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import re
from functools import wraps
from urlparse import urlparse
import math

from elasticutils import S


def parse_repo_name(repo_url):
Expand Down Expand Up @@ -73,3 +76,97 @@ 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

: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):
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):
if isinstance(self.results, S):
return self.results.count()
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