From a86e8d4f194b74a51b6b6cf144aff271b6053449 Mon Sep 17 00:00:00 2001 From: Simon Chen Date: Mon, 24 Jun 2019 15:40:06 -0400 Subject: [PATCH] EDUCATOR-4361 Add PUT api endpoint to the edx-plaform program course enrollment view --- .../api/v1/tests/test_views.py | 67 ++++++++++++++----- .../program_enrollments/api/v1/views.py | 33 ++++++++- 2 files changed, 83 insertions(+), 17 deletions(-) diff --git a/lms/djangoapps/program_enrollments/api/v1/tests/test_views.py b/lms/djangoapps/program_enrollments/api/v1/tests/test_views.py index adfa913096..0265d2975f 100644 --- a/lms/djangoapps/program_enrollments/api/v1/tests/test_views.py +++ b/lms/djangoapps/program_enrollments/api/v1/tests/test_views.py @@ -574,16 +574,17 @@ class CourseEnrollmentPostTests(BaseCourseEnrollmentTestsMixin, APITestCase): @ddt.ddt -class CourseEnrollmentPatchTests(BaseCourseEnrollmentTestsMixin, APITestCase): - """ Tests for course enrollment PATCH """ - - def request(self, path, data): - return self.client.patch(path, data, format='json') +class CourseEnrollmentModificationTestBase(BaseCourseEnrollmentTestsMixin): + """ + Base class for both the PATCH and PUT endpoints for Course Enrollment API + Children needs to implement assert_user_not_enrolled_test_result and + setup_change_test_data + """ def test_207_multistatus(self): self.create_program_and_course_enrollments('learner-1') - post_data = [self.learner_enrollment("learner-1"), self.learner_enrollment("learner-2")] - response = self.request(self.default_url, post_data) + mod_data = [self.learner_enrollment("learner-1"), self.learner_enrollment("learner-2")] + response = self.request(self.default_url, mod_data) self.assertEqual(207, response.status_code) self.assertDictEqual( {'learner-1': CourseStatuses.ACTIVE, 'learner-2': CourseStatuses.NOT_IN_PROGRAM}, @@ -594,8 +595,13 @@ class CourseEnrollmentPatchTests(BaseCourseEnrollmentTestsMixin, APITestCase): self.create_program_enrollment('learner-1') patch_data = [self.learner_enrollment('learner-1')] response = self.request(self.default_url, patch_data) - self.assertEqual(422, response.status_code) - self.assertDictEqual({'learner-1': CourseStatuses.NOT_FOUND}, response.data) + self.assert_user_not_enrolled_test_result(response) + + def assert_user_not_enrolled_test_result(self, response): + pass + + def setup_change_test_data(self, initial_statuses): + pass @ddt.data( ('active', 'inactive', 'active', 'inactive'), @@ -604,17 +610,14 @@ class CourseEnrollmentPatchTests(BaseCourseEnrollmentTestsMixin, APITestCase): ('inactive', 'inactive', 'inactive', 'inactive'), ) def test_change_status(self, initial_statuses): - self.create_program_and_course_enrollments('learner-1', course_status=initial_statuses[0]) - self.create_program_and_course_enrollments('learner-2', course_status=initial_statuses[1]) - self.create_program_and_course_enrollments('learner-3', course_status=initial_statuses[2], user=None) - self.create_program_and_course_enrollments('learner-4', course_status=initial_statuses[3], user=None) - patch_data = [ + self.setup_change_test_data(initial_statuses) + mod_data = [ self.learner_enrollment('learner-1', 'inactive'), self.learner_enrollment('learner-2', 'active'), self.learner_enrollment('learner-3', 'inactive'), self.learner_enrollment('learner-4', 'active'), ] - response = self.request(self.default_url, patch_data) + response = self.request(self.default_url, mod_data) self.assertEqual(200, response.status_code) self.assertDictEqual( { @@ -631,6 +634,40 @@ class CourseEnrollmentPatchTests(BaseCourseEnrollmentTestsMixin, APITestCase): self.assert_program_course_enrollment('learner-4', 'active', False) +class CourseEnrollmentPatchTests(CourseEnrollmentModificationTestBase, APITestCase): + """ Tests for course enrollment PATCH """ + + def request(self, path, data): + return self.client.patch(path, data, format='json') + + def assert_user_not_enrolled_test_result(self, response): + self.assertEqual(422, response.status_code) + self.assertDictEqual({'learner-1': CourseStatuses.NOT_FOUND}, response.data) + + def setup_change_test_data(self, initial_statuses): + self.create_program_and_course_enrollments('learner-1', course_status=initial_statuses[0]) + self.create_program_and_course_enrollments('learner-2', course_status=initial_statuses[1]) + self.create_program_and_course_enrollments('learner-3', course_status=initial_statuses[2], user=None) + self.create_program_and_course_enrollments('learner-4', course_status=initial_statuses[3], user=None) + + +class CourseEnrollmentPutTests(CourseEnrollmentModificationTestBase, APITestCase): + """ Tests for course enrollment PUT """ + + def request(self, path, data): + return self.client.put(path, data, format='json') + + def assert_user_not_enrolled_test_result(self, response): + self.assertEqual(200, response.status_code) + self.assertDictEqual({'learner-1': CourseStatuses.ACTIVE}, response.data) + + def setup_change_test_data(self, initial_statuses): + self.create_program_and_course_enrollments('learner-1', course_status=initial_statuses[0]) + self.create_program_enrollment('learner-2') + self.create_program_enrollment('learner-3', user=None) + self.create_program_and_course_enrollments('learner-4', course_status=initial_statuses[3], user=None) + + class ProgramCourseEnrollmentListTest(ListViewTestMixin, APITestCase): """ Tests for GET calls to the Program Course Enrollments API. diff --git a/lms/djangoapps/program_enrollments/api/v1/views.py b/lms/djangoapps/program_enrollments/api/v1/views.py index 2ca6e3f57a..cdbf0c7d70 100644 --- a/lms/djangoapps/program_enrollments/api/v1/views.py +++ b/lms/djangoapps/program_enrollments/api/v1/views.py @@ -588,13 +588,13 @@ class ProgramCourseEnrollmentsView(DeveloperErrorViewMixin, ProgramCourseRunSpec Path: ``/api/program_enrollments/v1/programs/{program_uuid}/courses/{course_id}/enrollments/`` - Accepts: [GET, POST] + Accepts: [GET, POST, PATCH, PUT] For GET requests, the path can contain an optional `page_size?=N` query parameter. The default page size is 100. ------------------------------------------------------------------------------------ - POST + POST, PATCH, PUT ------------------------------------------------------------------------------------ **Returns** @@ -702,6 +702,19 @@ class ProgramCourseEnrollmentsView(DeveloperErrorViewMixin, ProgramCourseRunSpec self.modify_learner_enrollment_status ) + @verify_program_exists + @verify_course_exists_and_in_program + # pylint: disable=unused-argument + def put(self, request, program_uuid=None, course_id=None): + """ + Create or Update the program course enrollments of a list of learners + """ + return self.create_or_modify_enrollments( + request, + program_uuid, + self.create_or_update_learner_enrollment + ) + def create_or_modify_enrollments(self, request, program_uuid, operation): """ Process a list of program course enrollment request objects @@ -812,6 +825,22 @@ class ProgramCourseEnrollmentsView(DeveloperErrorViewMixin, ProgramCourseRunSpec return CourseEnrollmentResponseStatuses.NOT_FOUND return program_course_enrollment.change_status(enrollment_request['status']) + def create_or_update_learner_enrollment(self, enrollment_request, program_enrollment, program_course_enrollment): + """ + Attempts to create or update the specified user's enrollment in the given course + in the given program + """ + if program_course_enrollment is None: + # create the course enrollment + return ProgramCourseEnrollment.create_program_course_enrollment( + program_enrollment, + self.course_key, + enrollment_request['status'] + ) + else: + # Update course enrollment + return program_course_enrollment.change_status(enrollment_request['status']) + class ProgramCourseEnrollmentOverviewView(DeveloperErrorViewMixin, ProgramSpecificViewMixin, APIView): """