def _get_course_duration_info(self, course_key):
        """
        Fetch course duration information from database
        """
        try:
            key = CourseKey.from_string(course_key)
            course = CourseOverview.objects.values('display_name').get(id=key)
            duration_config = CourseDurationLimitConfig.current(course_key=key)
            gating_config = ContentTypeGatingConfig.current(course_key=key)
            duration_enabled = CourseDurationLimitConfig.enabled_for_course(course_key=key)
            gating_enabled = ContentTypeGatingConfig.enabled_for_course(course_key=key)

            gating_dict = {
                'enabled': gating_enabled,
                'enabled_as_of': str(gating_config.enabled_as_of) if gating_config.enabled_as_of else 'N/A',
                'reason': gating_config.provenances['enabled'].value
            }
            duration_dict = {
                'enabled': duration_enabled,
                'enabled_as_of': str(duration_config.enabled_as_of) if duration_config.enabled_as_of else 'N/A',
                'reason': duration_config.provenances['enabled'].value
            }

            return {
                'course_id': course_key,
                'course_name': course.get('display_name'),
                'gating_config': gating_dict,
                'duration_config': duration_dict,
            }

        except (ObjectDoesNotExist, InvalidKeyError):
            return {}
Esempio n. 2
0
    def test_caching_global(self):
        global_config = ContentTypeGatingConfig(enabled=True,
                                                enabled_as_of=datetime(
                                                    2018, 1, 1))
        global_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the global value is not retrieved from cache after save
        with self.assertNumQueries(1):
            assert ContentTypeGatingConfig.current().enabled

        RequestCache.clear_all_namespaces()

        # Check that the global value can be retrieved from cache after read
        with self.assertNumQueries(0):
            assert ContentTypeGatingConfig.current().enabled

        global_config.enabled = False
        global_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the global value in cache was deleted on save
        with self.assertNumQueries(1):
            assert not ContentTypeGatingConfig.current().enabled
Esempio n. 3
0
def get_course_duration_info(course_key):
    """
    Fetch course duration information from database.
    """
    try:
        key = CourseKey.from_string(course_key)
        course = CourseOverview.objects.values('display_name').get(id=key)
        duration_config = CourseDurationLimitConfig.current(course_key=key)
        gating_config = ContentTypeGatingConfig.current(course_key=key)
        duration_enabled = CourseDurationLimitConfig.enabled_for_course(course_key=key)
        gating_enabled = ContentTypeGatingConfig.enabled_for_course(course_key=key)

        gating_dict = {
            'enabled': gating_enabled,
            'enabled_as_of': str(gating_config.enabled_as_of) if gating_config.enabled_as_of else 'N/A',
            'reason': gating_config.provenances['enabled'].value
        }
        duration_dict = {
            'enabled': duration_enabled,
            'enabled_as_of': str(duration_config.enabled_as_of) if duration_config.enabled_as_of else 'N/A',
            'reason': duration_config.provenances['enabled'].value
        }

        return {
            'course_id': course_key,
            'course_name': course.get('display_name'),
            'gating_config': gating_dict,
            'duration_config': duration_dict,
        }

    except (ObjectDoesNotExist, InvalidKeyError):
        return {}
Esempio n. 4
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
Esempio n. 5
0
    def test_config_overrides(self, global_setting, site_setting, org_setting, course_setting):
        """
        Test that the stacked configuration overrides happen in the correct order and priority.

        This is tested by exhaustively setting each combination of contexts, and validating that only
        the lowest level context that is set to not-None is applied.
        """
        # Add a bunch of configuration outside the contexts that are being tested, to make sure
        # there are no leaks of configuration across contexts
        non_test_course_enabled = CourseOverviewFactory.create(org='non-test-org-enabled')
        non_test_course_disabled = CourseOverviewFactory.create(org='non-test-org-disabled')
        non_test_site_cfg_enabled = SiteConfigurationFactory.create(
            site_values={'course_org_filter': non_test_course_enabled.org}
        )
        non_test_site_cfg_disabled = SiteConfigurationFactory.create(
            site_values={'course_org_filter': non_test_course_disabled.org}
        )

        ContentTypeGatingConfig.objects.create(
            course=non_test_course_enabled, enabled=True, enabled_as_of=datetime(2018, 1, 1)
        )
        ContentTypeGatingConfig.objects.create(course=non_test_course_disabled, enabled=False)
        ContentTypeGatingConfig.objects.create(
            org=non_test_course_enabled.org, enabled=True, enabled_as_of=datetime(2018, 1, 1)
        )
        ContentTypeGatingConfig.objects.create(org=non_test_course_disabled.org, enabled=False)
        ContentTypeGatingConfig.objects.create(
            site=non_test_site_cfg_enabled.site, enabled=True, enabled_as_of=datetime(2018, 1, 1)
        )
        ContentTypeGatingConfig.objects.create(site=non_test_site_cfg_disabled.site, enabled=False)

        # Set up test objects
        test_course = CourseOverviewFactory.create(org='test-org')
        test_site_cfg = SiteConfigurationFactory.create(
            site_values={'course_org_filter': test_course.org}
        )

        ContentTypeGatingConfig.objects.create(enabled=global_setting, enabled_as_of=datetime(2018, 1, 1))
        ContentTypeGatingConfig.objects.create(
            course=test_course, enabled=course_setting, enabled_as_of=datetime(2018, 1, 1)
        )
        ContentTypeGatingConfig.objects.create(
            org=test_course.org, enabled=org_setting, enabled_as_of=datetime(2018, 1, 1)
        )
        ContentTypeGatingConfig.objects.create(
            site=test_site_cfg.site, enabled=site_setting, enabled_as_of=datetime(2018, 1, 1)
        )

        all_settings = [global_setting, site_setting, org_setting, course_setting]
        expected_global_setting = self._resolve_settings([global_setting])
        expected_site_setting = self._resolve_settings([global_setting, site_setting])
        expected_org_setting = self._resolve_settings([global_setting, site_setting, org_setting])
        expected_course_setting = self._resolve_settings([global_setting, site_setting, org_setting, course_setting])

        assert expected_global_setting == ContentTypeGatingConfig.current().enabled
        assert expected_site_setting == ContentTypeGatingConfig.current(site=test_site_cfg.site).enabled
        assert expected_org_setting == ContentTypeGatingConfig.current(org=test_course.org).enabled
        assert expected_course_setting == ContentTypeGatingConfig.current(course_key=test_course.id).enabled
