diff --git a/openedx/core/djangoapps/enrollments/forms.py b/openedx/core/djangoapps/enrollments/forms.py index 4c691ae704..e87210b3fe 100644 --- a/openedx/core/djangoapps/enrollments/forms.py +++ b/openedx/core/djangoapps/enrollments/forms.py @@ -18,6 +18,7 @@ class CourseEnrollmentsApiListForm(Form): MAX_INPUT_COUNT = 100 username = CharField(required=False) course_id = CharField(required=False) + course_ids = CharField(required=False) email = CharField(required=False) def clean_course_id(self): @@ -51,6 +52,24 @@ class CourseEnrollmentsApiListForm(Form): return usernames return usernames_csv_string + def clean_course_ids(self): + """ + Validate a string of comma-separated course IDs and return a list of course IDs. + """ + course_ids_csv_string = self.cleaned_data.get('course_ids') + if course_ids_csv_string: + course_ids = course_ids_csv_string.split(',') + if len(course_ids) > self.MAX_INPUT_COUNT: + raise ValidationError( + "Too many course_ids in a single request - {}. A maximum of {} is allowed".format( + len(course_ids), + self.MAX_INPUT_COUNT, + ) + ) + return course_ids + + return course_ids_csv_string + def clean_email(self): """ Validate a string of comma-separated emails and return a list of emails. diff --git a/openedx/core/djangoapps/enrollments/tests/fixtures/course-enrollments-api-list-valid-data.json b/openedx/core/djangoapps/enrollments/tests/fixtures/course-enrollments-api-list-valid-data.json index 6108d92efd..65b7dfdfd0 100644 --- a/openedx/core/djangoapps/enrollments/tests/fixtures/course-enrollments-api-list-valid-data.json +++ b/openedx/core/djangoapps/enrollments/tests/fixtures/course-enrollments-api-list-valid-data.json @@ -210,5 +210,69 @@ "created": "2018-01-01T00:00:01Z" } ] + ], + [ + { + "course_ids": "course-v1:e+d+X,x+y+Z" + }, + [ + { + "course_id": "course-v1:e+d+X", + "is_active": true, + "mode": "honor", + "user": "student1", + "created": "2018-01-01T00:00:01Z" + }, + { + "course_id": "course-v1:e+d+X", + "is_active": true, + "mode": "honor", + "user": "student2", + "created": "2018-01-01T00:00:01Z" + }, + { + "course_id": "course-v1:x+y+Z", + "is_active": true, + "mode": "verified", + "user": "staff", + "created": "2018-01-01T00:00:01Z" + }, + { + "course_id": "course-v1:x+y+Z", + "is_active": true, + "mode": "honor", + "user": "student2", + "created": "2018-01-01T00:00:01Z" + }, + { + "course_id": "course-v1:x+y+Z", + "is_active": true, + "mode": "verified", + "user": "student3", + "created": "2018-01-01T00:00:01Z" + } + ] + ], + [ + { + "course_ids": "course-v1:e+d+X,x+y+Z", + "username": "student2" + }, + [ + { + "course_id": "course-v1:e+d+X", + "is_active": true, + "mode": "honor", + "user": "student2", + "created": "2018-01-01T00:00:01Z" + }, + { + "course_id": "course-v1:x+y+Z", + "is_active": true, + "mode": "honor", + "user": "student2", + "created": "2018-01-01T00:00:01Z" + } + ] ] ] diff --git a/openedx/core/djangoapps/enrollments/views.py b/openedx/core/djangoapps/enrollments/views.py index 8f4c1f7de0..e857a0d02d 100644 --- a/openedx/core/djangoapps/enrollments/views.py +++ b/openedx/core/djangoapps/enrollments/views.py @@ -11,6 +11,7 @@ from django.core.exceptions import ( # lint-amnesty, pylint: disable=wrong-impo ValidationError, ) from django.db import IntegrityError # lint-amnesty, pylint: disable=wrong-import-order +from django.db.models import Q # lint-amnesty, pylint: disable=wrong-import-order from django.utils.decorators import method_decorator # lint-amnesty, pylint: disable=wrong-import-order from edx_rest_framework_extensions.auth.jwt.authentication import ( JwtAuthentication, @@ -934,6 +935,8 @@ class CourseEnrollmentsApiListView(DeveloperErrorViewMixin, ListAPIView): GET /api/enrollment/v1/enrollments?course_id={course_id} + GET /api/enrollment/v1/enrollments?course_ids={course_id},{course_id},{course_id} + GET /api/enrollment/v1/enrollments?username={username},{username},{username} GET /api/enrollment/v1/enrollments?course_id={course_id}&username={username} @@ -945,6 +948,10 @@ class CourseEnrollmentsApiListView(DeveloperErrorViewMixin, ListAPIView): * course_id: Filters the result to course enrollments for the course corresponding to the given course ID. The value must be URL encoded. Optional. + * course_ids: List of comma-separated course IDs. Filters the result to course enrollments + for the courses corresponding to the given course IDs. Course IDs could be course run IDs + or course IDs. The value must be URL encoded. Optional. + * username: List of comma-separated usernames. Filters the result to the course enrollments of the given users. Optional. @@ -1011,13 +1018,20 @@ class CourseEnrollmentsApiListView(DeveloperErrorViewMixin, ListAPIView): if not form.is_valid(): raise ValidationError(form.errors) - queryset = CourseEnrollment.objects.all() + queryset = CourseEnrollment.objects.all().select_related("user", "course") course_id = form.cleaned_data.get("course_id") + course_ids = form.cleaned_data.get("course_ids") usernames = form.cleaned_data.get("username") emails = form.cleaned_data.get("email") if course_id: queryset = queryset.filter(course__id=course_id) + if course_ids: + # Handles the case if parent course ID is sent rather than course run ID + query = Q() + for cid in course_ids: + query |= Q(course__id__icontains=cid) + queryset = queryset.filter(query) if usernames: queryset = queryset.filter(user__username__in=usernames) if emails: