From d132efa08dbb061436fdb5f9bf6dac3a8399135d Mon Sep 17 00:00:00 2001 From: Paulo Viadanna Date: Thu, 21 Aug 2025 14:56:18 -0300 Subject: [PATCH] feat: add a POST endpoint for listing courses (#35586) * feat: add a POST endpoint for listing courses --- docs/lms-openapi.yaml | 5 +++++ lms/djangoapps/course_api/tests/test_views.py | 12 ++++++++++++ lms/djangoapps/course_api/views.py | 19 ++++++++++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/lms-openapi.yaml b/docs/lms-openapi.yaml index 457255d7e4..6360c5c4a0 100644 --- a/docs/lms-openapi.yaml +++ b/docs/lms-openapi.yaml @@ -2623,6 +2623,7 @@ paths: **Example Requests** GET /api/courses/v1/courses/ + POST /api/courses/v1/courses/ **Response Values** @@ -2698,6 +2699,10 @@ paths: "start_type": "timestamp" } ] + **Note** + + The POST /api/courses/v1/courses/ reads `request.body` for parameters, allowing for + larger input than the query string. parameters: - name: page in: query diff --git a/lms/djangoapps/course_api/tests/test_views.py b/lms/djangoapps/course_api/tests/test_views.py index aab9769661..d0f50c5818 100644 --- a/lms/djangoapps/course_api/tests/test_views.py +++ b/lms/djangoapps/course_api/tests/test_views.py @@ -250,6 +250,18 @@ class CourseListViewTestCaseMultipleCourses(CourseApiTestViewMixin, ModuleStoreT ids = {c['course_id'] for c in response.json()['results']} self.assertEqual(ids, {str(self.course.id)}) + def test_filter_post(self): + """Verify that CourseOverviews are filtered by the provided org key in a POST request.""" + self.setup_user(self.staff_user) + + # Create a second course to be filtered out of queries. + alternate_course = self.create_course( + org=md5(self.course.org.encode('utf-8')).hexdigest() + ) + + response = self.client.post(self.url, data={'course_keys': str(self.course.id)}) + assert all((course['org'] == self.course.org) for course in response.json()['results']) + class CourseDetailViewTestCase(CourseApiTestViewMixin, SharedModuleStoreTestCase): """ diff --git a/lms/djangoapps/course_api/views.py b/lms/djangoapps/course_api/views.py index 698800bf18..909c7fab1e 100644 --- a/lms/djangoapps/course_api/views.py +++ b/lms/djangoapps/course_api/views.py @@ -254,6 +254,7 @@ class CourseListView(DeveloperErrorViewMixin, ListAPIView): **Example Requests** GET /api/courses/v1/courses/ + POST /api/courses/v1/courses/ **Response Values** @@ -329,6 +330,10 @@ class CourseListView(DeveloperErrorViewMixin, ListAPIView): "start_type": "timestamp" } ] + **Note** + + The POST /api/courses/v1/courses/ reads `request.body` for parameters, allowing for + larger input than the query string. """ class CourseListPageNumberPagination(LazyPageNumberPagination): max_page_size = 100 @@ -341,7 +346,13 @@ class CourseListView(DeveloperErrorViewMixin, ListAPIView): """ Yield courses visible to the user. """ - form = CourseListGetForm(self.request.query_params, initial={'requesting_user': self.request.user}) + form_data = self.request.query_params + if self.request.method == 'POST': + form_data = self.request.data + form = CourseListGetForm( + data=form_data, + initial={'requesting_user': self.request.user} + ) if not form.is_valid(): raise ValidationError(form.errors) return list_courses( @@ -356,6 +367,12 @@ class CourseListView(DeveloperErrorViewMixin, ListAPIView): mobile_search=form.cleaned_data.get('mobile_search', False), ) + def post(self, request, *args, **kwargs): + """ + POST courses filter. + """ + return self.list(request, *args, **kwargs) + class CourseIdListUserThrottle(UserRateThrottle): """Limit the number of requests users can make to the course list id API."""