Esempio n. 6
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')))
Esempio n. 7
0
    def test_enabled_for_course(
        self,
        before_enabled,
    ):
        config = ContentTypeGatingConfig.objects.create(
            enabled=True,
            course=self.course_overview,
            enabled_as_of=timezone.now(),
        )

        # Tweak the datetime to check for course enablement so it is either
        # before or after when the configuration was enabled
        if before_enabled:
            target_datetime = config.enabled_as_of - timedelta(days=1)
        else:
            target_datetime = config.enabled_as_of + timedelta(days=1)

        course_key = self.course_overview.id

        self.assertEqual(
            not before_enabled,
            ContentTypeGatingConfig.enabled_for_course(
                course_key=course_key,
                target_datetime=target_datetime,
            )
        )
Esempio n. 8
0
    def test_enabled_for_course(
        self,
        before_enabled,
    ):
        config = ContentTypeGatingConfig.objects.create(
            enabled=True,
            course=self.course_overview,
            enabled_as_of=timezone.now(),
        )

        # Tweak the datetime to check for course enablement so it is either
        # before or after when the configuration was enabled
        if before_enabled:
            target_datetime = config.enabled_as_of - timedelta(days=1)
        else:
            target_datetime = config.enabled_as_of + timedelta(days=1)

        course_key = self.course_overview.id

        self.assertEqual(
            not before_enabled,
            ContentTypeGatingConfig.enabled_for_course(
                course_key=course_key,
                target_datetime=target_datetime,
            ))
Esempio n. 9
0
def create_content_gating_partition(course):
    """
    Create and return the Content Gating user partition.
    """

    if not ContentTypeGatingConfig.enabled_for_course(course_key=course.id):
        return None

    try:
        content_gate_scheme = UserPartition.get_scheme("content_type_gate")
    except UserPartitionError:
        LOG.warning("No 'content_type_gate' scheme registered, ContentTypeGatingPartitionScheme will not be created.")
        return None

    used_ids = set(p.id for p in course.user_partitions)
    if CONTENT_GATING_PARTITION_ID in used_ids:
        # It's possible for course authors to add arbitrary partitions via XML import. If they do, and create a
        # partition with id 51, it will collide with the Content Gating Partition. We'll catch that here, and
        # then fix the course content as needed (or get the course team to).
        LOG.warning(
            "Can't add 'content_type_gate' partition, as ID {id} is assigned to {partition} in course {course}.".format(
                id=CONTENT_GATING_PARTITION_ID,
                partition=_get_partition_from_id(course.user_partitions, CONTENT_GATING_PARTITION_ID).name,
                course=unicode(course.id)
            )
        )
        return None

    partition = content_gate_scheme.create_user_partition(
        id=CONTENT_GATING_PARTITION_ID,
        name=_(u"Feature-based Enrollments"),
        description=_(u"Partition for segmenting users by access to gated content types"),
        parameters={"course_id": unicode(course.id)}
    )
    return partition
