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
56 changes: 48 additions & 8 deletions cms/djangoapps/contentstore/rest_api/v2/views/home.py
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."""
Expand Down Expand Up @@ -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,
Expand All @@ -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**

Expand Down Expand Up @@ -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,
Copy link
Member Author

@mariajgrimaldi mariajgrimaldi Feb 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to reviewer: I'm unsure how to handle this case. In this view, not only courses are returned, but also returns in_process_course_actions which is a list of CourseRerunState objects not necessarily related to the courses list so I'm not sure why they're currently returned together.

So I didn't add pagination to in_process_course_actions since I believe that that list should be returned by another different view dedicated only to course reruns state objects.

What do you think? That new view is out of the scope of this PR, so this new view returns the list of course reruns objects without pagination.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we currently need this paginated list, I don't see a problem to leave it like that for the moment, and work on it later in a dedicated PR if necessary.

})
return paginator.get_paginated_response(serializer.data)
Empty file.
69 changes: 69 additions & 0 deletions cms/djangoapps/contentstore/rest_api/v2/views/tests/test_home.py
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)