Add basic elasticsearch search for teams
TNL-3014 Add tests for search Add text_search to TeamsListView Add reindex command line tool for course teams Add Search Pagination and update comments Move paginate_search_results to common library
This commit is contained in:
52
openedx/core/lib/api/paginators.py
Normal file
52
openedx/core/lib/api/paginators.py
Normal file
@@ -0,0 +1,52 @@
|
||||
""" Paginatator methods for edX API implementations."""
|
||||
|
||||
from django.http import Http404
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.paginator import Paginator, InvalidPage
|
||||
|
||||
|
||||
def paginate_search_results(object_class, search_results, page_size, page):
|
||||
"""
|
||||
Takes edx-search results and returns a Page object populated
|
||||
with db objects for that page.
|
||||
|
||||
:param object_class: Model class to use when querying the db for objects.
|
||||
:param search_results: edX-search results.
|
||||
:param page_size: Number of results per page.
|
||||
:param page: Page number.
|
||||
:return: Paginator object with model objects
|
||||
"""
|
||||
paginator = Paginator(search_results['results'], page_size)
|
||||
|
||||
# This code is taken from within the GenericAPIView#paginate_queryset method.
|
||||
# It is common code, but
|
||||
try:
|
||||
page_number = paginator.validate_number(page)
|
||||
except InvalidPage:
|
||||
if page == 'last':
|
||||
page_number = paginator.num_pages
|
||||
else:
|
||||
raise Http404(_("Page is not 'last', nor can it be converted to an int."))
|
||||
|
||||
try:
|
||||
paged_results = paginator.page(page_number)
|
||||
except InvalidPage as e: # pylint: disable=invalid-name
|
||||
raise Http404(_('Invalid page (%(page_number)s): %(message)s') % {
|
||||
'page_number': page_number,
|
||||
'message': str(e)
|
||||
})
|
||||
|
||||
search_queryset_pks = [item['data']['pk'] for item in paged_results.object_list]
|
||||
queryset = object_class.objects.filter(pk__in=search_queryset_pks)
|
||||
|
||||
def ordered_objects(primary_key):
|
||||
""" Returns database object matching the search result object"""
|
||||
for obj in queryset:
|
||||
if obj.pk == primary_key:
|
||||
return obj
|
||||
|
||||
# map over the search results and get a list of database objects in the same order
|
||||
object_results = map(ordered_objects, search_queryset_pks)
|
||||
paged_results.object_list = object_results
|
||||
|
||||
return paged_results
|
||||
139
openedx/core/lib/api/tests/test_paginators.py
Normal file
139
openedx/core/lib/api/tests/test_paginators.py
Normal file
@@ -0,0 +1,139 @@
|
||||
""" Tests paginator methods """
|
||||
import ddt
|
||||
from mock import Mock, MagicMock
|
||||
from unittest import TestCase
|
||||
from django.http import Http404
|
||||
|
||||
from openedx.core.lib.api.paginators import paginate_search_results
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class PaginateSearchResultsTestCase(TestCase):
|
||||
"""Test cases for paginate_search_results method"""
|
||||
|
||||
def setUp(self):
|
||||
super(PaginateSearchResultsTestCase, self).setUp()
|
||||
|
||||
self.default_size = 6
|
||||
self.default_page = 1
|
||||
self.search_results = {
|
||||
"count": 3,
|
||||
"took": 1,
|
||||
"results": [
|
||||
{
|
||||
'_id': 0,
|
||||
'data': {
|
||||
'pk': 0,
|
||||
'name': 'object 0'
|
||||
}
|
||||
},
|
||||
{
|
||||
'_id': 1,
|
||||
'data': {
|
||||
'pk': 1,
|
||||
'name': 'object 1'
|
||||
}
|
||||
},
|
||||
{
|
||||
'_id': 2,
|
||||
'data': {
|
||||
'pk': 2,
|
||||
'name': 'object 2'
|
||||
}
|
||||
},
|
||||
{
|
||||
'_id': 3,
|
||||
'data': {
|
||||
'pk': 3,
|
||||
'name': 'object 3'
|
||||
}
|
||||
},
|
||||
{
|
||||
'_id': 4,
|
||||
'data': {
|
||||
'pk': 4,
|
||||
'name': 'object 4'
|
||||
}
|
||||
},
|
||||
{
|
||||
'_id': 5,
|
||||
'data': {
|
||||
'pk': 5,
|
||||
'name': 'object 5'
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
self.mock_model = Mock()
|
||||
self.mock_model.objects = Mock()
|
||||
self.mock_model.objects.filter = Mock()
|
||||
|
||||
@ddt.data(
|
||||
(1, 1, True),
|
||||
(1, 3, True),
|
||||
(1, 5, True),
|
||||
(1, 10, False),
|
||||
(2, 1, True),
|
||||
(2, 3, False),
|
||||
(2, 5, False),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_paginated_results(self, page_number, page_size, has_next):
|
||||
""" Test the page returned has the expected db objects and acts
|
||||
like a proper page object.
|
||||
"""
|
||||
id_range = get_object_range(page_number, page_size)
|
||||
db_objects = [build_mock_object(obj_id) for obj_id in id_range]
|
||||
self.mock_model.objects.filter = MagicMock(return_value=db_objects)
|
||||
|
||||
page = paginate_search_results(self.mock_model, self.search_results, page_size, page_number)
|
||||
|
||||
self.mock_model.objects.filter.assert_called_with(pk__in=id_range)
|
||||
self.assertEquals(db_objects, page.object_list)
|
||||
self.assertTrue(page.number, page_number)
|
||||
self.assertEquals(page.has_next(), has_next)
|
||||
|
||||
def test_paginated_results_last_keyword(self):
|
||||
""" Test the page returned has the expected db objects and acts
|
||||
like a proper page object using 'last' keyword.
|
||||
"""
|
||||
page_number = 2
|
||||
page_size = 3
|
||||
id_range = get_object_range(page_number, page_size)
|
||||
db_objects = [build_mock_object(obj_id) for obj_id in id_range]
|
||||
self.mock_model.objects.filter = MagicMock(return_value=db_objects)
|
||||
|
||||
page = paginate_search_results(self.mock_model, self.search_results, self.default_size, 'last')
|
||||
|
||||
self.mock_model.objects.filter.assert_called_with(pk__in=id_range)
|
||||
self.assertEquals(db_objects, page.object_list)
|
||||
self.assertTrue(page.number, page_number)
|
||||
self.assertFalse(page.has_next())
|
||||
|
||||
@ddt.data(10, -1, 0, 'str')
|
||||
def test_invalid_page_number(self, page_num):
|
||||
""" Test that a Http404 error is raised with non-integer and out-of-range pages
|
||||
"""
|
||||
with self.assertRaises(Http404):
|
||||
paginate_search_results(self.mock_model, self.search_results, self.default_size, page_num)
|
||||
|
||||
|
||||
def build_mock_object(obj_id):
|
||||
""" Build a mock object with the passed id"""
|
||||
mock_object = Mock()
|
||||
object_config = {
|
||||
'pk': obj_id,
|
||||
'name': "object {}".format(obj_id)
|
||||
}
|
||||
mock_object.configure_mock(**object_config)
|
||||
return mock_object
|
||||
|
||||
|
||||
def get_object_range(page, page_size):
|
||||
""" Get the range of expected object ids given a page and page size.
|
||||
This will take into account the max_id of the sample data. Currently 5.
|
||||
"""
|
||||
max_id = 5
|
||||
start = min((page - 1) * page_size, max_id)
|
||||
end = min(start + page_size, max_id + 1)
|
||||
return range(start, end)
|
||||
Reference in New Issue
Block a user