Esempio n. 10
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
Esempio n. 11
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)
Esempio n. 12
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)
Esempio n. 13
0
def create_content_gating_partition(course):
    """
    Create and return the Content Gating user partition.
    """

    enabled_for_course = ContentTypeGatingConfig.enabled_for_course(
        course_key=course.id)
    studio_override_for_course = ContentTypeGatingConfig.current(
        course_key=course.id).studio_override_enabled
    if not (enabled_for_course or studio_override_for_course):
        return None

    try:
        content_gate_scheme = UserPartition.get_scheme(
            CONTENT_TYPE_GATING_SCHEME)
    except UserPartitionError:
        LOG.warning(
            u"No %r scheme registered, ContentTypeGatingPartitionScheme will not be created.",
            CONTENT_TYPE_GATING_SCHEME)
        return None

    used_ids = set(p.id for p in course.user_partitions)
    if CONTENT_GATING_PARTITION_ID in used_ids:
        # It's possible for course authors to add arbitrary partitions via XML import. If they do, and create a
        # partition with id 51, it will collide with the Content Gating Partition. We'll catch that here, and
        # then fix the course content as needed (or get the course team to).
        LOG.warning(
            u"Can't add %r partition, as ID %r is assigned to %r in course %s.",
            CONTENT_TYPE_GATING_SCHEME,
            CONTENT_GATING_PARTITION_ID,
            _get_partition_from_id(course.user_partitions,
                                   CONTENT_GATING_PARTITION_ID).name,
            six.text_type(course.id),
        )
        return None

    partition = content_gate_scheme.create_user_partition(
        id=CONTENT_GATING_PARTITION_ID,
        # Content gating partition name should not be marked for translations
        # edX mobile apps expect it in english
        name=u"Feature-based Enrollments",
        description=_(
            u"Partition for segmenting users by access to gated content types"
        ),
        parameters={"course_id": six.text_type(course.id)})
    return partition
Esempio n. 14
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
Esempio n. 15
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)
Esempio n. 16
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
Esempio n. 17
0
    def test_caching_global(self):
        global_config = ContentTypeGatingConfig(enabled=True, enabled_as_of=date(2018, 1, 1))
        global_config.save()

        # Check that the global value is not retrieved from cache after save
        with self.assertNumQueries(1):
            self.assertTrue(ContentTypeGatingConfig.current().enabled)

        # Check that the global value can be retrieved from cache after read
        with self.assertNumQueries(0):
            self.assertTrue(ContentTypeGatingConfig.current().enabled)

        global_config.enabled = False
        global_config.save()

        # Check that the global value in cache was deleted on save
        with self.assertNumQueries(1):
            self.assertFalse(ContentTypeGatingConfig.current().enabled)
Esempio n. 18
0
    def test_all_current_course_configs(self):
        # Set up test objects
        for global_setting in (True, False, None):
            ContentTypeGatingConfig.objects.create(enabled=global_setting, enabled_as_of=datetime(2018, 1, 1))
            for site_setting in (True, False, None):
                test_site_cfg = SiteConfigurationFactory.create(
                    site_values={'course_org_filter': []}
                )
                ContentTypeGatingConfig.objects.create(site=test_site_cfg.site, enabled=site_setting, enabled_as_of=datetime(2018, 1, 1))

                for org_setting in (True, False, None):
                    test_org = "{}-{}".format(test_site_cfg.id, org_setting)
                    test_site_cfg.site_values['course_org_filter'].append(test_org)
                    test_site_cfg.save()

                    ContentTypeGatingConfig.objects.create(org=test_org, enabled=org_setting, enabled_as_of=datetime(2018, 1, 1))

                    for course_setting in (True, False, None):
                        test_course = CourseOverviewFactory.create(
                            org=test_org,
                            id=CourseLocator(test_org, 'test_course', 'run-{}'.format(course_setting))
                        )
                        ContentTypeGatingConfig.objects.create(course=test_course, enabled=course_setting, enabled_as_of=datetime(2018, 1, 1))

            with self.assertNumQueries(4):
                all_configs = ContentTypeGatingConfig.all_current_course_configs()

        # Deliberatly using the last all_configs that was checked after the 3rd pass through the global_settings loop
        # We should be creating 3^4 courses (3 global values * 3 site values * 3 org values * 3 course values)
        # Plus 1 for the edX/toy/2012_Fall course
        self.assertEqual(len(all_configs), 3**4 + 1)

        # Point-test some of the final configurations
        self.assertEqual(
            all_configs[CourseLocator('7-True', 'test_course', 'run-None')],
            {
                'enabled': (True, Provenance.org),
                'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC), Provenance.run),
                'studio_override_enabled': (None, Provenance.default),
            }
        )
        self.assertEqual(
            all_configs[CourseLocator('7-True', 'test_course', 'run-False')],
            {
                'enabled': (False, Provenance.run),
                'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC), Provenance.run),
                'studio_override_enabled': (None, Provenance.default),
            }
        )
        self.assertEqual(
            all_configs[CourseLocator('7-None', 'test_course', 'run-None')],
            {
                'enabled': (True, Provenance.site),
                'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC), Provenance.run),
                'studio_override_enabled': (None, Provenance.default),
            }
        )
