-
Notifications
You must be signed in to change notification settings - Fork 4.2k
feat: add pagination to the HomePageCoursesV2 API #34175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1567a35
546a74e
d87c42a
ee1c0d2
9cb10e7
d127b36
e817f56
e59900e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,44 @@ | ||
| """HomePageCoursesViewV2 APIView for getting content available to the logged in user.""" | ||
| import edx_api_doc_tools as apidocs | ||
| from rest_framework.request import Request | ||
| from collections import OrderedDict | ||
| from rest_framework.response import Response | ||
| from rest_framework.request import Request | ||
| from rest_framework.views import APIView | ||
| from rest_framework.pagination import PageNumberPagination | ||
|
|
||
| from openedx.core.lib.api.view_utils import view_auth_classes | ||
|
|
||
| from cms.djangoapps.contentstore.utils import get_course_context_v2 | ||
| from cms.djangoapps.contentstore.rest_api.v2.serializers import CourseHomeTabSerializerV2 | ||
|
|
||
|
|
||
| class HomePageCoursesPaginator(PageNumberPagination): | ||
|
|
||
| def get_paginated_response(self, data): | ||
| """Return a paginated style `Response` object for the given output data.""" | ||
| return Response(OrderedDict([ | ||
| ('count', self.page.paginator.count), | ||
| ('num_pages', self.page.paginator.num_pages), | ||
| ('next', self.get_next_link()), | ||
| ('previous', self.get_previous_link()), | ||
| ('results', data), | ||
| ])) | ||
|
|
||
| def paginate_queryset(self, queryset, request, view=None): | ||
| """ | ||
| Paginate a queryset if required, either returning a page object, | ||
| or `None` if pagination is not configured for this view. | ||
|
|
||
| This method is a modified version of the original `paginate_queryset` method | ||
| from the `PageNumberPagination` class. The original method was modified to | ||
| handle the case where the `queryset` is a `filter` object. | ||
| """ | ||
| if isinstance(queryset, filter): | ||
| queryset = list(queryset) | ||
|
|
||
| return super().paginate_queryset(queryset, request, view) | ||
|
|
||
|
|
||
| @view_auth_classes(is_authenticated=True) | ||
| class HomePageCoursesViewV2(APIView): | ||
| """View for getting all courses available to the logged in user.""" | ||
|
|
@@ -40,6 +70,11 @@ class HomePageCoursesViewV2(APIView): | |
| apidocs.ParameterLocation.QUERY, | ||
| description="Query param to filter by archived courses only", | ||
| ), | ||
| apidocs.string_parameter( | ||
| "page", | ||
| apidocs.ParameterLocation.QUERY, | ||
| description="Query param to paginate the courses", | ||
| ), | ||
| ], | ||
| responses={ | ||
| 200: CourseHomeTabSerializerV2, | ||
|
|
@@ -58,6 +93,7 @@ def get(self, request: Request): | |
| GET /api/contentstore/v2/home/courses?order=-org | ||
| GET /api/contentstore/v2/home/courses?active_only=true | ||
| GET /api/contentstore/v2/home/courses?archived_only=true | ||
| GET /api/contentstore/v2/home/courses?page=2 | ||
|
|
||
| **Response Values** | ||
|
|
||
|
|
@@ -88,11 +124,15 @@ def get(self, request: Request): | |
| } | ||
| ``` | ||
| """ | ||
|
|
||
| courses, in_process_course_actions = get_course_context_v2(request) | ||
| courses_context = { | ||
| "courses": courses, | ||
| "in_process_course_actions": in_process_course_actions, | ||
| } | ||
| serializer = CourseHomeTabSerializerV2(courses_context) | ||
| return Response(serializer.data) | ||
| paginator = HomePageCoursesPaginator() | ||
| courses_page = paginator.paginate_queryset( | ||
| courses, | ||
| self.request, | ||
| view=self | ||
| ) | ||
| serializer = CourseHomeTabSerializerV2({ | ||
| 'courses': courses_page, | ||
| 'in_process_course_actions': in_process_course_actions, | ||
|
||
| }) | ||
| return paginator.get_paginated_response(serializer.data) | ||
mariajgrimaldi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| """ | ||
| Unit tests for home page view. | ||
| """ | ||
| import ddt | ||
| from django.conf import settings | ||
| from django.urls import reverse | ||
| from edx_toggles.toggles.testutils import ( | ||
| override_waffle_switch, | ||
| ) | ||
| from rest_framework import status | ||
|
|
||
| from cms.djangoapps.contentstore.tests.utils import CourseTestCase | ||
| from cms.djangoapps.contentstore.views.course import ENABLE_GLOBAL_STAFF_OPTIMIZATION | ||
| from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory | ||
| from xmodule.modulestore.tests.factories import CourseFactory | ||
|
|
||
|
|
||
| @ddt.ddt | ||
| class HomePageCoursesViewTest(CourseTestCase): | ||
| """ | ||
| Tests for HomePageView. | ||
| """ | ||
|
|
||
| def setUp(self): | ||
| super().setUp() | ||
| self.url = reverse("cms.djangoapps.contentstore:v2:courses") | ||
|
|
||
| def test_home_page_response(self): | ||
| """Check successful response content""" | ||
| response = self.client.get(self.url) | ||
| course_id = str(self.course.id) | ||
|
|
||
| expected_response = { | ||
| "courses": [{ | ||
| "course_key": course_id, | ||
| "display_name": self.course.display_name, | ||
| "lms_link": f'//{settings.LMS_BASE}/courses/{course_id}/jump_to/{self.course.location}', | ||
| "number": self.course.number, | ||
| "org": self.course.org, | ||
| "rerun_link": f'/course_rerun/{course_id}', | ||
| "run": self.course.id.run, | ||
| "url": f'/course/{course_id}', | ||
| }], | ||
| "in_process_course_actions": [], | ||
| } | ||
|
|
||
| self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
| self.assertDictEqual(expected_response, response.data) | ||
|
|
||
| @override_waffle_switch(ENABLE_GLOBAL_STAFF_OPTIMIZATION, True) | ||
| def test_org_query_if_passed(self): | ||
| """Test home page when org filter passed as a query param""" | ||
| foo_course = self.store.make_course_key('foo-org', 'bar-number', 'baz-run') | ||
| test_course = CourseFactory.create( | ||
| org=foo_course.org, | ||
| number=foo_course.course, | ||
| run=foo_course.run | ||
| ) | ||
| CourseOverviewFactory.create(id=test_course.id, org='foo-org') | ||
| response = self.client.get(self.url, {"org": "foo-org"}) | ||
| self.assertEqual(len(response.data['courses']), 1) | ||
| self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
|
|
||
| @override_waffle_switch(ENABLE_GLOBAL_STAFF_OPTIMIZATION, True) | ||
| def test_org_query_if_empty(self): | ||
| """Test home page with an empty org query param""" | ||
| response = self.client.get(self.url) | ||
| self.assertEqual(len(response.data['courses']), 0) | ||
| self.assertEqual(response.status_code, status.HTTP_200_OK) |
Uh oh!
There was an error while loading. Please reload this page.