def test_enabled_for_enrollment_flag_override(self): self.assertTrue( ContentTypeGatingConfig.enabled_for_enrollment(None, None, None)) self.assertTrue( ContentTypeGatingConfig.enabled_for_enrollment( Mock(name='enrollment'), Mock(name='user'), None)) self.assertTrue( ContentTypeGatingConfig.enabled_for_enrollment( Mock(name='enrollment'), None, Mock(name='course_key')))
def content_type_gating_enabled(self): course_key = self.course_key user = self.effective_user is_enabled = None course_masquerade = self.course_masquerade if is_masquerading(user, course_key, course_masquerade): if is_masquerading_as_staff(user, course_key): is_enabled = False elif is_masquerading_as_full_access(user, course_key, course_masquerade): is_enabled = False elif is_masquerading_as_non_audit_enrollment( user, course_key, course_masquerade): is_enabled = False elif is_masquerading_as_audit_enrollment(user, course_key, course_masquerade): is_enabled = ContentTypeGatingConfig.enabled_for_course( course_key) elif is_masquerading_as_limited_access(user, course_key, course_masquerade): is_enabled = ContentTypeGatingConfig.enabled_for_course( course_key) if is_enabled is None: is_enabled = ContentTypeGatingConfig.enabled_for_enrollment( user=user, course_key=course_key, ) return is_enabled
def get_dates_banner_info(self, _): """ Serializer mixin for returning date banner info. Gets its input from the views course_key_string url parameter and the request's user object. """ info = { 'missed_deadlines': False, 'content_type_gating_enabled': False, } course_key_string = self.context['view'].kwargs.get( 'course_key_string') if course_key_string: course_key = CourseKey.from_string(course_key_string) request = self.context['request'] missed_deadlines, missed_gated_content = dates_banner_should_display( course_key, request.user) info['missed_deadlines'] = missed_deadlines info['missed_gated_content'] = missed_gated_content info[ 'content_type_gating_enabled'] = ContentTypeGatingConfig.enabled_for_enrollment( user=request.user, course_key=course_key, ) info['verified_upgrade_link'] = verified_upgrade_deadline_link( request.user, course_id=course_key) return info
def get(self, request, *args, **kwargs): course_key_string = kwargs.get('course_key_string') course_key = CourseKey.from_string(course_key_string) if course_home_legacy_is_active(course_key): raise Http404 # Enable NR tracing for this view based on course monitoring_utils.set_custom_attribute('course_id', course_key_string) monitoring_utils.set_custom_attribute('user_id', request.user.id) monitoring_utils.set_custom_attribute('is_staff', request.user.is_staff) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False) is_staff = bool(has_access(request.user, 'staff', course_key)) _, request.user = setup_masquerade( request, course_key, staff_access=is_staff, reset_masquerade_data=True, ) if not CourseEnrollment.is_enrolled(request.user, course_key) and not is_staff: return Response('User not enrolled.', status=401) blocks = get_course_date_blocks(course, request.user, request, include_access=True, include_past_dates=True) learner_is_full_access = not ContentTypeGatingConfig.enabled_for_enrollment( user=request.user, course_key=course_key, ) # User locale settings user_timezone_locale = user_timezone_locale_prefs(request) user_timezone = user_timezone_locale['user_timezone'] data = { 'has_ended': course.has_ended(), 'course_date_blocks': [block for block in blocks if not isinstance(block, TodaysDate)], 'learner_is_full_access': learner_is_full_access, 'user_timezone': user_timezone, } context = self.get_serializer_context() context['learner_is_full_access'] = learner_is_full_access serializer = self.get_serializer_class()(data, context=context) return Response(serializer.data)
def transform(self, usage_info, block_structure): if not ContentTypeGatingConfig.enabled_for_enrollment( user=usage_info.user, course_key=usage_info.course_key, ): return for block_key in block_structure.topological_traversal(): graded = block_structure.get_xblock_field(block_key, 'graded') has_score = block_structure.get_xblock_field( block_key, 'has_score') weight_not_zero = block_structure.get_xblock_field( block_key, 'weight') != 0 problem_eligible_for_content_gating = graded and has_score and weight_not_zero if problem_eligible_for_content_gating: current_access = self._get_block_group_access( block_structure, block_key) current_access.setdefault( CONTENT_GATING_PARTITION_ID, [settings.CONTENT_TYPE_GATE_GROUP_IDS['full_access']]) block_structure.override_xblock_field(block_key, 'group_access', current_access) if current_access[CONTENT_GATING_PARTITION_ID] == [ settings.CONTENT_TYPE_GATE_GROUP_IDS['full_access'] ]: self._set_contains_gated_content_on_parents( block_structure, block_key)
def get_group_for_user(cls, course_key, user, user_partition, **kwargs): # pylint: disable=unused-argument """ Returns the Group for the specified user. """ if not ContentTypeGatingConfig.enabled_for_enrollment(user=user, course_key=course_key): return FULL_ACCESS else: return LIMITED_ACCESS
def student_view(self, context): _ = self.runtime.service(self, "i18n").ugettext context = context or {} self._capture_basic_metrics() banner_text = None prereq_met = True prereq_meta_info = {} if TIMED_EXAM_GATING_WAFFLE_FLAG.is_enabled(): # Content type gating for FBE previously only gated individual blocks # This was an issue because audit learners could start a timed exam and then be unable to complete the exam # even if they later upgrade because the timer would have expired. # For this reason we check if content gating is enabled for the user # and gate the entire sequence in that case # This functionality still needs to be replicated in the frontend-app-learning courseware MFE # The ticket to track this is https://openedx.atlassian.net/browse/REV-1220 # Note that this will break compatability with using sequences outside of edx-platform # but we are ok with this for now if self.is_time_limited: try: user = User.objects.get(id=self.runtime.user_id) # importing here to avoid a circular import from openedx.features.content_type_gating.models import ContentTypeGatingConfig from openedx.features.content_type_gating.helpers import CONTENT_GATING_PARTITION_ID if ContentTypeGatingConfig.enabled_for_enrollment( user=user, course_key=self.runtime.course_id): # Get the content type gating locked content fragment to render for this sequence partition = self.descriptor._get_user_partition( CONTENT_GATING_PARTITION_ID) # pylint: disable=protected-access user_group = partition.scheme.get_group_for_user( self.runtime.course_id, user, partition) self.gated_sequence_fragment = partition.access_denied_fragment( self.descriptor, user, user_group, []) except User.DoesNotExist: pass if self._required_prereq(): if self.runtime.user_is_staff: banner_text = _( 'This subsection is unlocked for learners when they meet the prerequisite requirements.' ) else: # check if prerequisite has been met prereq_met, prereq_meta_info = self._compute_is_prereq_met( True) if prereq_met: special_html_view = self._hidden_content_student_view( context) or self._special_exam_student_view() if special_html_view: masquerading_as_specific_student = context.get( 'specific_masquerade', False) banner_text, special_html = special_html_view if special_html and not masquerading_as_specific_student: return Fragment(special_html) return self._student_or_public_view(context, prereq_met, prereq_meta_info, banner_text)
def get_group_for_user(cls, course_key, user, user_partition, **kwargs): # pylint: disable=unused-argument """ Returns the Group for the specified user. """ if not ContentTypeGatingConfig.enabled_for_enrollment(user=user, course_key=course_key, user_partition=user_partition): return FULL_ACCESS else: return LIMITED_ACCESS
def get_group_for_user(cls, course_key, user, user_partition, **kwargs): # pylint: disable=unused-argument """ Returns the Group for the specified user. """ # For now, treat everyone as a Full-access user, until we have the rest of the # feature gating logic in place. if not ContentTypeGatingConfig.enabled_for_enrollment( user=user, course_key=course_key, user_partition=user_partition): return FULL_ACCESS # If CONTENT_TYPE_GATING is enabled use the following logic to determine whether a user should have FULL_ACCESS # or LIMITED_ACCESS course_mode = apps.get_model('course_modes.CourseMode') modes = course_mode.modes_for_course(course_key, include_expired=True, only_selectable=False) modes_dict = {mode.slug: mode for mode in modes} # If there is no verified mode, all users are granted FULL_ACCESS if not course_mode.has_verified_mode(modes_dict): return FULL_ACCESS course_enrollment = apps.get_model('student.CourseEnrollment') mode_slug, is_active = course_enrollment.enrollment_mode_for_user( user, course_key) if mode_slug and is_active: course_mode = course_mode.mode_for_course( course_key, mode_slug, modes=modes, ) if course_mode is None: LOG.error( "User %s is in an unknown CourseMode '%s'" " for course %s. Granting full access to content for this user", user.username, mode_slug, course_key, ) return FULL_ACCESS if mode_slug == CourseMode.AUDIT: return LIMITED_ACCESS else: return FULL_ACCESS else: # Unenrolled users don't get gated content return LIMITED_ACCESS
def get(self, request, *args, **kwargs): course_key_string = kwargs.get('course_key_string') course_key = CourseKey.from_string(course_key_string) if not course_home_mfe_dates_tab_is_active(course_key): return Response(status=status.HTTP_404_NOT_FOUND) # Enable NR tracing for this view based on course monitoring_utils.set_custom_metric('course_id', course_key_string) monitoring_utils.set_custom_metric('user_id', request.user.id) monitoring_utils.set_custom_metric('is_staff', request.user.is_staff) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False) blocks = get_course_date_blocks(course, request.user, request, include_access=True, include_past_dates=True) missed_deadlines, missed_gated_content = dates_banner_should_display( course_key, request.user) learner_is_full_access = not ContentTypeGatingConfig.enabled_for_enrollment( user=request.user, course_key=course_key, ) # User locale settings user_timezone_locale = user_timezone_locale_prefs(request) user_timezone = user_timezone_locale['user_timezone'] data = { 'has_ended': course.has_ended(), 'course_date_blocks': [block for block in blocks if not isinstance(block, TodaysDate)], 'missed_deadlines': missed_deadlines, 'missed_gated_content': missed_gated_content, 'learner_is_full_access': learner_is_full_access, 'user_timezone': user_timezone, 'verified_upgrade_link': verified_upgrade_deadline_link(request.user, course=course), } context = self.get_serializer_context() context['learner_is_full_access'] = learner_is_full_access serializer = self.get_serializer_class()(data, context=context) return Response(serializer.data)
def test_enabled_for_enrollment_failure(self): with self.assertRaises(ValueError): ContentTypeGatingConfig.enabled_for_enrollment(None, None, None) with self.assertRaises(ValueError): ContentTypeGatingConfig.enabled_for_enrollment(Mock(name='enrollment'), Mock(name='user'), None) with self.assertRaises(ValueError): ContentTypeGatingConfig.enabled_for_enrollment(Mock(name='enrollment'), None, Mock(name='course_key'))
def test_enabled_for_enrollment( self, already_enrolled, pass_enrollment, enrolled_before_enabled, ): # Tweak the datetime to enable the config so that it is either before # or after now (which is when the enrollment will be created) if enrolled_before_enabled: enabled_as_of = datetime.now() + timedelta(days=1) else: enabled_as_of = datetime.now() - timedelta(days=1) config = ContentTypeGatingConfig.objects.create( enabled=True, course=self.course_overview, enabled_as_of=enabled_as_of, ) if already_enrolled: existing_enrollment = CourseEnrollmentFactory.create( user=self.user, course=self.course_overview, ) else: existing_enrollment = None if pass_enrollment: enrollment = existing_enrollment user = None course_key = None else: enrollment = None user = self.user course_key = self.course_overview.id if already_enrolled and pass_enrollment: query_count = 7 elif not pass_enrollment and already_enrolled: query_count = 8 else: query_count = 7 with self.assertNumQueries(query_count): enabled = ContentTypeGatingConfig.enabled_for_enrollment( enrollment=enrollment, user=user, course_key=course_key, ) self.assertEqual(not enrolled_before_enabled, enabled)
def test_enabled_for_enrollment( self, already_enrolled, pass_enrollment, enrolled_before_enabled, ): # Tweak the datetime to enable the config so that it is either before # or after now (which is when the enrollment will be created) if enrolled_before_enabled: enabled_as_of = datetime.now() + timedelta(days=1) else: enabled_as_of = datetime.now() - timedelta(days=1) config = ContentTypeGatingConfig.objects.create( enabled=True, course=self.course_overview, enabled_as_of=enabled_as_of, ) if already_enrolled: existing_enrollment = CourseEnrollmentFactory.create( user=self.user, course=self.course_overview, ) else: existing_enrollment = None if pass_enrollment: enrollment = existing_enrollment user = None course_key = None else: enrollment = None user = self.user course_key = self.course_overview.id if already_enrolled and pass_enrollment: query_count = 4 elif not pass_enrollment and already_enrolled: query_count = 6 else: query_count = 5 with self.assertNumQueries(query_count): enabled = ContentTypeGatingConfig.enabled_for_enrollment( enrollment=enrollment, user=user, course_key=course_key, ) self.assertEqual(not enrolled_before_enabled, enabled)
def get_group_for_user(cls, course_key, user, user_partition, **kwargs): # pylint: disable=unused-argument """ Returns the Group for the specified user. """ # For now, treat everyone as a Full-access user, until we have the rest of the # feature gating logic in place. if not ContentTypeGatingConfig.enabled_for_enrollment(user=user, course_key=course_key, user_partition=user_partition): return FULL_ACCESS # If CONTENT_TYPE_GATING is enabled use the following logic to determine whether a user should have FULL_ACCESS # or LIMITED_ACCESS course_mode = apps.get_model('course_modes.CourseMode') modes = course_mode.modes_for_course(course_key, include_expired=True, only_selectable=False) modes_dict = {mode.slug: mode for mode in modes} # If there is no verified mode, all users are granted FULL_ACCESS if not course_mode.has_verified_mode(modes_dict): return FULL_ACCESS course_enrollment = apps.get_model('student.CourseEnrollment') mode_slug, is_active = course_enrollment.enrollment_mode_for_user(user, course_key) if mode_slug and is_active: course_mode = course_mode.mode_for_course( course_key, mode_slug, modes=modes, ) if course_mode is None: LOG.error( u"User %s is in an unknown CourseMode '%s'" u" for course %s. Granting full access to content for this user", user.username, mode_slug, course_key, ) return FULL_ACCESS if mode_slug == CourseMode.AUDIT: return LIMITED_ACCESS else: return FULL_ACCESS else: # Unenrolled users don't get gated content return LIMITED_ACCESS
def get_object(self): """ Return the requested course object, if the user has appropriate permissions. """ overview = course_detail( self.request, self.request.user.username, CourseKey.from_string(self.kwargs['course_key_string']), ) if self.request.user.is_anonymous: mode = None is_active = False else: mode, is_active = CourseEnrollment.enrollment_mode_for_user( overview.effective_user, overview.id) overview.enrollment = {'mode': mode, 'is_active': is_active} overview.is_staff = has_access(self.request.user, 'staff', overview).has_access overview.can_load_courseware = check_course_access( overview, self.request.user, 'load', check_if_enrolled=True, check_survey_complete=False, check_if_authenticated=True, ).to_json() # TODO: TNL-7185 Legacy: Refactor to return the expiration date and format the message in the MFE overview.course_expired_message = generate_course_expired_message( self.request.user, overview) # TODO: TNL-7185 Legacy: Refactor to return the offer data and format the message in the MFE overview.offer_html = generate_offer_html(self.request.user, overview) course_key = CourseKey.from_string(self.kwargs['course_key_string']) overview.content_type_gating_enabled = ContentTypeGatingConfig.enabled_for_enrollment( user=self.request.user, course_key=course_key, ) return overview
def transform(self, usage_info, block_structure): if not ContentTypeGatingConfig.enabled_for_enrollment( user=usage_info.user, course_key=usage_info.course_key, ): return for block_key in block_structure.topological_traversal(): graded = block_structure.get_xblock_field(block_key, 'graded') has_score = block_structure.get_xblock_field(block_key, 'has_score') weight_not_zero = block_structure.get_xblock_field(block_key, 'weight') != 0 problem_eligible_for_content_gating = graded and has_score and weight_not_zero if problem_eligible_for_content_gating: current_access = block_structure.get_xblock_field(block_key, 'group_access') if current_access is None: current_access = {} current_access.setdefault( CONTENT_GATING_PARTITION_ID, [settings.CONTENT_TYPE_GATE_GROUP_IDS['full_access']] ) block_structure.override_xblock_field(block_key, 'group_access', current_access)
def get_group_for_user(cls, course_key, user, user_partition, **kwargs): # pylint: disable=unused-argument """ Returns the Group for the specified user. """ # First, check if we have to deal with masquerading. # If the current user is masquerading as a specific student, use the # same logic as normal to return that student's group. If the current # user is masquerading as a generic student in a specific group, then # return that group. course_masquerade = get_course_masquerade(user, course_key) if course_masquerade and not is_masquerading_as_specific_student(user, course_key): masquerade_group = get_masquerading_user_group(course_key, user, user_partition) if masquerade_group is not None: return masquerade_group else: audit_mode_id = settings.COURSE_ENROLLMENT_MODES.get(CourseMode.AUDIT, {}).get('id') if course_masquerade.user_partition_id == ENROLLMENT_TRACK_PARTITION_ID: if course_masquerade.group_id != audit_mode_id: return cls.FULL_ACCESS else: return cls.LIMITED_ACCESS # For now, treat everyone as a Full-access user, until we have the rest of the # feature gating logic in place. if not ContentTypeGatingConfig.enabled_for_enrollment(user=user, course_key=course_key): return cls.FULL_ACCESS # If CONTENT_TYPE_GATING is enabled use the following logic to determine whether a user should have FULL_ACCESS # or LIMITED_ACCESS course_mode = apps.get_model('course_modes.CourseMode') modes = course_mode.modes_for_course(course_key, include_expired=True, only_selectable=False) modes_dict = {mode.slug: mode for mode in modes} # If there is no verified mode, all users are granted FULL_ACCESS if not course_mode.has_verified_mode(modes_dict): return cls.FULL_ACCESS # If the user is a beta tester for this course they are granted FULL_ACCESS if CourseBetaTesterRole(course_key).has_user(user): return cls.FULL_ACCESS course_enrollment = apps.get_model('student.CourseEnrollment') mode_slug, is_active = course_enrollment.enrollment_mode_for_user(user, course_key) if mode_slug and is_active: course_mode = course_mode.mode_for_course( course_key, mode_slug, modes=modes, ) if course_mode is None: LOG.error( "User %s is in an unknown CourseMode '%s'" " for course %s. Granting full access to content for this user", user.username, mode_slug, course_key, ) return cls.FULL_ACCESS if mode_slug == CourseMode.AUDIT: return cls.LIMITED_ACCESS else: return cls.FULL_ACCESS else: # Unenrolled users don't get gated content return cls.LIMITED_ACCESS
def content_type_gating_enabled(self): return ContentTypeGatingConfig.enabled_for_enrollment( user=self.effective_user, course_key=self.course_key, )
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.get_checkout_page_url(professional_mode.sku) if purchase_workflow == "bulk" and professional_mode.bulk_sku: redirect_url = ecommerce_service.get_checkout_page_url(professional_mode.bulk_sku) return redirect(redirect_url) course = modulestore().get_course(course_key) # If there isn't a verified mode available, then there's nothing # to do on this page. 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]): # If the course has started redirect to course home instead if course.has_started(): return redirect(reverse('openedx.course_experience.course_home', kwargs={'course_id': course_key})) return redirect(reverse('dashboard')) donation_for_course = request.session.get("donation_for_course", {}) chosen_price = donation_for_course.get(unicode(course_key), None) 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) ) course_id = text_type(course_key) context = { "course_modes_choose_url": reverse( "course_modes_choose", kwargs={'course_id': course_id} ), "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, "nav_hidden": True, "content_gating_enabled": ContentTypeGatingConfig.enabled_for_enrollment( user=request.user, course_key=course_key ), "course_duration_limit_enabled": CourseDurationLimitConfig.enabled_for_enrollment( user=request.user, course_key=course_key ), } context.update( get_experiment_user_metadata_context( course, request.user, ) ) title_content = _("Congratulations! You are now enrolled in {course_name}").format( course_name=course.display_name_with_default ) context["title_content"] = title_content 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 context['currency_data'] = [] if waffle.switch_is_active('local_currency'): if 'edx-price-l10n' not in request.COOKIES: currency_data = get_currency_data() try: context['currency_data'] = json.dumps(currency_data) except TypeError: pass return render_to_response("course_modes/choose.html", context)
def get(self, request, course_id, error=None): # lint-amnesty, pylint: disable=too-many-statements """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_client_ip(request)[0], url=request.path) if embargo_redirect: return redirect(embargo_redirect) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user( request.user, course_key) increment('track-selection.{}.{}'.format( enrollment_mode, 'active' if is_active else 'inactive')) increment('track-selection.views') if enrollment_mode is None: LOG.info( 'Rendering track selection for unenrolled user, referred by %s', request.META.get('HTTP_REFERER')) 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 'no-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") redirect_url = IDVerificationService.get_verify_location( course_id=course_key) 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.get_checkout_page_url( professional_mode.sku) if purchase_workflow == "bulk" and professional_mode.bulk_sku: redirect_url = ecommerce_service.get_checkout_page_url( professional_mode.bulk_sku) return redirect(redirect_url) course = modulestore().get_course(course_key) # If there isn't a verified mode available, then there's nothing # to do on this page. Send the user to the dashboard. if not CourseMode.has_verified_mode(modes): return self._redirect_to_course_or_dashboard( course, course_key, request.user) # 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 self._redirect_to_course_or_dashboard( course, course_key, request.user) donation_for_course = request.session.get("donation_for_course", {}) chosen_price = donation_for_course.get(str(course_key), None) 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 = six.moves.urllib.parse.urlencode( {'course_closed': enrollment_end_date}) return redirect('{}?{}'.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 university 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)) course_id = str(course_key) gated_content = ContentTypeGatingConfig.enabled_for_enrollment( user=request.user, course_key=course_key) context = { "course_modes_choose_url": reverse("course_modes_choose", kwargs={'course_id': course_id}), "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, "nav_hidden": True, "content_gating_enabled": gated_content, "course_duration_limit_enabled": CourseDurationLimitConfig.enabled_for_enrollment( request.user, course), } context.update( get_experiment_user_metadata_context( course, request.user, )) title_content = '' if enrollment_mode: title_content = _( "Congratulations! You are now enrolled in {course_name}" ).format(course_name=course.display_name_with_default) context["title_content"] = title_content 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() ] price_before_discount = verified_mode.min_price course_price = price_before_discount enterprise_customer = enterprise_customer_for_request(request) LOG.info( '[e-commerce calculate API] Going to hit the API for user [%s] linked to [%s] enterprise', request.user.username, enterprise_customer.get('name') if isinstance( enterprise_customer, dict) else None # Test Purpose ) if enterprise_customer and verified_mode.sku: course_price = get_course_final_price(request.user, verified_mode.sku, price_before_discount) context["currency"] = verified_mode.currency.upper() context["currency_symbol"] = get_currency_symbol( verified_mode.currency.upper()) context["min_price"] = course_price context["verified_name"] = verified_mode.name context["verified_description"] = verified_mode.description # if course_price is equal to price_before_discount then user doesn't entitle to any discount. if course_price != price_before_discount: context["price_before_discount"] = price_before_discount 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 context['currency_data'] = [] if waffle.switch_is_active('local_currency'): if 'edx-price-l10n' not in request.COOKIES: currency_data = get_currency_data() try: context['currency_data'] = json.dumps(currency_data) except TypeError: pass language = get_language() context['track_links'] = get_verified_track_links(language) duration = get_user_course_duration(request.user, course) deadline = duration and get_user_course_expiration_date( request.user, course) if deadline: formatted_audit_access_date = strftime_localized_html( deadline, 'SHORT_DATE') context['audit_access_deadline'] = formatted_audit_access_date fbe_is_on = deadline and gated_content # Route to correct Track Selection page. # REV-2133 TODO Value Prop: remove waffle flag after testing is completed # and happy path version is ready to be rolled out to all users. if VALUE_PROP_TRACK_SELECTION_FLAG.is_enabled(): if not error: # TODO: Remove by executing REV-2355 if not enterprise_customer_for_request( request): # TODO: Remove by executing REV-2342 if fbe_is_on: return render_to_response("course_modes/fbe.html", context) else: return render_to_response("course_modes/unfbe.html", context) # If error or enterprise_customer, failover to old choose.html page return render_to_response("course_modes/choose.html", context)
def render_to_fragment(self, request, course_id, user_is_enrolled=True, **kwargs): # pylint: disable=arguments-differ """ Renders the course outline as a fragment. """ from lms.urls import RESET_COURSE_DEADLINES_NAME from openedx.features.course_experience.urls import COURSE_HOME_VIEW_NAME course_key = CourseKey.from_string(course_id) course_overview = get_course_overview_with_access( request.user, 'load', course_key, check_if_enrolled=user_is_enrolled) course = modulestore().get_course(course_key) course_block_tree = get_course_outline_block_tree( request, course_id, request.user if user_is_enrolled else None) if not course_block_tree: return None context = { 'csrf': csrf(request)['csrf_token'], 'course': course_overview, 'due_date_display_format': course.due_date_display_format, 'blocks': course_block_tree, 'enable_links': user_is_enrolled or course.course_visibility == COURSE_VISIBILITY_PUBLIC, 'course_key': course_key, } resume_block = get_resume_block( course_block_tree) if user_is_enrolled else None if not resume_block: self.mark_first_unit_to_resume(course_block_tree) xblock_display_names = self.create_xblock_id_and_name_dict( course_block_tree) gated_content = self.get_content_milestones(request, course_key) context['gated_content'] = gated_content context['xblock_display_names'] = xblock_display_names page_context = kwargs.get('page_context', None) if page_context: context['self_paced'] = page_context.get( 'pacing_type', 'instructor_paced') == 'self_paced' # We're using this flag to prevent old self-paced dates from leaking out on courses not # managed by edx-when. context['in_edx_when'] = edx_when_api.is_enabled_for_course(course_key) reset_deadlines_url = reverse(RESET_COURSE_DEADLINES_NAME) reset_deadlines_redirect_url_base = COURSE_HOME_VIEW_NAME context['reset_deadlines_url'] = reset_deadlines_url context[ 'reset_deadlines_redirect_url_base'] = reset_deadlines_redirect_url_base context['reset_deadlines_redirect_url_id_dict'] = { 'course_id': str(course.id) } context['verified_upgrade_link'] = verified_upgrade_deadline_link( request.user, course=course) context['on_course_outline_page'] = True context[ 'content_type_gating_enabled'] = ContentTypeGatingConfig.enabled_for_enrollment( user=request.user, course_key=course_key) # We use javascript to check whether to actually display this banner, so we let the banner assume # that deadlines have been missed. context['missed_deadlines'] = True html = render_to_string( 'course_experience/course-outline-fragment.html', context) return Fragment(html)
def _enabled_for_enrollment(self, **kwargs): """ Returns whether content type gating is enabled for a given user/course pair """ return ContentTypeGatingConfig.enabled_for_enrollment(**kwargs)
def test_enabled_for_enrollment_flag_override(self): self.assertTrue(ContentTypeGatingConfig.enabled_for_enrollment(None, None, None)) self.assertTrue(ContentTypeGatingConfig.enabled_for_enrollment(Mock(name='enrollment'), Mock(name='user'), None)) self.assertTrue(ContentTypeGatingConfig.enabled_for_enrollment(Mock(name='enrollment'), None, Mock(name='course_key')))