Esempio n. 19
0
    def test_all_current_course_configs(self):
        # Set up test objects
        for global_setting in (True, False, None):
            ContentTypeGatingConfig.objects.create(enabled=global_setting, enabled_as_of=datetime(2018, 1, 1))
            for site_setting in (True, False, None):
                test_site_cfg = SiteConfigurationFactory.create(values={'course_org_filter': []})
                ContentTypeGatingConfig.objects.create(site=test_site_cfg.site, enabled=site_setting, enabled_as_of=datetime(2018, 1, 1))

                for org_setting in (True, False, None):
                    test_org = "{}-{}".format(test_site_cfg.id, org_setting)
                    test_site_cfg.values['course_org_filter'].append(test_org)
                    test_site_cfg.save()

                    ContentTypeGatingConfig.objects.create(org=test_org, enabled=org_setting, enabled_as_of=datetime(2018, 1, 1))

                    for course_setting in (True, False, None):
                        test_course = CourseOverviewFactory.create(
                            org=test_org,
                            id=CourseLocator(test_org, 'test_course', 'run-{}'.format(course_setting))
                        )
                        ContentTypeGatingConfig.objects.create(course=test_course, enabled=course_setting, enabled_as_of=datetime(2018, 1, 1))

            with self.assertNumQueries(4):
                all_configs = ContentTypeGatingConfig.all_current_course_configs()

        # Deliberatly using the last all_configs that was checked after the 3rd pass through the global_settings loop
        # We should be creating 3^4 courses (3 global values * 3 site values * 3 org values * 3 course values)
        # Plus 1 for the edX/toy/2012_Fall course
        self.assertEqual(len(all_configs), 3**4 + 1)

        # Point-test some of the final configurations
        self.assertEqual(
            all_configs[CourseLocator('7-True', 'test_course', 'run-None')],
            {
                'enabled': (True, Provenance.org),
                'enabled_as_of': (datetime(2018, 1, 1, 5, tzinfo=pytz.UTC), Provenance.run),
                'studio_override_enabled': (None, Provenance.default),
            }
        )
        self.assertEqual(
            all_configs[CourseLocator('7-True', 'test_course', 'run-False')],
            {
                'enabled': (False, Provenance.run),
                'enabled_as_of': (datetime(2018, 1, 1, 5, tzinfo=pytz.UTC), Provenance.run),
                'studio_override_enabled': (None, Provenance.default),
            }
        )
        self.assertEqual(
            all_configs[CourseLocator('7-None', 'test_course', 'run-None')],
            {
                'enabled': (True, Provenance.site),
                'enabled_as_of': (datetime(2018, 1, 1, 5, tzinfo=pytz.UTC), Provenance.run),
                'studio_override_enabled': (None, Provenance.default),
            }
        )
Esempio n. 20
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)
Esempio n. 21
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
Esempio n. 22
0
def create_content_gating_partition(course):
    """
    Create and return the Content Gating user partition.
    """

    enabled_for_course = ContentTypeGatingConfig.enabled_for_course(course_key=course.id)
    studio_override_for_course = ContentTypeGatingConfig.current(course_key=course.id).studio_override_enabled
    if not (enabled_for_course or studio_override_for_course):
        return None

    try:
        content_gate_scheme = UserPartition.get_scheme(CONTENT_TYPE_GATING_SCHEME)
    except UserPartitionError:
        LOG.warning(
            u"No %r scheme registered, ContentTypeGatingPartitionScheme will not be created.",
            CONTENT_TYPE_GATING_SCHEME
        )
        return None

    used_ids = set(p.id for p in course.user_partitions)
    if CONTENT_GATING_PARTITION_ID in used_ids:
        # It's possible for course authors to add arbitrary partitions via XML import. If they do, and create a
        # partition with id 51, it will collide with the Content Gating Partition. We'll catch that here, and
        # then fix the course content as needed (or get the course team to).
        LOG.warning(
            u"Can't add %r partition, as ID %r is assigned to %r in course %s.",
            CONTENT_TYPE_GATING_SCHEME,
            CONTENT_GATING_PARTITION_ID,
            _get_partition_from_id(course.user_partitions, CONTENT_GATING_PARTITION_ID).name,
            unicode(course.id),
        )
        return None

    partition = content_gate_scheme.create_user_partition(
        id=CONTENT_GATING_PARTITION_ID,
        name=_(u"Feature-based Enrollments"),
        description=_(u"Partition for segmenting users by access to gated content types"),
        parameters={"course_id": unicode(course.id)}
    )
    return partition
