Paginate results of Courses API list endpoint

* Catalog results are now paginated
* Implements the new namespaced pagination described at
  https://openedx.atlassian.net/wiki/pages/viewpage.action?pageId=47481813
* API level code returns pythonic business objects
* View layer performs serialize at the view layer
* Convert views to use DRF generic views
* Removes an unintentional authentication decorator that caused
  the detail endpoint to return a 401 for anonymous users

MA-1724
This commit is contained in:
J. Cliff Dyer
2015-11-20 21:59:54 +00:00
committed by Nimisha Asthagiri
parent 11f34c1e8c
commit f53de2c04a
8 changed files with 219 additions and 82 deletions

View File

@@ -29,6 +29,38 @@ class DefaultPagination(pagination.PageNumberPagination):
})
class NamespacedPageNumberPagination(pagination.PageNumberPagination):
"""
Pagination scheme that returns results with pagination metadata
embedded in a "pagination" attribute. Can be used with data
that comes as a list of items, or as a dict with a "results"
attribute that contains a list of items.
"""
page_size_query_param = "page_size"
def get_paginated_response(self, data):
"""
Annotate the response with pagination information
"""
metadata = {
'next': self.get_next_link(),
'previous': self.get_previous_link(),
'count': self.page.paginator.count,
'num_pages': self.page.paginator.num_pages,
}
if isinstance(data, dict):
if 'results' not in data:
raise TypeError(u'Malformed result dict')
data['pagination'] = metadata
else:
data = {
'results': data,
'pagination': metadata,
}
return Response(data)
def paginate_search_results(object_class, search_results, page_size, page):
"""
Takes edx-search results and returns a Page object populated

View File

@@ -1,10 +1,15 @@
""" Tests paginator methods """
from collections import namedtuple
import ddt
from mock import Mock, MagicMock
from unittest import TestCase
from django.http import Http404
from django.test import RequestFactory
from rest_framework import serializers
from openedx.core.lib.api.paginators import paginate_search_results
from openedx.core.lib.api.paginators import NamespacedPageNumberPagination, paginate_search_results
@ddt.ddt
@@ -118,6 +123,50 @@ class PaginateSearchResultsTestCase(TestCase):
paginate_search_results(self.mock_model, self.search_results, self.default_size, page_num)
class NamespacedPaginationTestCase(TestCase):
"""
Test behavior of `NamespacedPageNumberPagination`
"""
TestUser = namedtuple('TestUser', ['username', 'email'])
class TestUserSerializer(serializers.Serializer): # pylint: disable=abstract-method
"""
Simple serializer to paginate results from
"""
username = serializers.CharField()
email = serializers.CharField()
expected_data = {
'results': [
{'username': 'user_5', 'email': 'user_5@example.com'},
{'username': 'user_6', 'email': 'user_6@example.com'},
{'username': 'user_7', 'email': 'user_7@example.com'},
{'username': 'user_8', 'email': 'user_8@example.com'},
{'username': 'user_9', 'email': 'user_9@example.com'},
],
'pagination': {
'next': 'http://testserver/endpoint?page=3&page_size=5',
'previous': 'http://testserver/endpoint?page_size=5',
'count': 25,
'num_pages': 5,
}
}
def setUp(self):
super(NamespacedPaginationTestCase, self).setUp()
self.paginator = NamespacedPageNumberPagination()
self.users = [self.TestUser('user_{}'.format(idx), 'user_{}@example.com'.format(idx)) for idx in xrange(25)]
self.request_factory = RequestFactory()
def test_basic_pagination(self):
request = self.request_factory.get('/endpoint', data={'page': 2, 'page_size': 5})
request.query_params = {'page': 2, 'page_size': 5}
paged_users = self.paginator.paginate_queryset(self.users, request)
results = self.TestUserSerializer(paged_users, many=True).data
self.assertEqual(self.expected_data, self.paginator.get_paginated_response(results).data)
def build_mock_object(obj_id):
""" Build a mock object with the passed id"""
mock_object = Mock()