def post(self, request): """ Post function for updating the email opt in preference. Allows the modification or creation of the email opt in preference at an organizational level. Args: request (Request): The request should contain the following POST parameters: * course_id: The slash separated course ID. Used to determine the organization for this preference setting. * email_opt_in: "True" or "False" to determine if the user is opting in for emails from this organization. If the string does not match "True" (case insensitive) it will assume False. """ course_id = request.data['course_id'] try: org = locator.CourseLocator.from_string(course_id).org except InvalidKeyError: return HttpResponse( status=400, content="No course '{course_id}' found".format(course_id=course_id), content_type="text/plain" ) # Only check for true. All other values are False. email_opt_in = request.data['email_opt_in'].lower() == 'true' update_email_opt_in(request.user, org, email_opt_in) return HttpResponse(status=status.HTTP_200_OK)
def post(self, request): """ POST /api/user/v1/accounts/{username}/retire_mailings/ Allows an administrative user to take the following actions on behalf of an LMS user: - Update UserOrgTags to opt the user out of org emails - Call Sailthru API to force opt-out the user from all email lists """ username = request.data['username'] try: retirement = UserRetirementStatus.get_retirement_for_retirement_action(username) with transaction.atomic(): # Take care of org emails first, using the existing API for consistency for preference in UserOrgTag.objects.filter(user=retirement.user, key='email-optin'): update_email_opt_in(retirement.user, preference.org, False) # This signal allows lms' email_marketing and other 3rd party email # providers to unsubscribe the user as well USER_RETIRE_MAILINGS.send( sender=self.__class__, email=retirement.original_email, new_email=retirement.retired_email, user=retirement.user ) return Response(status=status.HTTP_204_NO_CONTENT) except UserRetirementStatus.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except Exception as exc: # pylint: disable=broad-except return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def post(self, request): """ Post function for updating the email opt in preference. Allows the modification or creation of the email opt in preference at an organizational level. Args: request (Request): The request should contain the following POST parameters: * course_id: The slash separated course ID. Used to determine the organization for this preference setting. * email_opt_in: "True" or "False" to determine if the user is opting in for emails from this organization. If the string does not match "True" (case insensitive) it will assume False. """ course_id = request.data['course_id'] try: org = locator.CourseLocator.from_string(course_id).org except InvalidKeyError: return HttpResponse(status=400, content="No course '{course_id}' found".format( course_id=course_id), content_type="text/plain") # Only check for true. All other values are False. email_opt_in = request.data['email_opt_in'].lower() == 'true' update_email_opt_in(request.user, org, email_opt_in) return HttpResponse(status=status.HTTP_200_OK)
def _update_email_opt_in(request, org): """Helper function used to hit the profile API if email opt-in is enabled.""" email_opt_in = request.POST.get('email_opt_in') if email_opt_in is not None: email_opt_in_boolean = email_opt_in == 'true' preferences_api.update_email_opt_in(request.user, org, email_opt_in_boolean)
def post(self, request): """ POST /api/user/v1/accounts/{username}/retire_mailings/ Allows an administrative user to take the following actions on behalf of an LMS user: - Update UserOrgTags to opt the user out of org emails - Call Sailthru API to force opt-out the user from all email lists """ username = request.data['username'] try: retirement = UserRetirementStatus.get_retirement_for_retirement_action( username) with transaction.atomic(): # Take care of org emails first, using the existing API for consistency for preference in UserOrgTag.objects.filter( user=retirement.user, key='email-optin'): update_email_opt_in(retirement.user, preference.org, False) # This signal allows lms' email_marketing and other 3rd party email # providers to unsubscribe the user as well USER_RETIRE_MAILINGS.send(sender=self.__class__, email=retirement.original_email, new_email=retirement.retired_email, user=retirement.user) return Response(status=status.HTTP_204_NO_CONTENT) except UserRetirementStatus.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except Exception as exc: # pylint: disable=broad-except return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def create(self, request, *args, **kwargs): support_details = request.data.pop('support_details', []) email_opt_in = request.data.pop('email_opt_in', False) serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) entitlement = serializer.instance set_entitlement_policy(entitlement, request.site) # The owners for a course are the organizations that own the course. By taking owner.key, # we are able to pass in the organization key for email_opt_in owners = get_owners_for_course(entitlement.course_uuid) for owner in owners: update_email_opt_in(entitlement.user, owner['key'], email_opt_in) if support_details: for support_detail in support_details: support_detail['entitlement'] = entitlement support_detail['support_user'] = request.user CourseEntitlementSupportDetail.objects.create(**support_detail) else: user = entitlement.user upgradeable_enrollments = self.get_upgradeable_enrollments_for_entitlement(entitlement) # if there is only one upgradeable enrollment, update the mode to the paid entitlement.mode # if there is any ambiguity about which enrollment to upgrade # (i.e. multiple upgradeable enrollments or no available upgradeable enrollment), don't alter # the enrollment if len(upgradeable_enrollments) == 1: enrollment = upgradeable_enrollments[0] log.info( 'Upgrading enrollment [%s] from %s to %s while adding entitlement for user [%s] for course [%s]', enrollment, enrollment.mode, serializer.data.get('mode'), user.username, serializer.data.get('course_uuid') ) enrollment.update_enrollment(mode=entitlement.mode) entitlement.set_enrollment(enrollment) else: log.info( 'No enrollment upgraded while adding entitlement for user [%s] for course [%s] ', user.username, serializer.data.get('course_uuid') ) headers = self.get_success_headers(serializer.data) # Note, the entitlement is re-serialized before getting added to the Response, # so that the 'modified' date reflects changes that occur when upgrading enrollment. return Response( CourseEntitlementSerializer(entitlement).data, status=status.HTTP_201_CREATED, headers=headers )
def _set_opt_in_pref(self, user, org, is_opted_in): """Set the email opt-in preference. Arguments: user (User): The user model. org (unicode): The org in the course key. is_opted_in (bool): Whether the user is opted in or out of emails. Returns: None """ update_email_opt_in(user, org, is_opted_in)
def _handle_marketing_opt_in(self, request, course_key, user): """ Handle the marketing email opt-in flag, if it was set. Errors here aren't expected, but should not break the outer enrollment transaction. """ email_opt_in = request.DATA.get('email_opt_in', None) if email_opt_in is not None: try: update_email_opt_in(user, course_key.org, email_opt_in) except Exception: # pylint: disable=broad-except # log the error, return silently log.exception( 'Failed to handle marketing opt-in flag: user="******", course="%s"', user.username, course_key )
def _handle_marketing_opt_in(self, request, course_key, user): """ Handle the marketing email opt-in flag, if it was set. Errors here aren't expected, but should not break the outer enrollment transaction. """ email_opt_in = request.data.get('email_opt_in', None) if email_opt_in is not None: try: update_email_opt_in(user, course_key.org, email_opt_in) except Exception: # pylint: disable=broad-except # log the error, return silently log.exception( 'Failed to handle marketing opt-in flag: user="******", course="%s"', user.username, course_key )
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): """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 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) 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. """ # 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 change_enrollment(strategy, auth_entry=None, user=None, *args, **kwargs): """Enroll a user in a course. If a user entered the authentication flow when trying to enroll in a course, then attempt to enroll the user. We will try to do this if the pipeline was started with the querystring param `enroll_course_id`. In the following cases, we can't enroll the user: * The course does not have an honor mode. * The course has an honor mode with a minimum price. * The course is not yet open for enrollment. * The course does not exist. If we can't enroll the user now, then skip this step. For paid courses, users will be redirected to the payment flow upon completion of the authentication pipeline (configured using the ?next parameter to the third party auth login url). Keyword Arguments: auth_entry: The entry mode into the pipeline. user (User): The user being authenticated. """ # We skip enrollment if the user entered the flow from the "link account" # button on the account settings page. At this point, either: # # 1) The user already had a linked account when they started the enrollment flow, # in which case they would have been enrolled during the normal authentication process. # # 2) The user did NOT have a linked account, in which case they would have # needed to go through the login/register page. Since we preserve the querystring # args when sending users to this page, successfully authenticating through this page # would also enroll the student in the course. enroll_course_id = strategy.session_get('enroll_course_id') if enroll_course_id and auth_entry != AUTH_ENTRY_ACCOUNT_SETTINGS: course_id = CourseKey.from_string(enroll_course_id) modes = CourseMode.modes_for_course_dict(course_id) # If the email opt in parameter is found, set the preference. email_opt_in = strategy.session_get(AUTH_EMAIL_OPT_IN_KEY) if email_opt_in: opt_in = email_opt_in.lower() == 'true' update_email_opt_in(user, course_id.org, opt_in) # Check whether we're blocked from enrolling by a # country access rule. # Note: We skip checking the user's profile setting # for country here because the "redirect URL" pointing # to the blocked message page is set when the user # *enters* the pipeline, at which point they're # not authenticated. If they end up being blocked # from the courseware, it's better to let them # enroll and then show the message when they # enter the course than to skip enrollment # altogether. is_blocked = not embargo_api.check_course_access( course_id, ip_address=get_ip(strategy.request), url=strategy.request.path ) if is_blocked: # If we're blocked, skip enrollment. # A redirect URL should have been set so the user # ends up on the embargo page when enrollment completes. pass elif CourseMode.can_auto_enroll(course_id, modes_dict=modes): try: CourseEnrollment.enroll(user, course_id, check_access=True) except CourseEnrollmentException: pass except Exception as ex: logger.exception(ex) # Handle white-label courses as a special case # If a course is white-label, we should add it to the shopping cart. elif CourseMode.is_white_label(course_id, modes_dict=modes): try: cart = Order.get_cart_for_user(user) PaidCourseRegistration.add_to_order(cart, course_id) except ( CourseDoesNotExistException, ItemAlreadyInCartException, AlreadyEnrolledInCourseException, ): pass # It's more important to complete login than to # ensure that the course was added to the shopping cart. # Log errors, but don't stop the authentication pipeline. except Exception as ex: # pylint: disable=broad-except logger.exception(ex)
def post(self, request): """ Enrolls the list of users in a verified course mode. """ # Get the users, Course ID, and Mode from the request. users = request.DATA.get('users', []) if len(users) == 0: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"Users must be specified to create a new enrollment."} ) 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) } ) # use verified course mode by default mode = request.DATA.get('mode', CourseMode.VERIFIED) bad_users = [] list_users = [] for username in users: try: user = User.objects.get(username=username) list_users.append(user) except ObjectDoesNotExist: bad_users.append(username) if len(bad_users) > 0: return Response( status=status.HTTP_400_BAD_REQUEST, data={'message': u'Users: {} does not exist.'.format(', '.join(bad_users))} ) for user in list_users: embargo_response = embargo_api.get_embargo_response(request, course_id, user) if embargo_response: return embargo_response current_username = None 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') errors = False already_paid = [] # list of users with verified enrollment not_enrolled = [] # list of not enrolled yet or unenrolled users for username in users: current_username = username enrollment = api.get_enrollment(username, unicode(course_id)) if not enrollment: not_enrolled.append(username) elif enrollment['is_active'] is not True: not_enrolled.append(username) elif enrollment['mode'] == CourseMode.VERIFIED: already_paid.append(username) msg_paid = u"" msg_not_enrolled = u"" if len(already_paid) > 0: msg_paid = u'Users: {} already paid for course.'.format(', '.join(already_paid)) errors = True if len(not_enrolled) > 0: msg_not_enrolled = u'Users: {} not enrolled for course.'.format(', '.join(not_enrolled)) errors = True if errors: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": (u"'{course_id}'\n:{msg_paid}\n{msg_not_enrolled}").format( course_id=course_id, msg_paid=msg_paid, msg_not_enrolled=msg_not_enrolled ), }) for username in users: current_username = username response = api.update_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 for username in users: update_email_opt_in(username, org, email_opt_in) return Response( status=status.HTTP_200_OK, data={ "message": u"Success for course '{course_id}'.".format(course_id=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="verified", 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=current_username, 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. 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 list of users in a verified course mode. """ # Get the users, Course ID, and Mode from the request. users = request.data.get('users', []) if len(users) == 0: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"Users must be specified to create a new enrollment."} ) 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_key = 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)} ) # use verified course mode by default mode = request.data.get('mode', CourseMode.VERIFIED) bad_users = [] list_users = [] for username in users: try: user = User.objects.get(username=username) list_users.append(user) except ObjectDoesNotExist: bad_users.append(username) if len(bad_users) > 0: return Response( status=status.HTTP_400_BAD_REQUEST, data={'message': u'Users: {} does not exist.'.format(', '.join(bad_users))} ) for user in list_users: embargo_response = embargo_api.get_embargo_response(request, course_key, user) if embargo_response: return embargo_response current_username = None 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') errors = False already_paid = [] # list of users with verified enrollment not_enrolled = [] # list of not enrolled yet or unenrolled users for username in users: current_username = username enrollment = api.get_enrollment(username, unicode(course_key)) if not enrollment: not_enrolled.append(username) elif enrollment['is_active'] is not True: not_enrolled.append(username) elif enrollment['mode'] == CourseMode.VERIFIED: already_paid.append(username) msg_paid = u"" msg_not_enrolled = u"" if len(already_paid) > 0: msg_paid = u'Users: {} already paid for course.'.format(', '.join(already_paid)) errors = True if len(not_enrolled) > 0: msg_not_enrolled = u'Users: {} not enrolled for course.'.format(', '.join(not_enrolled)) errors = True if errors: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": (u"'{course_id}'\n:{msg_paid}\n{msg_not_enrolled}").format( course_id=course_id, msg_paid=msg_paid, msg_not_enrolled=msg_not_enrolled ), }) # update for cohorts cohort_exists = is_cohort_exists(course_key, VERIFIED) if not cohort_exists: cohort = add_cohort(course_key, VERIFIED, 'manual') else: cohort = get_cohort_by_name(course_key, VERIFIED) for username in users: current_username = username api.update_enrollment(username, unicode(course_key), mode=mode, is_active=is_active) user = User.objects.get(username=username) course_cohorts = CourseUserGroup.objects.filter( course_id=cohort.course_id, users__id=user.id, group_type=CourseUserGroup.COHORT ) add_user_into_verified_cohort(course_cohorts, cohort, user) email_opt_in = request.data.get('email_opt_in', None) if email_opt_in is not None: org = course_key.org for username in users: update_email_opt_in(username, org, email_opt_in) return Response( status=status.HTTP_200_OK, data={ "message": u"Success for course '{course_id}'.".format(course_id=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="verified", course_id=course_id), "course_details": error.data }) 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=current_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', 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 change_enrollment(strategy, auth_entry=None, user=None, *args, **kwargs): """Enroll a user in a course. If a user entered the authentication flow when trying to enroll in a course, then attempt to enroll the user. We will try to do this if the pipeline was started with the querystring param `enroll_course_id`. In the following cases, we can't enroll the user: * The course does not have an honor mode. * The course has an honor mode with a minimum price. * The course is not yet open for enrollment. * The course does not exist. If we can't enroll the user now, then skip this step. For paid courses, users will be redirected to the payment flow upon completion of the authentication pipeline (configured using the ?next parameter to the third party auth login url). Keyword Arguments: auth_entry: The entry mode into the pipeline. user (User): The user being authenticated. """ # We skip enrollment if the user entered the flow from the "link account" # button on the student dashboard. At this point, either: # # 1) The user already had a linked account when they started the enrollment flow, # in which case they would have been enrolled during the normal authentication process. # # 2) The user did NOT have a linked account, in which case they would have # needed to go through the login/register page. Since we preserve the querystring # args when sending users to this page, successfully authenticating through this page # would also enroll the student in the course. enroll_course_id = strategy.session_get('enroll_course_id') if enroll_course_id and auth_entry != AUTH_ENTRY_DASHBOARD: course_id = CourseKey.from_string(enroll_course_id) modes = CourseMode.modes_for_course_dict(course_id) # If the email opt in parameter is found, set the preference. email_opt_in = strategy.session_get(AUTH_EMAIL_OPT_IN_KEY) if email_opt_in: opt_in = email_opt_in.lower() == 'true' update_email_opt_in(user, course_id.org, opt_in) # Check whether we're blocked from enrolling by a # country access rule. # Note: We skip checking the user's profile setting # for country here because the "redirect URL" pointing # to the blocked message page is set when the user # *enters* the pipeline, at which point they're # not authenticated. If they end up being blocked # from the courseware, it's better to let them # enroll and then show the message when they # enter the course than to skip enrollment # altogether. is_blocked = not embargo_api.check_course_access( course_id, ip_address=get_ip(strategy.request), url=strategy.request.path) if is_blocked: # If we're blocked, skip enrollment. # A redirect URL should have been set so the user # ends up on the embargo page when enrollment completes. pass elif CourseMode.can_auto_enroll(course_id, modes_dict=modes): try: CourseEnrollment.enroll(user, course_id, check_access=True) except CourseEnrollmentException: pass except Exception as ex: logger.exception(ex) # Handle white-label courses as a special case # If a course is white-label, we should add it to the shopping cart. elif CourseMode.is_white_label(course_id, modes_dict=modes): try: cart = Order.get_cart_for_user(user) PaidCourseRegistration.add_to_order(cart, course_id) except ( CourseDoesNotExistException, ItemAlreadyInCartException, AlreadyEnrolledInCourseException, ): pass # It's more important to complete login than to # ensure that the course was added to the shopping cart. # Log errors, but don't stop the authentication pipeline. except Exception as ex: # pylint: disable=broad-except logger.exception(ex)