Esempio n. 23
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'))
Esempio n. 24
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'))
Esempio n. 25
0
    def test_config_overrides(self, global_setting, site_setting, org_setting, course_setting):
        """
        Test that the stacked configuration overrides happen in the correct order and priority.

        This is tested by exhaustively setting each combination of contexts, and validating that only
        the lowest level context that is set to not-None is applied.
        """
        # Add a bunch of configuration outside the contexts that are being tested, to make sure
        # there are no leaks of configuration across contexts
        non_test_course_enabled = CourseOverviewFactory.create(org='non-test-org-enabled')
        non_test_course_disabled = CourseOverviewFactory.create(org='non-test-org-disabled')
        non_test_site_cfg_enabled = SiteConfigurationFactory.create(values={'course_org_filter': non_test_course_enabled.org})
        non_test_site_cfg_disabled = SiteConfigurationFactory.create(values={'course_org_filter': non_test_course_disabled.org})

        ContentTypeGatingConfig.objects.create(course=non_test_course_enabled, enabled=True, enabled_as_of=datetime(2018, 1, 1))
        ContentTypeGatingConfig.objects.create(course=non_test_course_disabled, enabled=False)
        ContentTypeGatingConfig.objects.create(org=non_test_course_enabled.org, enabled=True, enabled_as_of=datetime(2018, 1, 1))
        ContentTypeGatingConfig.objects.create(org=non_test_course_disabled.org, enabled=False)
        ContentTypeGatingConfig.objects.create(site=non_test_site_cfg_enabled.site, enabled=True, enabled_as_of=datetime(2018, 1, 1))
        ContentTypeGatingConfig.objects.create(site=non_test_site_cfg_disabled.site, enabled=False)

        # Set up test objects
        test_course = CourseOverviewFactory.create(org='test-org')
        test_site_cfg = SiteConfigurationFactory.create(values={'course_org_filter': test_course.org})

        ContentTypeGatingConfig.objects.create(enabled=global_setting, enabled_as_of=datetime(2018, 1, 1))
        ContentTypeGatingConfig.objects.create(course=test_course, enabled=course_setting, enabled_as_of=datetime(2018, 1, 1))
        ContentTypeGatingConfig.objects.create(org=test_course.org, enabled=org_setting, enabled_as_of=datetime(2018, 1, 1))
        ContentTypeGatingConfig.objects.create(site=test_site_cfg.site, enabled=site_setting, enabled_as_of=datetime(2018, 1, 1))

        all_settings = [global_setting, site_setting, org_setting, course_setting]
        expected_global_setting = self._resolve_settings([global_setting])
        expected_site_setting = self._resolve_settings([global_setting, site_setting])
        expected_org_setting = self._resolve_settings([global_setting, site_setting, org_setting])
        expected_course_setting = self._resolve_settings([global_setting, site_setting, org_setting, course_setting])

        self.assertEqual(expected_global_setting, ContentTypeGatingConfig.current().enabled)
        self.assertEqual(expected_site_setting, ContentTypeGatingConfig.current(site=test_site_cfg.site).enabled)
        self.assertEqual(expected_org_setting, ContentTypeGatingConfig.current(org=test_course.org).enabled)
        self.assertEqual(expected_course_setting, ContentTypeGatingConfig.current(course_key=test_course.id).enabled)
Esempio n. 26
0
    def test_caching_global(self):
        global_config = ContentTypeGatingConfig(enabled=True, enabled_as_of=datetime(2018, 1, 1))
        global_config.save()

        # Check that the global value is not retrieved from cache after save
        with self.assertNumQueries(1):
            self.assertTrue(ContentTypeGatingConfig.current().enabled)

        # Check that the global value can be retrieved from cache after read
        with self.assertNumQueries(0):
            self.assertTrue(ContentTypeGatingConfig.current().enabled)

        global_config.enabled = False
        global_config.save()

        # Check that the global value in cache was deleted on save
        with self.assertNumQueries(1):
            self.assertFalse(ContentTypeGatingConfig.current().enabled)
Esempio n. 27
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)
Esempio n. 28
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)
Esempio n. 29
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
Esempio n. 30
0
    def _get_course_duration_info(self, course_key):
        """
        Fetch course duration information from database
        """
        results = []

        try:
            key = CourseKey.from_string(course_key)
            course = CourseOverview.objects.values('display_name').get(id=key)
            duration_config = CourseDurationLimitConfig.current(course_key=key)
            gating_config = ContentTypeGatingConfig.current(course_key=key)
            partially_enabled = duration_config.enabled != gating_config.enabled

            if partially_enabled:
                if duration_config.enabled:
                    enabled = 'Course Duration Limits Only'
                    enabled_as_of = str(
                        duration_config.enabled_as_of
                    ) if duration_config.enabled_as_of else 'N/A'
                    reason = 'Course duration limits are enabled for this course, but content type gating is disabled.'
                elif gating_config.enabled:
                    enabled = 'Content Type Gating Only'
                    enabled_as_of = str(
                        gating_config.enabled_as_of
                    ) if gating_config.enabled_as_of else 'N/A'
                    reason = 'Content type gating is enabled for this course, but course duration limits are disabled.'
            else:
                enabled = duration_config.enabled or False
                enabled_as_of = str(
                    duration_config.enabled_as_of
                ) if duration_config.enabled_as_of else 'N/A'
                reason = duration_config.provenances['enabled']

            data = {
                'course_id': course_key,
                'course_name': course.get('display_name'),
                'enabled': enabled,
                'enabled_as_of': enabled_as_of,
                'reason': reason,
            }
            results.append(data)

        except (ObjectDoesNotExist, InvalidKeyError):
            pass

        return results
