def country_access_rules(self, user, ip_address, url_path): """ Check the country access rules for a given course. Applies only to courseware URLs. Args: user (User): The user making the current request. ip_address (str): The IP address from which the request originated. url_path (str): The request path. Returns: HttpResponse or None """ course_id = course_id_from_url(url_path) if course_id: redirect_url = embargo_api.redirect_if_blocked( course_id, user=user, ip_address=ip_address, url=url_path, access_point='courseware' ) if redirect_url: return redirect(redirect_url)
def country_access_rules(self, user, ip_address, url_path): """ Check the country access rules for a given course. Applies only to courseware URLs. Args: user (User): The user making the current request. ip_address (str): The IP address from which the request originated. url_path (str): The request path. Returns: HttpResponse or None """ course_id = course_id_from_url(url_path) if course_id: redirect_url = embargo_api.redirect_if_blocked( course_id, user=user, ip_address=ip_address, url=url_path, access_point='courseware') if redirect_url: return redirect(redirect_url)
def enroll(user, course_id, request, check_access): # Make sure the course exists # We don't do this check on unenroll, or a bad course id can't be unenrolled from if not modulestore().has_course(course_id): log.warning(u"User %s tried to enroll in non-existent course %s", user.username, course_id) raise InvalidCourseIdError() # Record the user's email opt-in preference if settings.FEATURES.get('ENABLE_MKTG_EMAIL_OPT_IN'): _update_email_opt_in(request, course_id.org) available_modes = CourseMode.modes_for_course_dict(course_id) # Check whether the user is blocked from enrolling in this course # This can occur if the user's IP is on a global blacklist # or if the user is enrolling in a country in which the course # is not available. redirect_url = embargo_api.redirect_if_blocked(course_id, user=user, ip_address=get_ip(request), url=request.path) if redirect_url: return redirect_url # Check that auto enrollment is allowed for this course # (= the course is NOT behind a paywall) if CourseMode.can_auto_enroll(course_id): # Enroll the user using the default mode (honor) # We're assuming that users of the course enrollment table # will NOT try to look up the course enrollment model # by its slug. If they do, it's possible (based on the state of the database) # for no such model to exist, even though we've set the enrollment type # to "honor". # Exception is to be handled by the caller CourseEnrollment.enroll(user, course_id, check_access=check_access) # If we have more than one course mode or professional ed is enabled, # then send the user to the choose your track page. # (In the case of no-id-professional/professional ed, this will redirect to a page that # funnels users directly into the verification / payment flow) if CourseMode.has_verified_mode( available_modes) or CourseMode.has_professional_mode( available_modes): reverse("course_modes_choose", kwargs={'course_id': unicode(course_id)}) # Otherwise, there is only one mode available (the default) # Success return None
def enroll(user, course_id, request, check_access): # Make sure the course exists # We don't do this check on unenroll, or a bad course id can't be unenrolled from if not modulestore().has_course(course_id): log.warning( u"User %s tried to enroll in non-existent course %s", user.username, course_id ) raise InvalidCourseIdError() # Record the user's email opt-in preference if settings.FEATURES.get('ENABLE_MKTG_EMAIL_OPT_IN'): _update_email_opt_in(request, course_id.org) available_modes = CourseMode.modes_for_course_dict(course_id) # Check whether the user is blocked from enrolling in this course # This can occur if the user's IP is on a global blacklist # or if the user is enrolling in a country in which the course # is not available. redirect_url = embargo_api.redirect_if_blocked( course_id, user=user, ip_address=get_ip(request), url=request.path ) if redirect_url: return redirect_url # Check that auto enrollment is allowed for this course # (= the course is NOT behind a paywall) if CourseMode.can_auto_enroll(course_id): # Enroll the user using the default mode (honor) # We're assuming that users of the course enrollment table # will NOT try to look up the course enrollment model # by its slug. If they do, it's possible (based on the state of the database) # for no such model to exist, even though we've set the enrollment type # to "honor". # Exception is to be handled by the caller CourseEnrollment.enroll(user, course_id, check_access=check_access) # If we have more than one course mode or professional ed is enabled, # then send the user to the choose your track page. # (In the case of no-id-professional/professional ed, this will redirect to a page that # funnels users directly into the verification / payment flow) if CourseMode.has_verified_mode(available_modes) or CourseMode.has_professional_mode(available_modes): reverse("course_modes_choose", kwargs={'course_id': unicode(course_id)}) # Otherwise, there is only one mode available (the default) # Success return None
def _third_party_auth_context(request): """Context for third party auth providers and the currently running pipeline. Arguments: request (HttpRequest): The request, used to determine if a pipeline is currently running. Returns: dict """ context = {"currentProvider": None, "providers": []} course_id = request.GET.get("course_id") email_opt_in = request.GET.get("email_opt_in") redirect_to = request.GET.get("next") # Check if the user is trying to enroll in a course # that they don't have access to based on country # access rules. # # If so, set the redirect URL to the blocked page. # We need to set it here, rather than redirecting # from within the pipeline, because a redirect # from the pipeline can prevent users # from completing the authentication process. # # Note that we can't check the user's country # profile at this point, since the user hasn't # authenticated. If the user ends up being blocked # by their country preference, we let them enroll; # they'll still be blocked when they try to access # the courseware. if course_id: try: course_key = CourseKey.from_string(course_id) redirect_url = embargo_api.redirect_if_blocked(course_key, ip_address=get_ip(request), url=request.path) if redirect_url: redirect_to = embargo_api.message_url_path(course_key, "enrollment") except InvalidKeyError: pass login_urls = auth_pipeline_urls( third_party_auth.pipeline.AUTH_ENTRY_LOGIN, course_id=course_id, email_opt_in=email_opt_in, redirect_url=redirect_to, ) register_urls = auth_pipeline_urls( third_party_auth.pipeline.AUTH_ENTRY_REGISTER, course_id=course_id, email_opt_in=email_opt_in, redirect_url=redirect_to, ) if third_party_auth.is_enabled(): context["providers"] = [ { "name": enabled.NAME, "iconClass": enabled.ICON_CLASS, "loginUrl": login_urls[enabled.NAME], "registerUrl": register_urls[enabled.NAME], } for enabled in third_party_auth.provider.Registry.enabled() ] running_pipeline = third_party_auth.pipeline.get(request) if running_pipeline is not None: current_provider = third_party_auth.provider.Registry.get_by_backend_name(running_pipeline.get("backend")) context["currentProvider"] = current_provider.NAME return context
def _third_party_auth_context(request): """Context for third party auth providers and the currently running pipeline. Arguments: request (HttpRequest): The request, used to determine if a pipeline is currently running. Returns: dict """ context = { "currentProvider": None, "providers": [] } course_id = request.GET.get("course_id") email_opt_in = request.GET.get('email_opt_in') redirect_to = request.GET.get("next") # Check if the user is trying to enroll in a course # that they don't have access to based on country # access rules. # # If so, set the redirect URL to the blocked page. # We need to set it here, rather than redirecting # from within the pipeline, because a redirect # from the pipeline can prevent users # from completing the authentication process. # # Note that we can't check the user's country # profile at this point, since the user hasn't # authenticated. If the user ends up being blocked # by their country preference, we let them enroll; # they'll still be blocked when they try to access # the courseware. if course_id: try: course_key = CourseKey.from_string(course_id) redirect_url = embargo_api.redirect_if_blocked( course_key, ip_address=get_ip(request), url=request.path ) if redirect_url: redirect_to = embargo_api.message_url_path(course_key, "enrollment") except InvalidKeyError: pass login_urls = auth_pipeline_urls( third_party_auth.pipeline.AUTH_ENTRY_LOGIN, course_id=course_id, email_opt_in=email_opt_in, redirect_url=redirect_to ) register_urls = auth_pipeline_urls( third_party_auth.pipeline.AUTH_ENTRY_REGISTER, course_id=course_id, email_opt_in=email_opt_in, redirect_url=redirect_to ) if third_party_auth.is_enabled(): context["providers"] = [ { "name": enabled.NAME, "iconClass": enabled.ICON_CLASS, "loginUrl": login_urls[enabled.NAME], "registerUrl": register_urls[enabled.NAME] } for enabled in third_party_auth.provider.Registry.enabled() ] running_pipeline = third_party_auth.pipeline.get(request) if running_pipeline is not None: current_provider = third_party_auth.provider.Registry.get_by_backend_name( running_pipeline.get('backend') ) context["currentProvider"] = current_provider.NAME return context
def get(self, request, course_id, error=None): """Displays the course mode choice page. Args: request (`Request`): The Django Request object. course_id (unicode): The slash-separated course key. Keyword Args: error (unicode): If provided, display this error message on the page. Returns: Response """ course_key = CourseKey.from_string(course_id) # Check whether the user has access to this course # based on country access rules. embargo_redirect = embargo_api.redirect_if_blocked( course_key, user=request.user, ip_address=get_ip(request), url=request.path) if embargo_redirect: return redirect(embargo_redirect) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user( request.user, course_key) modes = CourseMode.modes_for_course_dict(course_key) ecommerce_service = EcommerceService() # We assume that, if 'professional' is one of the modes, it should be the *only* mode. # If there are both modes, default to non-id-professional. has_enrolled_professional = ( CourseMode.is_professional_slug(enrollment_mode) and is_active) if CourseMode.has_professional_mode( modes) and not has_enrolled_professional: purchase_workflow = request.GET.get("purchase_workflow", "single") verify_url = reverse('verify_student_start_flow', kwargs={'course_id': unicode(course_key)}) redirect_url = "{url}?purchase_workflow={workflow}".format( url=verify_url, workflow=purchase_workflow) if ecommerce_service.is_enabled(request.user): professional_mode = modes.get( CourseMode.NO_ID_PROFESSIONAL_MODE) or modes.get( CourseMode.PROFESSIONAL) if purchase_workflow == "single" and professional_mode.sku: redirect_url = ecommerce_service.checkout_page_url( professional_mode.sku) if purchase_workflow == "bulk" and professional_mode.bulk_sku: redirect_url = ecommerce_service.checkout_page_url( professional_mode.bulk_sku) return redirect(redirect_url) # If there isn't a verified mode available, then there's nothing # to do on this page. The user has almost certainly been auto-registered # in the "honor" track by this point, so we send the user # to the dashboard. if not CourseMode.has_verified_mode(modes): return redirect(reverse('dashboard')) # If a user has already paid, redirect them to the dashboard. if is_active and (enrollment_mode in CourseMode.VERIFIED_MODES + [CourseMode.NO_ID_PROFESSIONAL_MODE]): return redirect(reverse('dashboard')) donation_for_course = request.session.get("donation_for_course", {}) chosen_price = donation_for_course.get(unicode(course_key), None) course = modulestore().get_course(course_key) if CourseEnrollment.is_enrollment_closed(request.user, course): locale = to_locale(get_language()) enrollment_end_date = format_datetime(course.enrollment_end, 'short', locale=locale) params = urllib.urlencode({'course_closed': enrollment_end_date}) return redirect('{0}?{1}'.format(reverse('dashboard'), params)) # When a credit mode is available, students will be given the option # to upgrade from a verified mode to a credit mode at the end of the course. # This allows students who have completed photo verification to be eligible # for univerity credit. # Since credit isn't one of the selectable options on the track selection page, # we need to check *all* available course modes in order to determine whether # a credit mode is available. If so, then we show slightly different messaging # for the verified track. has_credit_upsell = any( CourseMode.is_credit_mode(mode) for mode in CourseMode.modes_for_course(course_key, only_selectable=False)) context = { "course_modes_choose_url": reverse("course_modes_choose", kwargs={'course_id': course_key.to_deprecated_string()}), "modes": modes, "has_credit_upsell": has_credit_upsell, "course_name": course.display_name_with_default_escaped, "course_org": course.display_org_with_default, "course_num": course.display_number_with_default, "chosen_price": chosen_price, "error": error, "responsive": True, "nav_hidden": True, } if "verified" in modes: verified_mode = modes["verified"] context["suggested_prices"] = [ decimal.Decimal(x.strip()) for x in verified_mode.suggested_prices.split(",") if x.strip() ] context["currency"] = verified_mode.currency.upper() context["min_price"] = verified_mode.min_price context["verified_name"] = verified_mode.name context["verified_description"] = verified_mode.description if verified_mode.sku: context[ "use_ecommerce_payment_flow"] = ecommerce_service.is_enabled( request.user) context[ "ecommerce_payment_page"] = ecommerce_service.payment_page_url( ) context["sku"] = verified_mode.sku context["bulk_sku"] = verified_mode.bulk_sku return render_to_response("course_modes/choose.html", context)
def register_code_redemption(request, registration_code): """ This view allows the student to redeem the registration code and enroll in the course. """ # Add some rate limiting here by re-using the RateLimitMixin as a helper class site_name = microsite.get_value('SITE_NAME', settings.SITE_NAME) limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning( "Rate limit exceeded in registration code redemption.") return HttpResponseForbidden() template_to_render = 'shoppingcart/registration_code_redemption.html' if request.method == "GET": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity( registration_code, request, limiter) course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0) # Restrict the user from enrolling based on country access rules embargo_redirect = embargo_api.redirect_if_blocked( course.id, user=request.user, ip_address=get_ip(request), url=request.path) if embargo_redirect is not None: return redirect(embargo_redirect) context = { 'reg_code_already_redeemed': reg_code_already_redeemed, 'reg_code_is_valid': reg_code_is_valid, 'reg_code': registration_code, 'site_name': site_name, 'course': course, 'registered_for_course': not _is_enrollment_code_an_update(course, request.user, course_registration) } return render_to_response(template_to_render, context) elif request.method == "POST": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity( registration_code, request, limiter) course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0) # Restrict the user from enrolling based on country access rules embargo_redirect = embargo_api.redirect_if_blocked( course.id, user=request.user, ip_address=get_ip(request), url=request.path) if embargo_redirect is not None: return redirect(embargo_redirect) context = { 'reg_code': registration_code, 'site_name': site_name, 'course': course, 'reg_code_is_valid': reg_code_is_valid, 'reg_code_already_redeemed': reg_code_already_redeemed, } if reg_code_is_valid and not reg_code_already_redeemed: # remove the course from the cart if it was added there. cart = Order.get_cart_for_user(request.user) try: cart_items = cart.find_item_by_course_id( course_registration.course_id) except ItemNotFoundInCartException: pass else: for cart_item in cart_items: if isinstance(cart_item, PaidCourseRegistration) or isinstance( cart_item, CourseRegCodeItem): cart_item.delete() #now redeem the reg code. redemption = RegistrationCodeRedemption.create_invoice_generated_registration_redemption( course_registration, request.user) try: kwargs = {} if course_registration.mode_slug is not None: if CourseMode.mode_for_course( course.id, course_registration.mode_slug): kwargs['mode'] = course_registration.mode_slug else: raise RedemptionCodeError() redemption.course_enrollment = CourseEnrollment.enroll( request.user, course.id, **kwargs) redemption.save() context['redemption_success'] = True except RedemptionCodeError: context['redeem_code_error'] = True context['redemption_success'] = False except EnrollmentClosedError: context['enrollment_closed'] = True context['redemption_success'] = False except CourseFullError: context['course_full'] = True context['redemption_success'] = False except AlreadyEnrolledError: context['registered_for_course'] = True context['redemption_success'] = False else: context['redemption_success'] = False return render_to_response(template_to_render, context)
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. """ 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 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)) 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 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 get(self, request, course_id, error=None): """Displays the course mode choice page. Args: request (`Request`): The Django Request object. course_id (unicode): The slash-separated course key. Keyword Args: error (unicode): If provided, display this error message on the page. Returns: Response """ course_key = CourseKey.from_string(course_id) # Check whether the user has access to this course # based on country access rules. embargo_redirect = embargo_api.redirect_if_blocked( course_key, user=request.user, ip_address=get_ip(request), url=request.path ) if embargo_redirect: return redirect(embargo_redirect) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(request.user, course_key) modes = CourseMode.modes_for_course_dict(course_key) # We assume that, if 'professional' is one of the modes, it is the *only* mode. # If we offer more modes alongside 'professional' in the future, this will need to route # to the usual "choose your track" page. has_enrolled_professional = (enrollment_mode == "professional" and is_active) if "professional" in modes and not has_enrolled_professional: return redirect( reverse( 'verify_student_start_flow', kwargs={'course_id': unicode(course_key)} ) ) # If there isn't a verified mode available, then there's nothing # to do on this page. The user has almost certainly been auto-registered # in the "honor" track by this point, so we send the user # to the dashboard. if not CourseMode.has_verified_mode(modes): return redirect(reverse('dashboard')) # If a user has already paid, redirect them to the dashboard. if is_active and enrollment_mode in CourseMode.VERIFIED_MODES: return redirect(reverse('dashboard')) donation_for_course = request.session.get("donation_for_course", {}) chosen_price = donation_for_course.get(unicode(course_key), None) course = modulestore().get_course(course_key) context = { "course_modes_choose_url": reverse("course_modes_choose", kwargs={'course_id': course_key.to_deprecated_string()}), "modes": modes, "course_name": course.display_name_with_default, "course_org": course.display_org_with_default, "course_num": course.display_number_with_default, "chosen_price": chosen_price, "error": error, "can_audit": "audit" in modes, "responsive": True } if "verified" in modes: context["suggested_prices"] = [ decimal.Decimal(x.strip()) for x in modes["verified"].suggested_prices.split(",") if x.strip() ] context["currency"] = modes["verified"].currency.upper() context["min_price"] = modes["verified"].min_price context["verified_name"] = modes["verified"].name context["verified_description"] = modes["verified"].description return render_to_response("course_modes/choose.html", context)
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. """ # 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 get(self, request, course_id, error=None): """Displays the course mode choice page. Args: request (`Request`): The Django Request object. course_id (unicode): The slash-separated course key. Keyword Args: error (unicode): If provided, display this error message on the page. Returns: Response """ course_key = CourseKey.from_string(course_id) # Check whether the user has access to this course # based on country access rules. embargo_redirect = embargo_api.redirect_if_blocked( course_key, user=request.user, ip_address=get_ip(request), url=request.path ) if embargo_redirect: return redirect(embargo_redirect) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(request.user, course_key) modes = CourseMode.modes_for_course_dict(course_key) ecommerce_service = EcommerceService() # We assume that, if 'professional' is one of the modes, it should be the *only* mode. # If there are both modes, default to non-id-professional. has_enrolled_professional = CourseMode.is_professional_slug(enrollment_mode) and is_active if CourseMode.has_professional_mode(modes) and not has_enrolled_professional: purchase_workflow = request.GET.get("purchase_workflow", "single") verify_url = reverse("verify_student_start_flow", kwargs={"course_id": unicode(course_key)}) redirect_url = "{url}?purchase_workflow={workflow}".format(url=verify_url, workflow=purchase_workflow) if ecommerce_service.is_enabled(request.user): professional_mode = modes.get(CourseMode.NO_ID_PROFESSIONAL_MODE) or modes.get(CourseMode.PROFESSIONAL) if purchase_workflow == "single" and professional_mode.sku: redirect_url = ecommerce_service.checkout_page_url(professional_mode.sku) if purchase_workflow == "bulk" and professional_mode.bulk_sku: redirect_url = ecommerce_service.checkout_page_url(professional_mode.bulk_sku) return redirect(redirect_url) # If there isn't a verified mode available, then there's nothing # to do on this page. The user has almost certainly been auto-registered # in the "honor" track by this point, so we send the user # to the dashboard. if not CourseMode.has_verified_mode(modes): return redirect(reverse("dashboard")) # If a user has already paid, redirect them to the dashboard. if is_active and (enrollment_mode in CourseMode.VERIFIED_MODES + [CourseMode.NO_ID_PROFESSIONAL_MODE]): return redirect(reverse("dashboard")) donation_for_course = request.session.get("donation_for_course", {}) chosen_price = donation_for_course.get(unicode(course_key), None) course = modulestore().get_course(course_key) if CourseEnrollment.is_enrollment_closed(request.user, course): locale = to_locale(get_language()) enrollment_end_date = format_datetime(course.enrollment_end, "short", locale=locale) params = urllib.urlencode({"course_closed": enrollment_end_date}) return redirect("{0}?{1}".format(reverse("dashboard"), params)) # When a credit mode is available, students will be given the option # to upgrade from a verified mode to a credit mode at the end of the course. # This allows students who have completed photo verification to be eligible # for univerity credit. # Since credit isn't one of the selectable options on the track selection page, # we need to check *all* available course modes in order to determine whether # a credit mode is available. If so, then we show slightly different messaging # for the verified track. has_credit_upsell = any( CourseMode.is_credit_mode(mode) for mode in CourseMode.modes_for_course(course_key, only_selectable=False) ) context = { "course_modes_choose_url": reverse( "course_modes_choose", kwargs={"course_id": course_key.to_deprecated_string()} ), "modes": modes, "has_credit_upsell": has_credit_upsell, "course_name": course.display_name_with_default_escaped, "course_org": course.display_org_with_default, "course_num": course.display_number_with_default, "chosen_price": chosen_price, "error": error, "responsive": True, "nav_hidden": True, } if "verified" in modes: verified_mode = modes["verified"] context["suggested_prices"] = [ decimal.Decimal(x.strip()) for x in verified_mode.suggested_prices.split(",") if x.strip() ] context["currency"] = verified_mode.currency.upper() context["min_price"] = verified_mode.min_price context["verified_name"] = verified_mode.name context["verified_description"] = verified_mode.description if verified_mode.sku: context["use_ecommerce_payment_flow"] = ecommerce_service.is_enabled(request.user) context["ecommerce_payment_page"] = ecommerce_service.payment_page_url() context["sku"] = verified_mode.sku context["bulk_sku"] = verified_mode.bulk_sku return render_to_response("course_modes/choose.html", context)
def get( self, request, course_id, always_show_payment=False, current_step=None, message=FIRST_TIME_VERIFY_MSG ): """Render the pay/verify requirements page. Arguments: request (HttpRequest): The request object. course_id (unicode): The ID of the course the user is trying to enroll in. Keyword Arguments: always_show_payment (bool): If True, show the payment steps even if the user has already paid. This is useful for users returning to the flow after paying. current_step (string): The current step in the flow. message (string): The messaging to display. Returns: HttpResponse Raises: Http404: The course does not exist or does not have a verified mode. """ # Parse the course key # The URL regex should guarantee that the key format is valid. course_key = CourseKey.from_string(course_id) course = modulestore().get_course(course_key) # Verify that the course exists and has a verified mode if course is None: log.warn(u"No course specified for verification flow request.") raise Http404 # Check whether the user has access to this course # based on country access rules. redirect_url = embargo_api.redirect_if_blocked( course_key, user=request.user, ip_address=get_ip(request), url=request.path ) if redirect_url: return redirect(redirect_url) expired_verified_course_mode, unexpired_paid_course_mode = self._get_expired_verified_and_paid_mode(course_key) # Check that the course has an unexpired paid mode if unexpired_paid_course_mode is not None: if CourseMode.is_verified_mode(unexpired_paid_course_mode): log.info( u"Entering verified workflow for user '%s', course '%s', with current step '%s'.", request.user.id, course_id, current_step ) elif expired_verified_course_mode is not None: # Check if there is an *expired* verified course mode; # if so, we should show a message explaining that the verification # deadline has passed. log.info(u"Verification deadline for '%s' has passed.", course_id) context = { 'course': course, 'deadline': ( get_default_time_display(expired_verified_course_mode.expiration_datetime) if expired_verified_course_mode.expiration_datetime else "" ) } return render_to_response("verify_student/missed_verification_deadline.html", context) else: # Otherwise, there has never been a verified/paid mode, # so return a page not found response. log.warn( u"No paid/verified course mode found for course '%s' for verification/payment flow request", course_id ) raise Http404 # Check whether the user has verified, paid, and enrolled. # A user is considered "paid" if he or she has an enrollment # with a paid course mode (such as "verified"). # For this reason, every paid user is enrolled, but not # every enrolled user is paid. # If the course mode is not verified(i.e only paid) then already_verified is always True already_verified = self._check_already_verified(request.user) \ if CourseMode.is_verified_mode(unexpired_paid_course_mode) else True already_paid, is_enrolled = self._check_enrollment(request.user, course_key) # Redirect the user to a more appropriate page if the # messaging won't make sense based on the user's # enrollment / payment / verification status. redirect_response = self._redirect_if_necessary( message, already_verified, already_paid, is_enrolled, course_key ) if redirect_response is not None: return redirect_response display_steps = self._display_steps( always_show_payment, already_verified, already_paid, unexpired_paid_course_mode ) requirements = self._requirements(display_steps, request.user.is_active) if current_step is None: current_step = display_steps[0]['name'] # Allow the caller to skip the first page # This is useful if we want the user to be able to # use the "back" button to return to the previous step. # This parameter should only work for known skip-able steps if request.GET.get('skip-first-step') and current_step in self.SKIP_STEPS: display_step_names = [step['name'] for step in display_steps] current_step_idx = display_step_names.index(current_step) if (current_step_idx + 1) < len(display_steps): current_step = display_steps[current_step_idx + 1]['name'] courseware_url = "" if not course.start or course.start < datetime.datetime.today().replace(tzinfo=UTC): courseware_url = reverse( 'course_root', kwargs={'course_id': unicode(course_key)} ) full_name = ( request.user.profile.name if request.user.profile.name else "" ) # If the user set a contribution amount on another page, # use that amount to pre-fill the price selection form. contribution_amount = request.session.get( 'donation_for_course', {} ).get(unicode(course_key), '') # Remember whether the user is upgrading # so we can fire an analytics event upon payment. request.session['attempting_upgrade'] = (message == self.UPGRADE_MSG) # Determine the photo verification status verification_good_until = self._verification_valid_until(request.user) # Render the top-level page context = { 'contribution_amount': contribution_amount, 'course': course, 'course_key': unicode(course_key), 'course_mode': unexpired_paid_course_mode, 'courseware_url': courseware_url, 'current_step': current_step, 'disable_courseware_js': True, 'display_steps': display_steps, 'is_active': json.dumps(request.user.is_active), 'message_key': message, 'platform_name': settings.PLATFORM_NAME, 'purchase_endpoint': get_purchase_endpoint(), 'requirements': requirements, 'user_full_name': full_name, 'verification_deadline': ( get_default_time_display(unexpired_paid_course_mode.expiration_datetime) if unexpired_paid_course_mode.expiration_datetime else "" ), 'already_verified': already_verified, 'verification_good_until': verification_good_until, } return render_to_response("verify_student/pay_and_verify.html", context)
def get( self, request, course_id, always_show_payment=False, current_step=None, message=FIRST_TIME_VERIFY_MSG ): """Render the pay/verify requirements page. Arguments: request (HttpRequest): The request object. course_id (unicode): The ID of the course the user is trying to enroll in. Keyword Arguments: always_show_payment (bool): If True, show the payment steps even if the user has already paid. This is useful for users returning to the flow after paying. current_step (string): The current step in the flow. message (string): The messaging to display. Returns: HttpResponse Raises: Http404: The course does not exist or does not have a verified mode. """ # Parse the course key # The URL regex should guarantee that the key format is valid. course_key = CourseKey.from_string(course_id) course = modulestore().get_course(course_key) # Verify that the course exists and has a verified mode if course is None: log.warn(u"No course specified for verification flow request.") raise Http404 # Check whether the user has access to this course # based on country access rules. redirect_url = embargo_api.redirect_if_blocked( course_key, user=request.user, ip_address=get_ip(request), url=request.path ) if redirect_url: return redirect(redirect_url) expired_verified_course_mode, unexpired_paid_course_mode = self._get_expired_verified_and_paid_mode(course_key) # Check that the course has an unexpired paid mode if unexpired_paid_course_mode is not None: if CourseMode.is_verified_mode(unexpired_paid_course_mode): log.info( u"Entering verified workflow for user '%s', course '%s', with current step '%s'.", request.user.id, course_id, current_step ) elif expired_verified_course_mode is not None: # Check if there is an *expired* verified course mode; # if so, we should show a message explaining that the verification # deadline has passed. log.info(u"Verification deadline for '%s' has passed.", course_id) context = { 'course': course, 'deadline': ( get_default_time_display(expired_verified_course_mode.expiration_datetime) if expired_verified_course_mode.expiration_datetime else "" ) } return render_to_response("verify_student/missed_verification_deadline.html", context) else: # Otherwise, there has never been a verified/paid mode, # so return a page not found response. log.warn( u"No paid/verified course mode found for course '%s' for verification/payment flow request", course_id ) raise Http404 # Check whether the user has verified, paid, and enrolled. # A user is considered "paid" if he or she has an enrollment # with a paid course mode (such as "verified"). # For this reason, every paid user is enrolled, but not # every enrolled user is paid. # If the course mode is not verified(i.e only paid) then already_verified is always True already_verified = self._check_already_verified(request.user) \ if CourseMode.is_verified_mode(unexpired_paid_course_mode) else True already_paid, is_enrolled = self._check_enrollment(request.user, course_key) # Redirect the user to a more appropriate page if the # messaging won't make sense based on the user's # enrollment / payment / verification status. redirect_response = self._redirect_if_necessary( message, already_verified, already_paid, is_enrolled, course_key ) if redirect_response is not None: return redirect_response display_steps = self._display_steps( always_show_payment, already_verified, already_paid, unexpired_paid_course_mode ) requirements = self._requirements(display_steps, request.user.is_active) if current_step is None: current_step = display_steps[0]['name'] # Allow the caller to skip the first page # This is useful if we want the user to be able to # use the "back" button to return to the previous step. # This parameter should only work for known skip-able steps if request.GET.get('skip-first-step') and current_step in self.SKIP_STEPS: display_step_names = [step['name'] for step in display_steps] current_step_idx = display_step_names.index(current_step) if (current_step_idx + 1) < len(display_steps): current_step = display_steps[current_step_idx + 1]['name'] courseware_url = "" if not course.start or course.start < datetime.datetime.today().replace(tzinfo=UTC): courseware_url = reverse( 'course_root', kwargs={'course_id': unicode(course_key)} ) full_name = ( request.user.profile.name if request.user.profile.name else "" ) # If the user set a contribution amount on another page, # use that amount to pre-fill the price selection form. contribution_amount = request.session.get( 'donation_for_course', {} ).get(unicode(course_key), '') # Remember whether the user is upgrading # so we can fire an analytics event upon payment. request.session['attempting_upgrade'] = (message == self.UPGRADE_MSG) # Render the top-level page context = { 'contribution_amount': contribution_amount, 'course': course, 'course_key': unicode(course_key), 'course_mode': unexpired_paid_course_mode, 'courseware_url': courseware_url, 'current_step': current_step, 'disable_courseware_js': True, 'display_steps': display_steps, 'is_active': json.dumps(request.user.is_active), 'message_key': message, 'platform_name': settings.PLATFORM_NAME, 'purchase_endpoint': get_purchase_endpoint(), 'requirements': requirements, 'user_full_name': full_name, 'verification_deadline': ( get_default_time_display(unexpired_paid_course_mode.expiration_datetime) if unexpired_paid_course_mode.expiration_datetime else "" ), } return render_to_response("verify_student/pay_and_verify.html", context)
def get(self, request, course_id, error=None): """Displays the course mode choice page. Args: request (`Request`): The Django Request object. course_id (unicode): The slash-separated course key. Keyword Args: error (unicode): If provided, display this error message on the page. Returns: Response """ course_key = CourseKey.from_string(course_id) # Check whether the user has access to this course # based on country access rules. embargo_redirect = embargo_api.redirect_if_blocked( course_key, user=request.user, ip_address=get_ip(request), url=request.path) if embargo_redirect: return redirect(embargo_redirect) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user( request.user, course_key) modes = CourseMode.modes_for_course_dict(course_key) # We assume that, if 'professional' is one of the modes, it is the *only* mode. # If we offer more modes alongside 'professional' in the future, this will need to route # to the usual "choose your track" page same is true for no-id-professional mode. has_enrolled_professional = ( CourseMode.is_professional_slug(enrollment_mode) and is_active) if CourseMode.has_professional_mode( modes) and not has_enrolled_professional: return redirect( reverse('verify_student_start_flow', kwargs={'course_id': unicode(course_key)})) # If there isn't a verified mode available, then there's nothing # to do on this page. The user has almost certainly been auto-registered # in the "honor" track by this point, so we send the user # to the dashboard. if not CourseMode.has_verified_mode(modes): return redirect(reverse('dashboard')) # If a user has already paid, redirect them to the dashboard. if is_active and (enrollment_mode in CourseMode.VERIFIED_MODES + [CourseMode.NO_ID_PROFESSIONAL_MODE]): return redirect(reverse('dashboard')) donation_for_course = request.session.get("donation_for_course", {}) chosen_price = donation_for_course.get(unicode(course_key), None) course = modulestore().get_course(course_key) # When a credit mode is available, students will be given the option # to upgrade from a verified mode to a credit mode at the end of the course. # This allows students who have completed photo verification to be eligible # for univerity credit. # Since credit isn't one of the selectable options on the track selection page, # we need to check *all* available course modes in order to determine whether # a credit mode is available. If so, then we show slightly different messaging # for the verified track. has_credit_upsell = any( CourseMode.is_credit_mode(mode) for mode in CourseMode.modes_for_course(course_key, only_selectable=False)) context = { "course_modes_choose_url": reverse("course_modes_choose", kwargs={'course_id': course_key.to_deprecated_string()}), "modes": modes, "has_credit_upsell": has_credit_upsell, "course_name": course.display_name_with_default, "course_org": course.display_org_with_default, "course_num": course.display_number_with_default, "chosen_price": chosen_price, "error": error, "responsive": True } if "verified" in modes: context["suggested_prices"] = [ decimal.Decimal(x.strip()) for x in modes["verified"].suggested_prices.split(",") if x.strip() ] context["currency"] = modes["verified"].currency.upper() context["min_price"] = modes["verified"].min_price context["verified_name"] = modes["verified"].name context["verified_description"] = modes["verified"].description return render_to_response("course_modes/choose.html", context)
def register_code_redemption(request, registration_code): """ This view allows the student to redeem the registration code and enroll in the course. """ # Add some rate limiting here by re-using the RateLimitMixin as a helper class site_name = microsite.get_value('SITE_NAME', settings.SITE_NAME) limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Rate limit exceeded in registration code redemption.") return HttpResponseForbidden() template_to_render = 'shoppingcart/registration_code_redemption.html' if request.method == "GET": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity(registration_code, request, limiter) course = get_course_by_id(course_registration.course_id, depth=0) # Restrict the user from enrolling based on country access rules embargo_redirect = embargo_api.redirect_if_blocked( course.id, user=request.user, ip_address=get_ip(request), url=request.path ) if embargo_redirect is not None: return redirect(embargo_redirect) context = { 'reg_code_already_redeemed': reg_code_already_redeemed, 'reg_code_is_valid': reg_code_is_valid, 'reg_code': registration_code, 'site_name': site_name, 'course': course, 'registered_for_course': not _is_enrollment_code_an_update(course, request.user, course_registration) } return render_to_response(template_to_render, context) elif request.method == "POST": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity( registration_code, request, limiter ) course = get_course_by_id(course_registration.course_id, depth=0) # Restrict the user from enrolling based on country access rules embargo_redirect = embargo_api.redirect_if_blocked( course.id, user=request.user, ip_address=get_ip(request), url=request.path ) if embargo_redirect is not None: return redirect(embargo_redirect) context = { 'reg_code': registration_code, 'site_name': site_name, 'course': course, 'reg_code_is_valid': reg_code_is_valid, 'reg_code_already_redeemed': reg_code_already_redeemed, } if reg_code_is_valid and not reg_code_already_redeemed: # remove the course from the cart if it was added there. cart = Order.get_cart_for_user(request.user) try: cart_items = cart.find_item_by_course_id(course_registration.course_id) except ItemNotFoundInCartException: pass else: for cart_item in cart_items: if isinstance(cart_item, PaidCourseRegistration) or isinstance(cart_item, CourseRegCodeItem): cart_item.delete() #now redeem the reg code. redemption = RegistrationCodeRedemption.create_invoice_generated_registration_redemption(course_registration, request.user) try: kwargs = {} if course_registration.mode_slug is not None: if CourseMode.mode_for_course(course.id, course_registration.mode_slug): kwargs['mode'] = course_registration.mode_slug else: raise RedemptionCodeError() redemption.course_enrollment = CourseEnrollment.enroll(request.user, course.id, **kwargs) redemption.save() context['redemption_success'] = True except RedemptionCodeError: context['redeem_code_error'] = True context['redemption_success'] = False except EnrollmentClosedError: context['enrollment_closed'] = True context['redemption_success'] = False except CourseFullError: context['course_full'] = True context['redemption_success'] = False except AlreadyEnrolledError: context['registered_for_course'] = True context['redemption_success'] = False else: context['redemption_success'] = False return render_to_response(template_to_render, context)
def get(self, request, course_id, error=None): """Displays the course mode choice page. Args: request (`Request`): The Django Request object. course_id (unicode): The slash-separated course key. Keyword Args: error (unicode): If provided, display this error message on the page. Returns: Response """ course_key = CourseKey.from_string(course_id) # Check whether the user has access to this course # based on country access rules. embargo_redirect = embargo_api.redirect_if_blocked( course_key, user=request.user, ip_address=get_ip(request), url=request.path ) if embargo_redirect: return redirect(embargo_redirect) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(request.user, course_key) modes = CourseMode.modes_for_course_dict(course_key) # We assume that, if 'professional' is one of the modes, it is the *only* mode. # If we offer more modes alongside 'professional' in the future, this will need to route # to the usual "choose your track" page same is true for no-id-professional mode. has_enrolled_professional = (CourseMode.is_professional_slug(enrollment_mode) and is_active) if CourseMode.has_professional_mode(modes) and not has_enrolled_professional: return redirect( reverse( 'verify_student_start_flow', kwargs={'course_id': unicode(course_key)} ) ) # If there isn't a verified mode available, then there's nothing # to do on this page. The user has almost certainly been auto-registered # in the "honor" track by this point, so we send the user # to the dashboard. if not CourseMode.has_verified_mode(modes): return redirect(reverse('dashboard')) # If a user has already paid, redirect them to the dashboard. if is_active and (enrollment_mode in CourseMode.VERIFIED_MODES + [CourseMode.NO_ID_PROFESSIONAL_MODE]): return redirect(reverse('dashboard')) donation_for_course = request.session.get("donation_for_course", {}) chosen_price = donation_for_course.get(unicode(course_key), None) course = modulestore().get_course(course_key) # When a credit mode is available, students will be given the option # to upgrade from a verified mode to a credit mode at the end of the course. # This allows students who have completed photo verification to be eligible # for univerity credit. # Since credit isn't one of the selectable options on the track selection page, # we need to check *all* available course modes in order to determine whether # a credit mode is available. If so, then we show slightly different messaging # for the verified track. has_credit_upsell = any( CourseMode.is_credit_mode(mode) for mode in CourseMode.modes_for_course(course_key, only_selectable=False) ) context = { "course_modes_choose_url": reverse("course_modes_choose", kwargs={'course_id': course_key.to_deprecated_string()}), "modes": modes, "has_credit_upsell": has_credit_upsell, "course_name": course.display_name_with_default, "course_org": course.display_org_with_default, "course_num": course.display_number_with_default, "chosen_price": chosen_price, "error": error, "can_audit": "audit" in modes, "responsive": True } if "verified" in modes: context["suggested_prices"] = [ decimal.Decimal(x.strip()) for x in modes["verified"].suggested_prices.split(",") if x.strip() ] context["currency"] = modes["verified"].currency.upper() context["min_price"] = modes["verified"].min_price context["verified_name"] = modes["verified"].name context["verified_description"] = modes["verified"].description return render_to_response("course_modes/choose.html", context)
def get(self, request, course_id, error=None): """Displays the course mode choice page. Args: request (`Request`): The Django Request object. course_id (unicode): The slash-separated course key. Keyword Args: error (unicode): If provided, display this error message on the page. Returns: Response """ course_key = CourseKey.from_string(course_id) # Check whether the user has access to this course # based on country access rules. embargo_redirect = embargo_api.redirect_if_blocked( course_key, user=request.user, ip_address=get_ip(request), url=request.path) if embargo_redirect: return redirect(embargo_redirect) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user( request.user, course_key) modes = CourseMode.modes_for_course_dict(course_key) # We assume that, if 'professional' is one of the modes, it is the *only* mode. # If we offer more modes alongside 'professional' in the future, this will need to route # to the usual "choose your track" page. has_enrolled_professional = (enrollment_mode == "professional" and is_active) if "professional" in modes and not has_enrolled_professional: return redirect( reverse('verify_student_start_flow', kwargs={'course_id': unicode(course_key)})) # If there isn't a verified mode available, then there's nothing # to do on this page. The user has almost certainly been auto-registered # in the "honor" track by this point, so we send the user # to the dashboard. if not CourseMode.has_verified_mode(modes): return redirect(reverse('dashboard')) # If a user has already paid, redirect them to the dashboard. if is_active and enrollment_mode in CourseMode.VERIFIED_MODES: return redirect(reverse('dashboard')) donation_for_course = request.session.get("donation_for_course", {}) chosen_price = donation_for_course.get(unicode(course_key), None) course = modulestore().get_course(course_key) context = { "course_modes_choose_url": reverse("course_modes_choose", kwargs={'course_id': course_key.to_deprecated_string()}), "modes": modes, "course_name": course.display_name_with_default, "course_org": course.display_org_with_default, "course_num": course.display_number_with_default, "chosen_price": chosen_price, "error": error, "can_audit": "audit" in modes, "responsive": True } if "verified" in modes: context["suggested_prices"] = [ decimal.Decimal(x.strip()) for x in modes["verified"].suggested_prices.split(",") if x.strip() ] context["currency"] = modes["verified"].currency.upper() context["min_price"] = modes["verified"].min_price context["verified_name"] = modes["verified"].name context["verified_description"] = modes["verified"].description return render_to_response("course_modes/choose.html", context)