def post(self, request): email = request.DATA.get('email') course_id = request.DATA.get('course_id') mode = request.DATA.get('mode', CourseMode.HONOR) if not course_id: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"Course ID must be specified to create a new enrollment."} ) try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"No course '{course_id}' found for enrollment".format(course_id=course_id)} ) if not email: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"User email must be specified to create a new enrollment."} ) try: user = User.objects.get(email=email) except ObjectDoesNotExist: return Response( status=status.HTTP_406_NOT_ACCEPTABLE, data={ 'message': u'The user {} does not exist.'.format(email) } ) try: enrollment = api.get_enrollment(user.username, unicode(course_id)) if not enrollment: api.add_enrollment(user.username, unicode(course_id), mode=mode) return Response(status=status.HTTP_201_CREATED) return Response(status=status.HTTP_200_OK) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) except CourseEnrollmentExistsError as error: return Response(data=error.enrollment) except CourseEnrollmentError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"An error occurred while creating the new course enrollment for user " u"'{username}' in course '{course_id}'" ).format(username=user.username, course_id=course_id) } )
def test_get_all_enrollments(self, enrollments): for enrollment in enrollments: fake_data_api.add_course(enrollment['course_id'], course_modes=enrollment['course_modes']) api.add_enrollment(self.USERNAME, enrollment['course_id'], enrollment['mode']) result = api.get_enrollments(self.USERNAME) self.assertEqual(len(enrollments), len(result)) for result_enrollment in result: self.assertIn( result_enrollment['course']['course_id'], [enrollment['course_id'] for enrollment in enrollments] )
def handle(self, *args, **options): """ Get and enroll a user in the given course. Mode is optional and defers to the enrollment API for defaults. """ email = options['email'][0] course = options['course'][0] user = User.objects.get(email=email) try: add_enrollment(user.username, course) except CourseEnrollmentExistsError: # If the user is already enrolled in the course, do nothing. pass
def enroll_user_in_review_course_if_needed(user, current_course): ''' If the user is not enrolled in the review version of the course, they are unable to see any of the problems. This ensures they are enrolled so they can see review problems. Parameters: user (User): the current user interacting with the review XBlock current_course (CourseLocator): The course the learner is currently in ''' enrollment_course_id = ENROLLMENT_COURSE_MAPPING[str(current_course)] enrollment_status = get_enrollment(user.username, enrollment_course_id) if not enrollment_status: add_enrollment(user.username, enrollment_course_id) elif not enrollment_status['is_active']: update_enrollment(user.username, enrollment_course_id, is_active=True)
def test_update_enrollment_attributes(self): # Add fake course enrollment information to the fake data API fake_data_api.add_course( self.COURSE_ID, course_modes=['honor', 'verified', 'audit', 'credit']) # Enroll in the course and verify the URL we get sent to result = api.add_enrollment(self.USERNAME, self.COURSE_ID, mode='audit') get_result = api.get_enrollment(self.USERNAME, self.COURSE_ID) self.assertEquals(result, get_result) enrollment_attributes = [{ "namespace": "credit", "name": "provider_id", "value": "hogwarts", }] result = api.update_enrollment( self.USERNAME, self.COURSE_ID, mode='credit', enrollment_attributes=enrollment_attributes) self.assertEquals('credit', result['mode']) attributes = api.get_enrollment_attributes(self.USERNAME, self.COURSE_ID) self.assertEquals(enrollment_attributes[0], attributes[0])
def get_course_enrollment(request, course_id=None): """Create, read, or update enrollment information for a student. HTTP Endpoint for all CRUD operations for a student course enrollment. Allows creation, reading, and updates of the current enrollment for a particular course. Args: request (Request): To get current course enrollment information, a GET request will return information for the current user and the specified course. A POST request will create a new course enrollment for the current user. If 'mode' or 'deactivate' are found in the POST parameters, the mode can be modified, or the enrollment can be deactivated. course_id (str): URI element specifying the course location. Enrollment information will be returned, created, or updated for this particular course. Return: A JSON serialized representation of the course enrollment. If this is a new or modified enrollment, the returned enrollment will reflect all changes. """ try: if course_id and request.method == 'POST': return Response( api.add_enrollment(request.user.username, course_id)) else: return Response( api.get_enrollment(request.user.username, course_id)) except api.CourseModeNotFoundError as error: return Response(status=status.HTTP_400_BAD_REQUEST, data=error.data) except NonExistentCourseError: return Response(status=status.HTTP_400_BAD_REQUEST) except api.EnrollmentNotFoundError: return Response(status=status.HTTP_400_BAD_REQUEST) except CourseEnrollmentException: return Response(status=status.HTTP_400_BAD_REQUEST)
def get_course_enrollment(request, course_id=None): """Create, read, or update enrollment information for a student. HTTP Endpoint for all CRUD operations for a student course enrollment. Allows creation, reading, and updates of the current enrollment for a particular course. Args: request (Request): To get current course enrollment information, a GET request will return information for the current user and the specified course. A POST request will create a new course enrollment for the current user. If 'mode' or 'deactivate' are found in the POST parameters, the mode can be modified, or the enrollment can be deactivated. course_id (str): URI element specifying the course location. Enrollment information will be returned, created, or updated for this particular course. Return: A JSON serialized representation of the course enrollment. If this is a new or modified enrollment, the returned enrollment will reflect all changes. """ try: if course_id and request.method == 'POST': return Response(api.add_enrollment(request.user.username, course_id)) else: return Response(api.get_enrollment(request.user.username, course_id)) except api.CourseModeNotFoundError as error: return Response(status=status.HTTP_400_BAD_REQUEST, data=error.data) except NonExistentCourseError: return Response(status=status.HTTP_400_BAD_REQUEST) except api.EnrollmentNotFoundError: return Response(status=status.HTTP_400_BAD_REQUEST) except CourseEnrollmentException: return Response(status=status.HTTP_400_BAD_REQUEST)
def post(self, request): """ Enrolls the currently logged in user in a course. """ user = request.DATA.get('user', request.user.username) if not user: user = request.user.username if user != request.user.username: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if 'course_details' not in request.DATA or 'course_id' not in request.DATA[ 'course_details']: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"Course ID must be specified to create a new enrollment." }) course_id = request.DATA['course_details']['course_id'] try: return Response(api.add_enrollment(user, course_id)) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": (u"The course mode '{mode}' is not available for course '{course_id}'." ).format(mode="honor", course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format( course_id=course_id) }) except CourseEnrollmentError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": (u"An error occurred while creating the new course enrollment for user " u"'{user}' in course '{course_id}'").format( user=user, course_id=course_id) }) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format( course_id=course_id) })
def assert_add_modes_with_enrollment(self, enrollment_mode): """ Dry method for adding fake course enrollment information to fake data API and enroll the student in the course. """ fake_data_api.add_course(self.COURSE_ID, course_modes=['honor', 'verified', 'audit']) result = api.add_enrollment(self.USERNAME, self.COURSE_ID, mode=enrollment_mode) get_result = api.get_enrollment(self.USERNAME, self.COURSE_ID) self.assertEquals(result, get_result) # set the course verify mode as expire. fake_data_api.set_expired_mode(self.COURSE_ID)
def post(self, request): """ Enrolls the currently logged in user in a course. """ user = request.DATA.get('user', request.user.username) if not user: user = request.user.username if user != request.user.username: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if 'course_details' not in request.DATA or 'course_id' not in request.DATA['course_details']: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"Course ID must be specified to create a new enrollment."} ) course_id = request.DATA['course_details']['course_id'] try: return Response(api.add_enrollment(user, course_id)) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"The course mode '{mode}' is not available for course '{course_id}'." ).format(mode="honor", course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) except CourseEnrollmentError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"An error occurred while creating the new course enrollment for user " u"'{user}' in course '{course_id}'" ).format(user=user, course_id=course_id) } ) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } )
def enroll(self, user, course_id, course_key): """ Enroll users """ try: try: add_enrollment(user.username, course_id) except CourseEnrollmentExistsError: pass course = modulestore().get_course(course_key) if course.has_started(): return redirect('course_root', unicode(course_key)) else: return redirect('dashboard') except InvalidKeyError: return forus_error_redirect(_("Invalid course id")) except Exception as error: # pylint: disable=broad-except, invalid-name log.exception(error) return forus_error_redirect(_("Could not enroll"))
def test_enroll(self, course_modes, mode): # Add a fake course enrollment information to the fake data API fake_data_api.add_course(self.COURSE_ID, course_modes=course_modes) # Enroll in the course and verify the URL we get sent to result = api.add_enrollment(self.USERNAME, self.COURSE_ID, mode=mode) self.assertIsNotNone(result) self.assertEquals(result['student'], self.USERNAME) self.assertEquals(result['course']['course_id'], self.COURSE_ID) self.assertEquals(result['mode'], mode) get_result = api.get_enrollment(self.USERNAME, self.COURSE_ID) self.assertEquals(result, get_result)
def test_enroll_no_mode_success(self, course_modes, expected_mode): # Add a fake course enrollment information to the fake data API fake_data_api.add_course(self.COURSE_ID, course_modes=course_modes) with patch('enrollment.api.CourseMode.modes_for_course') as mock_modes_for_course: mock_course_modes = [Mock(slug=mode) for mode in course_modes] mock_modes_for_course.return_value = mock_course_modes # Enroll in the course and verify the URL we get sent to result = api.add_enrollment(self.USERNAME, self.COURSE_ID) self.assertIsNotNone(result) self.assertEquals(result['student'], self.USERNAME) self.assertEquals(result['course']['course_id'], self.COURSE_ID) self.assertEquals(result['mode'], expected_mode)
def test_update_enrollment(self): # Add fake course enrollment information to the fake data API fake_data_api.add_course(self.COURSE_ID, course_modes=['honor', 'verified', 'audit']) # Enroll in the course and verify the URL we get sent to result = api.add_enrollment(self.USERNAME, self.COURSE_ID, mode='audit') get_result = api.get_enrollment(self.USERNAME, self.COURSE_ID) self.assertEquals(result, get_result) result = api.update_enrollment(self.USERNAME, self.COURSE_ID, mode='honor') self.assertEquals('honor', result['mode']) result = api.update_enrollment(self.USERNAME, self.COURSE_ID, mode='verified') self.assertEquals('verified', result['mode'])
def test_enroll_no_mode_success(self, course_modes, expected_mode): # Add a fake course enrollment information to the fake data API fake_data_api.add_course(self.COURSE_ID, course_modes=course_modes) with patch('enrollment.api.CourseMode.modes_for_course' ) as mock_modes_for_course: mock_course_modes = [Mock(slug=mode) for mode in course_modes] mock_modes_for_course.return_value = mock_course_modes # Enroll in the course and verify the URL we get sent to result = api.add_enrollment(self.USERNAME, self.COURSE_ID) self.assertIsNotNone(result) self.assertEquals(result['student'], self.USERNAME) self.assertEquals(result['course']['course_id'], self.COURSE_ID) self.assertEquals(result['mode'], expected_mode)
def test_update_enrollment_attributes(self): # Add fake course enrollment information to the fake data API fake_data_api.add_course(self.COURSE_ID, course_modes=['honor', 'verified', 'audit', 'credit']) # Enroll in the course and verify the URL we get sent to result = api.add_enrollment(self.USERNAME, self.COURSE_ID, mode='audit') get_result = api.get_enrollment(self.USERNAME, self.COURSE_ID) self.assertEquals(result, get_result) enrollment_attributes = [ { "namespace": "credit", "name": "provider_id", "value": "hogwarts", } ] result = api.update_enrollment( self.USERNAME, self.COURSE_ID, mode='credit', enrollment_attributes=enrollment_attributes ) self.assertEquals('credit', result['mode']) attributes = api.get_enrollment_attributes(self.USERNAME, self.COURSE_ID) self.assertEquals(enrollment_attributes[0], attributes[0])
def create_user_enrollment(username, course_id): mode = CourseMode.HONOR is_active = True try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: raise InvalidCourseId() try: user = User.objects.get(username=username) except ObjectDoesNotExist: raise UserNotFound() enrollment = api.get_enrollment(username, unicode(course_id)) if enrollment: raise EnrollmentAlreadyExists() enrollment = api.add_enrollment(username, unicode(course_id), mode=mode, is_active=is_active) return enrollment
def post(self, request): """Create a new enrollment Creates a new enrollment based on the data posted. Currently all that can be specified is the course id. All other attributes will be determined by the server, and cannot be updated through this endpoint. By default, this will attempt to create the enrollment with course mode 'honor'. If the course does not have an 'honor' course mode, it will fail as a bad request and list the available course modes. Args: request (Request): The POST request to create a new enrollment. POST DATA should contain 'course_details' with an attribute 'course_id' to identify which course to enroll in. 'user' may be specified as well, but must match the username of the authenticated user. Ex. {'user': '******', 'course_details': { 'course_id': 'edx/demo/T2014' } } Returns: A JSON serialized representation of the user's new course enrollment. """ user = request.DATA.get('user', request.user.username) if not user: user = request.user.username if user != request.user.username: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if 'course_details' not in request.DATA or 'course_id' not in request.DATA['course_details']: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"Course ID must be specified to create a new enrollment."} ) course_id = request.DATA['course_details']['course_id'] try: return Response(api.add_enrollment(user, course_id)) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"The course mode '{mode}' is not available for course '{course_id}'." ).format(mode="honor", course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) except CourseEnrollmentError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"An error occurred while creating the new course enrollment for user " u"'{user}' in course '{course_id}'" ).format(user=user, course_id=course_id) } )
def post(self, request): """ Enrolls the currently logged in user in a course. """ # Get the User, Course ID, and Mode from the request. username = request.DATA.get('user', request.user.username) course_id = request.DATA.get('course_details', {}).get('course_id') if not course_id: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"Course ID must be specified to create a new enrollment."} ) try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) mode = request.DATA.get('mode', CourseMode.HONOR) has_api_key_permissions = self.has_api_key_permissions(request) # Check that the user specified is either the same user, or this is a server-to-server request. if not username: username = request.user.username if username != request.user.username and not has_api_key_permissions: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if mode != CourseMode.HONOR and not has_api_key_permissions: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": u"User does not have permission to create enrollment with mode [{mode}].".format( mode=mode ) } ) try: # Lookup the user, instead of using request.user, since request.user may not match the username POSTed. user = User.objects.get(username=username) except ObjectDoesNotExist: return Response( status=status.HTTP_406_NOT_ACCEPTABLE, data={ 'message': u'The user {} does not exist.'.format(username) } ) # Check whether any country access rules block the user from enrollment # We do this at the view level (rather than the Python API level) # because this check requires information about the HTTP request. redirect_url = embargo_api.redirect_if_blocked( course_id, user=user, ip_address=get_ip(request), url=request.path) if redirect_url: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": ( u"Users from this location cannot access the course '{course_id}'." ).format(course_id=course_id), "user_message_url": request.build_absolute_uri(redirect_url) } ) try: # Check if the user is currently enrolled, and if it is the same as the current enrolled mode. We do not # have to check if it is inactive or not, because if it is, we are still upgrading if the mode is different, # and either path will re-activate the enrollment. # # Only server-to-server calls will currently be allowed to modify the mode for existing enrollments. All # other requests will go through add_enrollment(), which will allow creating of new enrollments, and # re-activating enrollments enrollment = api.get_enrollment(username, unicode(course_id)) if has_api_key_permissions and enrollment and enrollment['mode'] != mode: response = api.update_enrollment(username, unicode(course_id), mode=mode) else: response = api.add_enrollment(username, unicode(course_id), mode=mode) email_opt_in = request.DATA.get('email_opt_in', None) if email_opt_in is not None: org = course_id.org update_email_opt_in(request.user, org, email_opt_in) return Response(response) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"The course mode '{mode}' is not available for course '{course_id}'." ).format(mode="honor", course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) except CourseEnrollmentExistsError as error: return Response(data=error.enrollment) except CourseEnrollmentError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"An error occurred while creating the new course enrollment for user " u"'{username}' in course '{course_id}'" ).format(username=username, course_id=course_id) } )
def post(self, request): """Enrolls the currently logged-in user in a course. Server-to-server calls may deactivate or modify the mode of existing enrollments. All other requests go through `add_enrollment()`, which allows creation of new and reactivation of old enrollments. """ # Get the User, Course ID, and Mode from the request. username = request.data.get('user', request.user.username) course_id = request.data.get('course_details', {}).get('course_id') if not course_id: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"Course ID must be specified to create a new enrollment."} ) try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) mode = request.data.get('mode') has_api_key_permissions = self.has_api_key_permissions(request) # Check that the user specified is either the same user, or this is a server-to-server request. if not username: username = request.user.username if username != request.user.username and not has_api_key_permissions: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if mode not in (CourseMode.AUDIT, CourseMode.HONOR, None) and not has_api_key_permissions: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": u"User does not have permission to create enrollment with mode [{mode}].".format( mode=mode ) } ) try: # Lookup the user, instead of using request.user, since request.user may not match the username POSTed. user = User.objects.get(username=username) except ObjectDoesNotExist: return Response( status=status.HTTP_406_NOT_ACCEPTABLE, data={ 'message': u'The user {} does not exist.'.format(username) } ) embargo_response = embargo_api.get_embargo_response(request, course_id, user) if embargo_response: return embargo_response try: is_active = request.data.get('is_active') # Check if the requested activation status is None or a Boolean if is_active is not None and not isinstance(is_active, bool): return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'message': (u"'{value}' is an invalid enrollment activation status.").format(value=is_active) } ) enrollment_attributes = request.data.get('enrollment_attributes') enrollment = api.get_enrollment(username, unicode(course_id)) mode_changed = enrollment and mode is not None and enrollment['mode'] != mode active_changed = enrollment and is_active is not None and enrollment['is_active'] != is_active missing_attrs = [] if enrollment_attributes: actual_attrs = [ u"{namespace}:{name}".format(**attr) for attr in enrollment_attributes ] missing_attrs = set(REQUIRED_ATTRIBUTES.get(mode, [])) - set(actual_attrs) if has_api_key_permissions and (mode_changed or active_changed): if mode_changed and active_changed and not is_active: # if the requester wanted to deactivate but specified the wrong mode, fail # the request (on the assumption that the requester had outdated information # about the currently active enrollment). msg = u"Enrollment mode mismatch: active mode={}, requested mode={}. Won't deactivate.".format( enrollment["mode"], mode ) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) if len(missing_attrs) > 0: msg = u"Missing enrollment attributes: requested mode={} required attributes={}".format( mode, REQUIRED_ATTRIBUTES.get(mode) ) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) response = api.update_enrollment( username, unicode(course_id), mode=mode, is_active=is_active, enrollment_attributes=enrollment_attributes ) else: # Will reactivate inactive enrollments. response = api.add_enrollment(username, unicode(course_id), mode=mode, is_active=is_active) email_opt_in = request.data.get('email_opt_in', None) if email_opt_in is not None: org = course_id.org update_email_opt_in(request.user, org, email_opt_in) log.info('The user [%s] has already been enrolled in course run [%s].', username, course_id) return Response(response) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"The [{mode}] course mode is expired or otherwise unavailable for course run [{course_id}]." ).format(mode=mode, course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) except CourseEnrollmentExistsError as error: log.warning('An enrollment already exists for user [%s] in course run [%s].', username, course_id) return Response(data=error.enrollment) except CourseEnrollmentError: log.exception("An error occurred while creating the new course enrollment for user " "[%s] in course run [%s]", username, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"An error occurred while creating the new course enrollment for user " u"'{username}' in course '{course_id}'" ).format(username=username, course_id=course_id) } ) finally: # Assumes that the ecommerce service uses an API key to authenticate. if has_api_key_permissions: current_enrollment = api.get_enrollment(username, unicode(course_id)) audit_log( 'enrollment_change_requested', course_id=unicode(course_id), requested_mode=mode, actual_mode=current_enrollment['mode'] if current_enrollment else None, requested_activation=is_active, actual_activation=current_enrollment['is_active'] if current_enrollment else None, user_id=user.id )
def post(self, request): """Enrolls the currently logged-in user in a course. Server-to-server calls may deactivate or modify the mode of existing enrollments. All other requests go through `add_enrollment()`, which allows creation of new and reactivation of old enrollments. """ # Get the User, Course ID, and Mode from the request. username = request.DATA.get('user', request.user.username) course_id = request.DATA.get('course_details', {}).get('course_id') if not course_id: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"Course ID must be specified to create a new enrollment."} ) try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) mode = request.DATA.get('mode', CourseMode.HONOR) has_api_key_permissions = self.has_api_key_permissions(request) # Check that the user specified is either the same user, or this is a server-to-server request. if not username: username = request.user.username if username != request.user.username and not has_api_key_permissions: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if mode != CourseMode.HONOR and not has_api_key_permissions: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": u"User does not have permission to create enrollment with mode [{mode}].".format( mode=mode ) } ) try: # Lookup the user, instead of using request.user, since request.user may not match the username POSTed. user = User.objects.get(username=username) except ObjectDoesNotExist: return Response( status=status.HTTP_406_NOT_ACCEPTABLE, data={ 'message': u'The user {} does not exist.'.format(username) } ) embargo_response = embargo_api.get_embargo_response(request, course_id, user) if embargo_response: return embargo_response try: is_active = request.DATA.get('is_active') # Check if the requested activation status is None or a Boolean if is_active is not None and not isinstance(is_active, bool): return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'message': (u"'{value}' is an invalid enrollment activation status.").format(value=is_active) } ) enrollment = api.get_enrollment(username, unicode(course_id)) mode_changed = enrollment and mode is not None and enrollment['mode'] != mode active_changed = enrollment and is_active is not None and enrollment['is_active'] != is_active if has_api_key_permissions and (mode_changed or active_changed): if mode_changed and active_changed and not is_active: # if the requester wanted to deactivate but specified the wrong mode, fail # the request (on the assumption that the requester had outdated information # about the currently active enrollment). msg = u"Enrollment mode mismatch: active mode={}, requested mode={}. Won't deactivate.".format( enrollment["mode"], mode ) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) response = api.update_enrollment(username, unicode(course_id), mode=mode, is_active=is_active) else: # Will reactivate inactive enrollments. response = api.add_enrollment(username, unicode(course_id), mode=mode, is_active=is_active) email_opt_in = request.DATA.get('email_opt_in', None) if email_opt_in is not None: org = course_id.org update_email_opt_in(request.user, org, email_opt_in) return Response(response) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"The course mode '{mode}' is not available for course '{course_id}'." ).format(mode="honor", course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) except CourseEnrollmentExistsError as error: return Response(data=error.enrollment) except CourseEnrollmentError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"An error occurred while creating the new course enrollment for user " u"'{username}' in course '{course_id}'" ).format(username=username, course_id=course_id) } )
def test_data_api_config_error(self): # Enroll in the course and verify the URL we get sent to with pytest.raises(EnrollmentApiLoadError): api.add_enrollment(self.USERNAME, self.COURSE_ID, mode='audit')
def post(self, request): """Enrolls the currently logged-in user in a course. Server-to-server calls may deactivate or modify the mode of existing enrollments. All other requests go through `add_enrollment()`, which allows creation of new and reactivation of old enrollments. """ # Get the User, Course ID, and Mode from the request. username = request.DATA.get('user', request.user.username) course_id = request.DATA.get('course_details', {}).get('course_id') if not course_id: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"Course ID must be specified to create a new enrollment." }) try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format( course_id=course_id) }) mode = request.DATA.get('mode', CourseMode.HONOR) has_api_key_permissions = self.has_api_key_permissions(request) # Check that the user specified is either the same user, or this is a server-to-server request. if not username: username = request.user.username if username != request.user.username and not has_api_key_permissions: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if mode != CourseMode.HONOR and not has_api_key_permissions: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": u"User does not have permission to create enrollment with mode [{mode}]." .format(mode=mode) }) try: # Lookup the user, instead of using request.user, since request.user may not match the username POSTed. user = User.objects.get(username=username) except ObjectDoesNotExist: return Response(status=status.HTTP_406_NOT_ACCEPTABLE, data={ 'message': u'The user {} does not exist.'.format(username) }) # Check whether any country access rules block the user from enrollment # We do this at the view level (rather than the Python API level) # because this check requires information about the HTTP request. redirect_url = embargo_api.redirect_if_blocked( course_id, user=user, ip_address=get_ip(request), url=request.path) if redirect_url: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": (u"Users from this location cannot access the course '{course_id}'." ).format(course_id=course_id), "user_message_url": request.build_absolute_uri(redirect_url) }) try: is_active = request.DATA.get('is_active') # Check if the requested activation status is None or a Boolean if is_active is not None and not isinstance(is_active, bool): return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'message': (u"'{value}' is an invalid enrollment activation status." ).format(value=is_active) }) enrollment = api.get_enrollment(username, unicode(course_id)) if has_api_key_permissions and enrollment and enrollment[ 'mode'] != mode: response = api.update_enrollment(username, unicode(course_id), mode=mode, is_active=is_active) else: # Will reactivate inactive enrollments. response = api.add_enrollment(username, unicode(course_id), mode=mode) email_opt_in = request.DATA.get('email_opt_in', None) if email_opt_in is not None: org = course_id.org update_email_opt_in(request.user, org, email_opt_in) return Response(response) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": (u"The course mode '{mode}' is not available for course '{course_id}'." ).format(mode="honor", course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format( course_id=course_id) }) except CourseEnrollmentExistsError as error: return Response(data=error.enrollment) except CourseEnrollmentError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": (u"An error occurred while creating the new course enrollment for user " u"'{username}' in course '{course_id}'").format( username=username, course_id=course_id) })
def post(self, request): """Enrolls the currently logged-in user in a course. Server-to-server calls may deactivate or modify the mode of existing enrollments. All other requests go through `add_enrollment()`, which allows creation of new and reactivation of old enrollments. """ # Get the User, Course ID, and Mode from the request. username = request.data.get('user', request.user.username) course_id = request.data.get('course_details', {}).get('course_id') if not course_id: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"Course ID must be specified to create a new enrollment."} ) try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) mode = request.data.get('mode') has_api_key_permissions = self.has_api_key_permissions(request) # Check that the user specified is either the same user, or this is a server-to-server request. if not username: username = request.user.username if username != request.user.username and not has_api_key_permissions: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) try: # Lookup the user, instead of using request.user, since request.user may not match the username POSTed. user = User.objects.get(username=username) except ObjectDoesNotExist: return Response( status=status.HTTP_406_NOT_ACCEPTABLE, data={ 'message': u'The user {} does not exist.'.format(username) } ) can_vip_enroll = False if settings.FEATURES.get('ENABLE_MEMBERSHIP_INTEGRATION'): from membership.models import VIPCourseEnrollment can_vip_enroll = VIPCourseEnrollment.can_vip_enroll(user, course_id) is_ecommerce_request = mode not in (CourseMode.AUDIT, CourseMode.HONOR, None) if is_ecommerce_request and not has_api_key_permissions and not can_vip_enroll: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": u"User does not have permission to create enrollment with mode [{mode}].".format( mode=mode ) } ) embargo_response = embargo_api.get_embargo_response(request, course_id, user) if embargo_response: return embargo_response try: is_active = request.data.get('is_active') # Check if the requested activation status is None or a Boolean if is_active is not None and not isinstance(is_active, bool): return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'message': (u"'{value}' is an invalid enrollment activation status.").format(value=is_active) } ) explicit_linked_enterprise = request.data.get('linked_enterprise_customer') if explicit_linked_enterprise and has_api_key_permissions and enterprise_enabled(): enterprise_api_client = EnterpriseApiServiceClient() consent_client = ConsentApiServiceClient() try: enterprise_api_client.post_enterprise_course_enrollment(username, unicode(course_id), None) except EnterpriseApiException as error: log.exception("An unexpected error occurred while creating the new EnterpriseCourseEnrollment " "for user [%s] in course run [%s]", username, course_id) raise CourseEnrollmentError(text_type(error)) kwargs = { 'username': username, 'course_id': unicode(course_id), 'enterprise_customer_uuid': explicit_linked_enterprise, } consent_client.provide_consent(**kwargs) enrollment_attributes = request.data.get('enrollment_attributes') enrollment = api.get_enrollment(username, unicode(course_id)) mode_changed = enrollment and mode is not None and enrollment['mode'] != mode active_changed = enrollment and is_active is not None and enrollment['is_active'] != is_active missing_attrs = [] audit_with_order = False if enrollment_attributes: actual_attrs = [ u"{namespace}:{name}".format(**attr) for attr in enrollment_attributes ] missing_attrs = set(REQUIRED_ATTRIBUTES.get(mode, [])) - set(actual_attrs) audit_with_order = mode == 'audit' and 'order:order_number' in actual_attrs # Remove audit_with_order when no longer needed - implemented for REV-141 if has_api_key_permissions and (mode_changed or active_changed or audit_with_order): if mode_changed and active_changed and not is_active: # if the requester wanted to deactivate but specified the wrong mode, fail # the request (on the assumption that the requester had outdated information # about the currently active enrollment). msg = u"Enrollment mode mismatch: active mode={}, requested mode={}. Won't deactivate.".format( enrollment["mode"], mode ) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) if len(missing_attrs) > 0: msg = u"Missing enrollment attributes: requested mode={} required attributes={}".format( mode, REQUIRED_ATTRIBUTES.get(mode) ) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) response = api.update_enrollment( username, unicode(course_id), mode=mode, is_active=is_active, enrollment_attributes=enrollment_attributes, # If we are updating enrollment by authorized api caller, we should allow expired modes include_expired=has_api_key_permissions ) else: # Will reactivate inactive enrollments. response = api.add_enrollment( username, unicode(course_id), mode=mode, is_active=is_active, enrollment_attributes=enrollment_attributes, user=user, is_ecommerce_request=is_ecommerce_request ) cohort_name = request.data.get('cohort') if cohort_name is not None: cohort = get_cohort_by_name(course_id, cohort_name) add_user_to_cohort(cohort, user) email_opt_in = request.data.get('email_opt_in', None) if email_opt_in is not None: org = course_id.org update_email_opt_in(request.user, org, email_opt_in) log.info('The user [%s] has already been enrolled in course run [%s].', username, course_id) return Response(response) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"The [{mode}] course mode is expired or otherwise unavailable for course run [{course_id}]." ).format(mode=mode, course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) except CourseEnrollmentExistsError as error: log.warning('An enrollment already exists for user [%s] in course run [%s].', username, course_id) return Response(data=error.enrollment) except CourseEnrollmentError: log.exception("An error occurred while creating the new course enrollment for user " "[%s] in course run [%s]", username, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"An error occurred while creating the new course enrollment for user " u"'{username}' in course '{course_id}'" ).format(username=username, course_id=course_id) } ) except CourseUserGroup.DoesNotExist: log.exception('Missing cohort [%s] in course run [%s]', cohort_name, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": "An error occured while adding to cohort [%s]" % cohort_name }) finally: # Assumes that the ecommerce service uses an API key to authenticate. if has_api_key_permissions: current_enrollment = api.get_enrollment(username, unicode(course_id)) audit_log( 'enrollment_change_requested', course_id=unicode(course_id), requested_mode=mode, actual_mode=current_enrollment['mode'] if current_enrollment else None, requested_activation=is_active, actual_activation=current_enrollment['is_active'] if current_enrollment else None, user_id=user.id )
def test_enroll_no_mode_error(self, course_modes): # Add a fake course enrollment information to the fake data API fake_data_api.add_course(self.COURSE_ID, course_modes=course_modes) # Enroll in the course and verify that we raise CourseModeNotFoundError api.add_enrollment(self.USERNAME, self.COURSE_ID)
def test_data_api_config_error(self): # Enroll in the course and verify the URL we get sent to api.add_enrollment(self.USERNAME, self.COURSE_ID, mode='audit')
def post(self, request): """ Enrolls the currently logged in user in a course. """ user = request.DATA.get('user', request.user.username) if not user: user = request.user.username if user != request.user.username and not self.has_api_key_permissions(request): # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if 'course_details' not in request.DATA or 'course_id' not in request.DATA['course_details']: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"Course ID must be specified to create a new enrollment."} ) course_id = request.DATA['course_details']['course_id'] try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) # Check whether any country access rules block the user from enrollment # We do this at the view level (rather than the Python API level) # because this check requires information about the HTTP request. redirect_url = embargo_api.redirect_if_blocked( course_id, user=request.user, ip_address=get_ip(request), url=request.path ) if redirect_url: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": ( u"Users from this location cannot access the course '{course_id}'." ).format(course_id=course_id), "user_message_url": redirect_url } ) try: response = api.add_enrollment(user, unicode(course_id)) email_opt_in = request.DATA.get('email_opt_in', None) if email_opt_in is not None: org = course_id.org user_api.profile.update_email_opt_in(request.user, org, email_opt_in) return Response(response) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"The course mode '{mode}' is not available for course '{course_id}'." ).format(mode="honor", course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) except CourseEnrollmentExistsError as error: return Response(data=error.enrollment) except CourseEnrollmentError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"An error occurred while creating the new course enrollment for user " u"'{user}' in course '{course_id}'" ).format(user=user, course_id=course_id) } )
def _enroll(self, course_key, user): """ Enroll the user in the course. """ add_enrollment(user.username, unicode(course_key))
def post(self, request, *args, **kwargs): """ POST /api/itoo_api/v0/paid_courses/ { "user": "******" "mode": "verified", "course_id": "edX/DemoX/Demo_Course", } """ serializer = ChangeModeStateUserSerializer(data=request.DATA) serializer.is_valid() data = serializer.validated_data username = data.get('user') course_key = data.get('course_key') mode = data.get('mode') if not course_key: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"Course ID must be specified to create a new enrollment." }) try: course_key = CourseKey.from_string(course_key) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_key}' found for enrollment".format( course_key=course_key) }) try: # Lookup the user, instead of using request.user, since request.user may not match the username POSTed. user = User.objects.get(username=username) except ObjectDoesNotExist: return Response(status=status.HTTP_406_NOT_ACCEPTABLE, data={ 'message': u'The user {} does not exist.'.format(username) }) try: is_active = data.get('is_active') # Check if the requested activation status is None or a Boolean if is_active is not None and not isinstance(is_active, bool): return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'message': (u"'{value}' is an invalid enrollment activation status." ).format(value=is_active) }) except: pass enrollment = api.get_enrollment(username, unicode(course_key)) mode_changed = enrollment and mode is not None and enrollment[ 'mode'] != mode active_changed = enrollment and is_active is not None and enrollment[ 'is_active'] != is_active if (mode_changed or active_changed): if mode_changed and active_changed and not is_active: # if the requester wanted to deactivate but specified the wrong mode, fail # the request (on the assumption that the requester had outdated information # about the currently active enrollment). msg = u"Enrollment mode mismatch: active mode={}, requested mode={}. Won't deactivate.".format( enrollment["mode"], mode) logger.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) response = api.update_enrollment(username, unicode(course_key), mode=mode, is_active=is_active) else: # Will reactivate inactive enrollments. response = api.add_enrollment(username, unicode(course_key), mode=mode, is_active=is_active) return Response(response)
def _enroll(self, course_key, user, mode=CourseMode.DEFAULT_MODE_SLUG): """ Enroll the user in the course. """ add_enrollment(user.username, unicode(course_key), mode)
def post(self, request): """ Enrolls the currently logged in user in a course. """ user = request.DATA.get('user', request.user.username) if not user: user = request.user.username if user != request.user.username: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if 'course_details' not in request.DATA or 'course_id' not in request.DATA['course_details']: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"Course ID must be specified to create a new enrollment."} ) course_id = request.DATA['course_details']['course_id'] try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) # Check whether any country access rules block the user from enrollment # We do this at the view level (rather than the Python API level) # because this check requires information about the HTTP request. redirect_url = embargo_api.redirect_if_blocked( course_id, user=request.user, ip_address=get_ip(request), url=request.path ) if redirect_url: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": ( u"Users from this location cannot access the course '{course_id}'." ).format(course_id=course_id), "user_message_url": redirect_url } ) try: response = api.add_enrollment(user, unicode(course_id)) email_opt_in = request.DATA.get('email_opt_in', None) if email_opt_in is not None: org = course_id.org user_api.profile.update_email_opt_in(request.user, org, email_opt_in) return Response(response) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"The course mode '{mode}' is not available for course '{course_id}'." ).format(mode="honor", course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) except CourseEnrollmentExistsError as error: return Response(data=error.enrollment) except CourseEnrollmentError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"An error occurred while creating the new course enrollment for user " u"'{user}' in course '{course_id}'" ).format(user=user, course_id=course_id) } )
def test_prof_ed_enroll(self): # Add a fake course enrollment information to the fake data API fake_data_api.add_course(self.COURSE_ID, course_modes=['professional']) # Enroll in the course and verify the URL we get sent to api.add_enrollment(self.USERNAME, self.COURSE_ID, mode='verified')
def post(self, request): """ Enrolls the currently logged in user in a course. """ # Get the User, Course ID, and Mode from the request. user = request.DATA.get('user', request.user.username) if 'course_details' not in request.DATA or 'course_id' not in request.DATA[ 'course_details']: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"Course ID must be specified to create a new enrollment." }) course_id = request.DATA['course_details']['course_id'] try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format( course_id=course_id) }) mode = request.DATA.get('mode', CourseMode.HONOR) has_api_key_permissions = self.has_api_key_permissions(request) # Check that the user specified is either the same user, or this is a server-to-server request. if not user: user = request.user.username if user != request.user.username and not has_api_key_permissions: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if mode != CourseMode.HONOR and not has_api_key_permissions: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": u"User does not have permission to create enrollment with mode [{mode}]." .format(mode=mode) }) # Check whether any country access rules block the user from enrollment # We do this at the view level (rather than the Python API level) # because this check requires information about the HTTP request. redirect_url = embargo_api.redirect_if_blocked( course_id, user=user, ip_address=get_ip(request), url=request.path) if redirect_url: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": (u"Users from this location cannot access the course '{course_id}'." ).format(course_id=course_id), "user_message_url": redirect_url }) try: # Check if the user is currently enrolled, and if it is the same as the current enrolled mode. We do not # have to check if it is inactive or not, because if it is, we are still upgrading if the mode is different, # and either path will re-activate the enrollment. # # Only server-to-server calls will currently be allowed to modify the mode for existing enrollments. All # other requests will go through add_enrollment(), which will allow creating of new enrollments, and # re-activating enrollments enrollment = api.get_enrollment(user, unicode(course_id)) if has_api_key_permissions and enrollment and enrollment[ 'mode'] != mode: response = api.update_enrollment(user, unicode(course_id), mode=mode) else: response = api.add_enrollment(user, unicode(course_id), mode=mode) email_opt_in = request.DATA.get('email_opt_in', None) if email_opt_in is not None: org = course_id.org update_email_opt_in(request.user, org, email_opt_in) return Response(response) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": (u"The course mode '{mode}' is not available for course '{course_id}'." ).format(mode="honor", course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format( course_id=course_id) }) except CourseEnrollmentExistsError as error: return Response(data=error.enrollment) except CourseEnrollmentError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": (u"An error occurred while creating the new course enrollment for user " u"'{user}' in course '{course_id}'").format( user=user, course_id=course_id) })
def post(self, request): """Enrolls the currently logged-in user in a course. Server-to-server calls may deactivate or modify the mode of existing enrollments. All other requests go through `add_enrollment()`, which allows creation of new and reactivation of old enrollments. """ # Get the User, Course ID, and Mode from the request. username = request.DATA.get('user', request.user.username) course_id = request.DATA.get('course_details', {}).get('course_id') if not course_id: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"Course ID must be specified to create a new enrollment." }) try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format( course_id=course_id) }) mode = request.DATA.get('mode', CourseMode.HONOR) has_api_key_permissions = self.has_api_key_permissions(request) # Check that the user specified is either the same user, or this is a server-to-server request. if not username: username = request.user.username if username != request.user.username and not has_api_key_permissions: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if mode != CourseMode.HONOR and not has_api_key_permissions: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": u"User does not have permission to create enrollment with mode [{mode}]." .format(mode=mode) }) try: # Lookup the user, instead of using request.user, since request.user may not match the username POSTed. user = User.objects.get(username=username) except ObjectDoesNotExist: return Response(status=status.HTTP_406_NOT_ACCEPTABLE, data={ 'message': u'The user {} does not exist.'.format(username) }) embargo_response = embargo_api.get_embargo_response( request, course_id, user) if embargo_response: return embargo_response try: is_active = request.DATA.get('is_active') # Check if the requested activation status is None or a Boolean if is_active is not None and not isinstance(is_active, bool): return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'message': (u"'{value}' is an invalid enrollment activation status." ).format(value=is_active) }) enrollment_attributes = request.DATA.get('enrollment_attributes') enrollment = api.get_enrollment(username, unicode(course_id)) mode_changed = enrollment and mode is not None and enrollment[ 'mode'] != mode active_changed = enrollment and is_active is not None and enrollment[ 'is_active'] != is_active missing_attrs = [] if enrollment_attributes: actual_attrs = [ u"{namespace}:{name}".format(**attr) for attr in enrollment_attributes ] missing_attrs = set(REQUIRED_ATTRIBUTES.get( mode, [])) - set(actual_attrs) if has_api_key_permissions and (mode_changed or active_changed): if mode_changed and active_changed and not is_active: # if the requester wanted to deactivate but specified the wrong mode, fail # the request (on the assumption that the requester had outdated information # about the currently active enrollment). msg = u"Enrollment mode mismatch: active mode={}, requested mode={}. Won't deactivate.".format( enrollment["mode"], mode) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) if len(missing_attrs) > 0: msg = u"Missing enrollment attributes: requested mode={} required attributes={}".format( mode, REQUIRED_ATTRIBUTES.get(mode)) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) response = api.update_enrollment( username, unicode(course_id), mode=mode, is_active=is_active, enrollment_attributes=enrollment_attributes) else: # Will reactivate inactive enrollments. response = api.add_enrollment(username, unicode(course_id), mode=mode, is_active=is_active) email_opt_in = request.DATA.get('email_opt_in', None) if email_opt_in is not None: org = course_id.org update_email_opt_in(request.user, org, email_opt_in) return Response(response) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": (u"The course mode '{mode}' is not available for course '{course_id}'." ).format(mode="honor", course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format( course_id=course_id) }) except CourseEnrollmentExistsError as error: return Response(data=error.enrollment) except CourseEnrollmentError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": (u"An error occurred while creating the new course enrollment for user " u"'{username}' in course '{course_id}'").format( username=username, course_id=course_id) }) finally: # Assumes that the ecommerce service uses an API key to authenticate. if has_api_key_permissions: current_enrollment = api.get_enrollment( username, unicode(course_id)) audit_log('enrollment_change_requested', course_id=unicode(course_id), requested_mode=mode, actual_mode=current_enrollment['mode'] if current_enrollment else None, requested_activation=is_active, actual_activation=current_enrollment['is_active'] if current_enrollment else None, user_id=user.id)
def post(self, request): """Create a new enrollment Creates a new enrollment based on the data posted. Currently all that can be specified is the course id. All other attributes will be determined by the server, and cannot be updated through this endpoint. By default, this will attempt to create the enrollment with course mode 'honor'. If the course does not have an 'honor' course mode, it will fail as a bad request and list the available course modes. Args: request (Request): The POST request to create a new enrollment. POST DATA should contain 'course_details' with an attribute 'course_id' to identify which course to enroll in. 'user' may be specified as well, but must match the username of the authenticated user. Ex. {'user': '******', 'course_details': { 'course_id': 'edx/demo/T2014' } } Returns: A JSON serialized representation of the user's new course enrollment. """ user = request.DATA.get('user', request.user.username) if not user: user = request.user.username if user != request.user.username: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if 'course_details' not in request.DATA or 'course_id' not in request.DATA[ 'course_details']: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"Course ID must be specified to create a new enrollment." }) course_id = request.DATA['course_details']['course_id'] try: return Response(api.add_enrollment(user, course_id)) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": (u"The course mode '{mode}' is not available for course '{course_id}'." ).format(mode="honor", course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format( course_id=course_id) }) except CourseEnrollmentError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": (u"An error occurred while creating the new course enrollment for user " u"'{user}' in course '{course_id}'").format( user=user, course_id=course_id) })