Esempio n. 31
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
Esempio n. 32
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)
    def _get_course_duration_info(self, course_key):
        """
        Fetch course duration information from database
        """
        results = []

        try:
            key = CourseKey.from_string(course_key)
            course = CourseOverview.objects.values('display_name').get(id=key)
            duration_config = CourseDurationLimitConfig.current(course_key=key)
            gating_config = ContentTypeGatingConfig.current(course_key=key)
            partially_enabled = duration_config.enabled != gating_config.enabled

            if partially_enabled:
                if duration_config.enabled:
                    enabled = 'Course Duration Limits Only'
                    enabled_as_of = str(duration_config.enabled_as_of) if duration_config.enabled_as_of else 'N/A'
                    reason = 'Course duration limits are enabled for this course, but content type gating is disabled.'
                elif gating_config.enabled:
                    enabled = 'Content Type Gating Only'
                    enabled_as_of = str(gating_config.enabled_as_of) if gating_config.enabled_as_of else 'N/A'
                    reason = 'Content type gating is enabled for this course, but course duration limits are disabled.'
            else:
                enabled = duration_config.enabled or False
                enabled_as_of = str(duration_config.enabled_as_of) if duration_config.enabled_as_of else 'N/A'
                reason = duration_config.provenances['enabled']

            data = {
                'course_id': course_key,
                'course_name': course.get('display_name'),
                'enabled': enabled,
                'enabled_as_of': enabled_as_of,
                'reason': reason,
            }
            results.append(data)

        except (ObjectDoesNotExist, InvalidKeyError):
            pass

        return results
Esempio n. 34
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
Esempio n. 35
0
 def content_type_gating_enabled(self):
     return ContentTypeGatingConfig.enabled_for_enrollment(
         user=self.effective_user,
         course_key=self.course_key,
     )
Esempio n. 36
0
def get_visibility_partition_info(xblock, course=None):
    """
    Retrieve user partition information for the component visibility editor.

    This pre-processes partition information to simplify the template.

    Arguments:
        xblock (XBlock): The component being edited.

        course (XBlock): The course descriptor.  If provided, uses this to look up the user partitions
            instead of loading the course.  This is useful if we're calling this function multiple
            times for the same course want to minimize queries to the modulestore.

    Returns: dict

    """
    selectable_partitions = []
    # We wish to display enrollment partitions before cohort partitions.
    enrollment_user_partitions = get_user_partition_info(
        xblock, schemes=["enrollment_track"], course=course)

    # For enrollment partitions, we only show them if there is a selected group or
    # or if the number of groups > 1.
    for partition in enrollment_user_partitions:
        if len(partition["groups"]) > 1 or any(
                group["selected"] for group in partition["groups"]):
            selectable_partitions.append(partition)

    course_key = xblock.scope_ids.usage_id.course_key
    is_library = isinstance(course_key, LibraryLocator)
    if not is_library and ContentTypeGatingConfig.current(
            course_key=course_key).studio_override_enabled:
        selectable_partitions += get_user_partition_info(
            xblock, schemes=[CONTENT_TYPE_GATING_SCHEME], course=course)

    # Now add the cohort user partitions.
    selectable_partitions = selectable_partitions + get_user_partition_info(
        xblock, schemes=["cohort"], course=course)

    # Find the first partition with a selected group. That will be the one initially enabled in the dialog
    # (if the course has only been added in Studio, only one partition should have a selected group).
    selected_partition_index = -1

    # At the same time, build up all the selected groups as they are displayed in the dialog title.
    selected_groups_label = ''

    for index, partition in enumerate(selectable_partitions):
        for group in partition["groups"]:
            if group["selected"]:
                if len(selected_groups_label) == 0:
                    selected_groups_label = group['name']
                else:
                    # Translators: This is building up a list of groups. It is marked for translation because of the
                    # comma, which is used as a separator between each group.
                    selected_groups_label = _(
                        u'{previous_groups}, {current_group}').format(
                            previous_groups=selected_groups_label,
                            current_group=group['name'])
                if selected_partition_index == -1:
                    selected_partition_index = index

    return {
        "selectable_partitions": selectable_partitions,
        "selected_partition_index": selected_partition_index,
        "selected_groups_label": selected_groups_label,
    }
Esempio n. 37
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)
Esempio n. 38
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)
Esempio n. 39
0
    def test_caching_site(self):
        site_cfg = SiteConfigurationFactory()
        site_config = ContentTypeGatingConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1))
        site_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the site value is not retrieved from cache after save
        with self.assertNumQueries(1):
            self.assertTrue(ContentTypeGatingConfig.current(site=site_cfg.site).enabled)

        RequestCache.clear_all_namespaces()

        # Check that the site value can be retrieved from cache after read
        with self.assertNumQueries(0):
            self.assertTrue(ContentTypeGatingConfig.current(site=site_cfg.site).enabled)

        site_config.enabled = False
        site_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the site value in cache was deleted on save
        with self.assertNumQueries(1):
            self.assertFalse(ContentTypeGatingConfig.current(site=site_cfg.site).enabled)

        global_config = ContentTypeGatingConfig(enabled=True, enabled_as_of=datetime(2018, 1, 1))
        global_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the site value is not updated in cache by changing the global value
        with self.assertNumQueries(0):
            self.assertFalse(ContentTypeGatingConfig.current(site=site_cfg.site).enabled)
