Create a staff-only API to list all course enrollments
This data can be used by external frontends or other apps. The list of course enrollments can optionally be filtered by course id or usernames.
This commit is contained in:
157
common/djangoapps/enrollment/tests/fixtures/course-enrollments-api-list-valid-data.json
vendored
Normal file
157
common/djangoapps/enrollment/tests/fixtures/course-enrollments-api-list-valid-data.json
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
[
|
||||
[
|
||||
{
|
||||
"course_id": "e/d/X"
|
||||
},
|
||||
[
|
||||
{
|
||||
"course_id": "e/d/X",
|
||||
"is_active": true,
|
||||
"mode": "honor",
|
||||
"user": "student1",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
},
|
||||
{
|
||||
"course_id": "e/d/X",
|
||||
"is_active": true,
|
||||
"mode": "honor",
|
||||
"user": "student2",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
{
|
||||
"course_id": "x/y/Z"
|
||||
},
|
||||
[
|
||||
{
|
||||
"course_id": "x/y/Z",
|
||||
"is_active": true,
|
||||
"mode": "verified",
|
||||
"user": "staff",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
},
|
||||
{
|
||||
"course_id": "x/y/Z",
|
||||
"is_active": true,
|
||||
"mode": "honor",
|
||||
"user": "student2",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
},
|
||||
{
|
||||
"course_id": "x/y/Z",
|
||||
"is_active": true,
|
||||
"mode": "verified",
|
||||
"user": "student3",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
{
|
||||
"course_id": "x/y/Z",
|
||||
"username": "student2,student3"
|
||||
},
|
||||
[
|
||||
{
|
||||
"course_id": "x/y/Z",
|
||||
"is_active": true,
|
||||
"mode": "honor",
|
||||
"user": "student2",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
},
|
||||
{
|
||||
"course_id": "x/y/Z",
|
||||
"is_active": true,
|
||||
"mode": "verified",
|
||||
"user": "student3",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
{
|
||||
"course_id": "x/y/Z",
|
||||
"username": "student1,student2"
|
||||
},
|
||||
[
|
||||
{
|
||||
"course_id": "x/y/Z",
|
||||
"is_active": true,
|
||||
"mode": "honor",
|
||||
"user": "student2",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
{
|
||||
"username": "student2,staff"
|
||||
},
|
||||
[
|
||||
{
|
||||
"course_id": "x/y/Z",
|
||||
"is_active": true,
|
||||
"mode": "verified",
|
||||
"user": "staff",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
},
|
||||
{
|
||||
"course_id": "e/d/X",
|
||||
"is_active": true,
|
||||
"mode": "honor",
|
||||
"user": "student2",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
},
|
||||
{
|
||||
"course_id": "x/y/Z",
|
||||
"is_active": true,
|
||||
"mode": "honor",
|
||||
"user": "student2",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
}
|
||||
]
|
||||
|
||||
],
|
||||
[
|
||||
null,
|
||||
[
|
||||
{
|
||||
"course_id": "e/d/X",
|
||||
"is_active": true,
|
||||
"mode": "honor",
|
||||
"user": "student1",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
},
|
||||
{
|
||||
"course_id": "e/d/X",
|
||||
"is_active": true,
|
||||
"mode": "honor",
|
||||
"user": "student2",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
},
|
||||
{
|
||||
"course_id": "x/y/Z",
|
||||
"is_active": true,
|
||||
"mode": "verified",
|
||||
"user": "student3",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
},
|
||||
{
|
||||
"course_id": "x/y/Z",
|
||||
"is_active": true,
|
||||
"mode": "honor",
|
||||
"user": "student2",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
},
|
||||
{
|
||||
"course_id": "x/y/Z",
|
||||
"is_active": true,
|
||||
"mode": "verified",
|
||||
"user": "staff",
|
||||
"created": "2018-01-01T00:00:01Z"
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
@@ -16,6 +16,7 @@ from django.core.handlers.wsgi import WSGIRequest
|
||||
from django.urls import reverse
|
||||
from django.test import Client
|
||||
from django.test.utils import override_settings
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
@@ -24,7 +25,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from course_modes.tests.factories import CourseModeFactory
|
||||
from enrollment import api
|
||||
from enrollment import api, data
|
||||
from enrollment.errors import CourseEnrollmentError
|
||||
from enrollment.views import EnrollmentUserThrottle
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
@@ -1522,3 +1523,162 @@ class UserRoleTest(ModuleStoreTestCase):
|
||||
}
|
||||
response_data = json.loads(response.content)
|
||||
self.assertEqual(response_data, expected_response)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class CourseEnrollmentsApiListTest(APITestCase, ModuleStoreTestCase):
|
||||
"""
|
||||
Test the course enrollments list API.
|
||||
"""
|
||||
shard = 3
|
||||
CREATED_DATA = datetime.datetime(2018, 1, 1, 0, 0, 1, tzinfo=pytz.UTC)
|
||||
|
||||
def setUp(self):
|
||||
super(CourseEnrollmentsApiListTest, self).setUp()
|
||||
self.rate_limit_config = RateLimitConfiguration.current()
|
||||
self.rate_limit_config.enabled = False
|
||||
self.rate_limit_config.save()
|
||||
|
||||
throttle = EnrollmentUserThrottle()
|
||||
self.rate_limit, __ = throttle.parse_rate(throttle.rate)
|
||||
|
||||
self.course = CourseFactory.create(org='e', number='d', run='X', emit_signals=True)
|
||||
self.course2 = CourseFactory.create(org='x', number='y', run='Z', emit_signal=True)
|
||||
|
||||
for mode_slug in ('honor', 'verified', 'audit'):
|
||||
CourseModeFactory.create(
|
||||
course_id=self.course.id,
|
||||
mode_slug=mode_slug,
|
||||
mode_display_name=mode_slug
|
||||
)
|
||||
|
||||
self.staff_user = AdminFactory(
|
||||
username='staff',
|
||||
email='staff@example.com',
|
||||
password='edx'
|
||||
)
|
||||
|
||||
self.student1 = UserFactory(
|
||||
username='student1',
|
||||
email='student1@example.com',
|
||||
password='edx'
|
||||
)
|
||||
|
||||
self.student2 = UserFactory(
|
||||
username='student2',
|
||||
email='student2@example.com',
|
||||
password='edx'
|
||||
)
|
||||
|
||||
self.student3 = UserFactory(
|
||||
username='student3',
|
||||
email='student3@example.com',
|
||||
password='edx'
|
||||
)
|
||||
|
||||
with freeze_time(self.CREATED_DATA):
|
||||
data.create_course_enrollment(
|
||||
self.student1.username,
|
||||
unicode(self.course.id),
|
||||
'honor',
|
||||
True
|
||||
)
|
||||
data.create_course_enrollment(
|
||||
self.student2.username,
|
||||
unicode(self.course.id),
|
||||
'honor',
|
||||
True
|
||||
)
|
||||
data.create_course_enrollment(
|
||||
self.student3.username,
|
||||
unicode(self.course2.id),
|
||||
'verified',
|
||||
True
|
||||
)
|
||||
data.create_course_enrollment(
|
||||
self.student2.username,
|
||||
unicode(self.course2.id),
|
||||
'honor',
|
||||
True
|
||||
)
|
||||
data.create_course_enrollment(
|
||||
self.staff_user.username,
|
||||
unicode(self.course2.id),
|
||||
'verified',
|
||||
True
|
||||
)
|
||||
self.url = reverse('courseenrollmentsapilist')
|
||||
|
||||
def _login_as_staff(self):
|
||||
self.client.login(username=self.staff_user.username, password='edx')
|
||||
|
||||
def _make_request(self, query_params=None):
|
||||
return self.client.get(self.url, query_params)
|
||||
|
||||
def _assert_list_of_enrollments(self, query_params=None, expected_status=status.HTTP_200_OK, error_fields=None):
|
||||
"""
|
||||
Make a request to the CourseEnrolllmentApiList endpoint and run assertions on the response
|
||||
using the optional parameters 'query_params', 'expected_status' and 'error_fields'.
|
||||
"""
|
||||
response = self._make_request(query_params)
|
||||
self.assertEqual(response.status_code, expected_status)
|
||||
content = json.loads(response.content)
|
||||
if error_fields is not None:
|
||||
self.assertIn('field_errors', content)
|
||||
for error_field in error_fields:
|
||||
self.assertIn(error_field, content['field_errors'])
|
||||
return content
|
||||
|
||||
def test_user_not_authenticated(self):
|
||||
self.client.logout()
|
||||
response = self.client.get(self.url, {'course_id': self.course.id})
|
||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
def test_user_not_authorized(self):
|
||||
self.client.login(username=self.student1.username, password='edx')
|
||||
response = self.client.get(self.url, {'course_id': self.course.id})
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
@ddt.data(
|
||||
({'course_id': '1'}, ['course_id', ]),
|
||||
({'course_id': '1', 'username': 'staff'}, ['course_id', ]),
|
||||
({'username': '1*2'}, ['username', ]),
|
||||
({'username': '1*2', 'course_id': 'org.0/course_0/Run_0'}, ['username', ]),
|
||||
({'username': '1*2', 'course_id': '1'}, ['username', 'course_id']),
|
||||
({'username': ','.join(str(x) for x in range(101))}, ['username', ])
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_query_string_parameters_invalid_errors(self, query_params, error_fields):
|
||||
self._login_as_staff()
|
||||
self._assert_list_of_enrollments(query_params, status.HTTP_400_BAD_REQUEST, error_fields)
|
||||
|
||||
@ddt.data(
|
||||
# Non-existent user
|
||||
({'username': 'nobody'}, ),
|
||||
({'username': 'nobody', 'course_id': 'e/d/X'}, ),
|
||||
|
||||
# Non-existent course
|
||||
({'course_id': 'a/b/c'}, ),
|
||||
({'course_id': 'a/b/c', 'username': 'student1'}, ),
|
||||
|
||||
# Non-existent course and user
|
||||
({'course_id': 'a/b/c', 'username': 'dummy'}, )
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_non_existent_course_user(self, query_params):
|
||||
self._login_as_staff()
|
||||
content = self._assert_list_of_enrollments(query_params, status.HTTP_200_OK)
|
||||
self.assertEqual(len(content['results']), 0)
|
||||
|
||||
@ddt.file_data('fixtures/course-enrollments-api-list-valid-data.json')
|
||||
@ddt.unpack
|
||||
def test_response_valid_queries(self, args):
|
||||
query_params = args[0]
|
||||
expected_results = args[1]
|
||||
|
||||
self._login_as_staff()
|
||||
content = self._assert_list_of_enrollments(query_params, status.HTTP_200_OK)
|
||||
results = content['results']
|
||||
|
||||
self.assertItemsEqual(results, expected_results)
|
||||
|
||||
Reference in New Issue
Block a user