示例#1
0
 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')))
示例#2
0
 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
示例#3
0
 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
示例#4
0
    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)
示例#5
0
    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)
示例#6
0
 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
示例#7
0
    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)
示例#8
0
 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
示例#9
0
    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
示例#10
0
    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)
示例#11
0
 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'))
示例#12
0
 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'))
示例#13
0
    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)
示例#14
0
    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)
示例#15
0
    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
示例#16
0
    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
示例#17
0
    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)
示例#18
0
    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
示例#19
0
 def content_type_gating_enabled(self):
     return ContentTypeGatingConfig.enabled_for_enrollment(
         user=self.effective_user,
         course_key=self.course_key,
     )
示例#20
0
    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)
示例#21
0
    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)
示例#22
0
    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)
示例#23
0
 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)
示例#24
0
 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')))