Esempio n. 40
0
    def test_caching_site(self):
        site_cfg = SiteConfigurationFactory()
        site_config = ContentTypeGatingConfig(site=site_cfg.site,
                                              enabled=True,
                                              enabled_as_of=datetime(
                                                  2018, 1, 1))
        site_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the site value is not retrieved from cache after save
        with self.assertNumQueries(1):
            assert ContentTypeGatingConfig.current(site=site_cfg.site).enabled

        RequestCache.clear_all_namespaces()

        # Check that the site value can be retrieved from cache after read
        with self.assertNumQueries(0):
            assert ContentTypeGatingConfig.current(site=site_cfg.site).enabled

        site_config.enabled = False
        site_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the site value in cache was deleted on save
        with self.assertNumQueries(1):
            assert not ContentTypeGatingConfig.current(
                site=site_cfg.site).enabled

        global_config = ContentTypeGatingConfig(enabled=True,
                                                enabled_as_of=datetime(
                                                    2018, 1, 1))
        global_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the site value is not updated in cache by changing the global value
        with self.assertNumQueries(0):
            assert not ContentTypeGatingConfig.current(
                site=site_cfg.site).enabled
Esempio n. 41
0
    def test_caching_org(self):
        course = CourseOverviewFactory.create(org='test-org')
        site_cfg = SiteConfigurationFactory.create(values={'course_org_filter': course.org})
        org_config = ContentTypeGatingConfig(org=course.org, enabled=True, enabled_as_of=date(2018, 1, 1))
        org_config.save()

        # Check that the org value is not retrieved from cache after save
        with self.assertNumQueries(2):
            self.assertTrue(ContentTypeGatingConfig.current(org=course.org).enabled)

        # Check that the org value can be retrieved from cache after read
        with self.assertNumQueries(0):
            self.assertTrue(ContentTypeGatingConfig.current(org=course.org).enabled)

        org_config.enabled = False
        org_config.save()

        # Check that the org value in cache was deleted on save
        with self.assertNumQueries(2):
            self.assertFalse(ContentTypeGatingConfig.current(org=course.org).enabled)

        global_config = ContentTypeGatingConfig(enabled=True, enabled_as_of=date(2018, 1, 1))
        global_config.save()

        # Check that the org value is not updated in cache by changing the global value
        with self.assertNumQueries(0):
            self.assertFalse(ContentTypeGatingConfig.current(org=course.org).enabled)

        site_config = ContentTypeGatingConfig(site=site_cfg.site, enabled=True, enabled_as_of=date(2018, 1, 1))
        site_config.save()

        # Check that the org value is not updated in cache by changing the site value
        with self.assertNumQueries(0):
            self.assertFalse(ContentTypeGatingConfig.current(org=course.org).enabled)
Esempio n. 42
0
def get_visibility_partition_info(xblock, course=None):
    """
    Retrieve user partition information for the component visibility editor.

    This pre-processes partition information to simplify the template.

    Arguments:
        xblock (XBlock): The component being edited.

        course (XBlock): The course descriptor.  If provided, uses this to look up the user partitions
            instead of loading the course.  This is useful if we're calling this function multiple
            times for the same course want to minimize queries to the modulestore.

    Returns: dict

    """
    selectable_partitions = []
    # We wish to display enrollment partitions before cohort partitions.
    enrollment_user_partitions = get_user_partition_info(xblock, schemes=["enrollment_track"], course=course)

    # For enrollment partitions, we only show them if there is a selected group or
    # or if the number of groups > 1.
    for partition in enrollment_user_partitions:
        if len(partition["groups"]) > 1 or any(group["selected"] for group in partition["groups"]):
            selectable_partitions.append(partition)

    course_key = xblock.scope_ids.usage_id.course_key
    is_library = isinstance(course_key, LibraryLocator)
    if not is_library and ContentTypeGatingConfig.current(course_key=course_key).studio_override_enabled:
        selectable_partitions += get_user_partition_info(xblock, schemes=[CONTENT_TYPE_GATING_SCHEME], course=course)

    # Now add the cohort user partitions.
    selectable_partitions = selectable_partitions + get_user_partition_info(xblock, schemes=["cohort"], course=course)

    # Find the first partition with a selected group. That will be the one initially enabled in the dialog
    # (if the course has only been added in Studio, only one partition should have a selected group).
    selected_partition_index = -1

    # At the same time, build up all the selected groups as they are displayed in the dialog title.
    selected_groups_label = ''

    for index, partition in enumerate(selectable_partitions):
        for group in partition["groups"]:
            if group["selected"]:
                if len(selected_groups_label) == 0:
                    selected_groups_label = group['name']
                else:
                    # Translators: This is building up a list of groups. It is marked for translation because of the
                    # comma, which is used as a separator between each group.
                    selected_groups_label = _(u'{previous_groups}, {current_group}').format(
                        previous_groups=selected_groups_label,
                        current_group=group['name']
                    )
                if selected_partition_index == -1:
                    selected_partition_index = index

    return {
        "selectable_partitions": selectable_partitions,
        "selected_partition_index": selected_partition_index,
        "selected_groups_label": selected_groups_label,
    }
Esempio n. 43
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')))
Esempio n. 44
0
 def enabled_for(cls, course):
     """This simple override provider is always enabled"""
     return ContentTypeGatingConfig.enabled_for_course(course_key=course.scope_ids.usage_id.course_key)
Esempio n. 45
0
    def test_caching_course(self):
        course = CourseOverviewFactory.create(org='test-org')
        site_cfg = SiteConfigurationFactory.create(
            site_values={'course_org_filter': course.org})
        course_config = ContentTypeGatingConfig(course=course,
                                                enabled=True,
                                                enabled_as_of=datetime(
                                                    2018, 1, 1))
        course_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the org value is not retrieved from cache after save
        with self.assertNumQueries(2):
            assert ContentTypeGatingConfig.current(
                course_key=course.id).enabled

        RequestCache.clear_all_namespaces()

        # Check that the org value can be retrieved from cache after read
        with self.assertNumQueries(0):
            assert ContentTypeGatingConfig.current(
                course_key=course.id).enabled

        course_config.enabled = False
        course_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the org value in cache was deleted on save
        with self.assertNumQueries(2):
            assert not ContentTypeGatingConfig.current(
                course_key=course.id).enabled

        global_config = ContentTypeGatingConfig(enabled=True,
                                                enabled_as_of=datetime(
                                                    2018, 1, 1))
        global_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the org value is not updated in cache by changing the global value
        with self.assertNumQueries(0):
            assert not ContentTypeGatingConfig.current(
                course_key=course.id).enabled

        site_config = ContentTypeGatingConfig(site=site_cfg.site,
                                              enabled=True,
                                              enabled_as_of=datetime(
                                                  2018, 1, 1))
        site_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the org value is not updated in cache by changing the site value
        with self.assertNumQueries(0):
            assert not ContentTypeGatingConfig.current(
                course_key=course.id).enabled

        org_config = ContentTypeGatingConfig(org=course.org,
                                             enabled=True,
                                             enabled_as_of=datetime(
                                                 2018, 1, 1))
        org_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the org value is not updated in cache by changing the site value
        with self.assertNumQueries(0):
            assert not ContentTypeGatingConfig.current(
                course_key=course.id).enabled
Esempio n. 46
0
 def enabled_for(cls, course):
     """Check our stackable config for this specific course"""
     return ContentTypeGatingConfig.enabled_for_course(
         course_key=course.scope_ids.usage_id.course_key)
Esempio n. 47
0
 def enabled_for(cls, course):
     """This simple override provider is always enabled"""
     return ContentTypeGatingConfig.enabled_for_course(
         course_key=course.scope_ids.usage_id.course_key)
Esempio n. 48
0
    def test_caching_course(self):
        course = CourseOverviewFactory.create(org='test-org')
        site_cfg = SiteConfigurationFactory.create(values={'course_org_filter': course.org})
        course_config = ContentTypeGatingConfig(course=course, enabled=True, enabled_as_of=datetime(2018, 1, 1))
        course_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the org value is not retrieved from cache after save
        with self.assertNumQueries(2):
            self.assertTrue(ContentTypeGatingConfig.current(course_key=course.id).enabled)

        RequestCache.clear_all_namespaces()

        # Check that the org value can be retrieved from cache after read
        with self.assertNumQueries(0):
            self.assertTrue(ContentTypeGatingConfig.current(course_key=course.id).enabled)

        course_config.enabled = False
        course_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the org value in cache was deleted on save
        with self.assertNumQueries(2):
            self.assertFalse(ContentTypeGatingConfig.current(course_key=course.id).enabled)

        global_config = ContentTypeGatingConfig(enabled=True, enabled_as_of=datetime(2018, 1, 1))
        global_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the org value is not updated in cache by changing the global value
        with self.assertNumQueries(0):
            self.assertFalse(ContentTypeGatingConfig.current(course_key=course.id).enabled)

        site_config = ContentTypeGatingConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1))
        site_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the org value is not updated in cache by changing the site value
        with self.assertNumQueries(0):
            self.assertFalse(ContentTypeGatingConfig.current(course_key=course.id).enabled)

        org_config = ContentTypeGatingConfig(org=course.org, enabled=True, enabled_as_of=datetime(2018, 1, 1))
        org_config.save()

        RequestCache.clear_all_namespaces()

        # Check that the org value is not updated in cache by changing the site value
        with self.assertNumQueries(0):
            self.assertFalse(ContentTypeGatingConfig.current(course_key=course.id).enabled)