예제 #1
0
    def get(self, block, name, default):
        if not CONTENT_TYPE_GATING_FLAG.is_enabled():
            return default

        if name != 'group_access':
            return default

        graded = getattr(block, 'graded', False)
        has_score = block.has_score
        weight_not_zero = getattr(block, 'weight', 0) != 0
        problem_eligible_for_content_gating = graded and has_score and weight_not_zero
        if not problem_eligible_for_content_gating:
            return default

        # Read the group_access from the fallback field-data service
        with disable_overrides():
            original_group_access = block.group_access

        if original_group_access is None:
            original_group_access = {}
        original_group_access.setdefault(
            CONTENT_GATING_PARTITION_ID,
            [settings.CONTENT_TYPE_GATE_GROUP_IDS['full_access']])

        return original_group_access
예제 #2
0
def create_content_gating_partition(course):
    """
    Create and return the Content Gating user partition.
    """

    if not (CONTENT_TYPE_GATING_FLAG.is_enabled() or CONTENT_TYPE_GATING_STUDIO_UI_FLAG.is_enabled()):
        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
예제 #3
0
    def get_queryset(self):
        api_version = self.kwargs.get('api_version')
        enrollments = self.queryset.filter(
            user__username=self.kwargs['username'],
            is_active=True).order_by('created').reverse()
        org = self.request.query_params.get('org', None)

        if api_version == API_V05:
            # for v0.5 don't return expired courses
            return [
                enrollment for enrollment in enrollments
                if enrollment.course_overview
                and self.is_org(org, enrollment.course_overview.org)
                and is_mobile_available_for_user(self.request.user,
                                                 enrollment.course_overview)
                and not self.hide_course_for_enrollment_fee_experiment(
                    self.request.user, enrollment) and (
                        not CONTENT_TYPE_GATING_FLAG.is_enabled()
                        or check_course_expired(self.request.user, enrollment.
                                                course) == ACCESS_GRANTED)
            ]
        else:
            # return all courses, with associated expiration
            return [
                enrollment for enrollment in enrollments
                if enrollment.course_overview
                and self.is_org(org, enrollment.course_overview.org)
                and is_mobile_available_for_user(self.request.user,
                                                 enrollment.course_overview)
            ]
예제 #4
0
    def enabled_for_course(cls, course_key, target_datetime=None):
        """
        Return whether Course Duration Limits are enabled for this course as of a particular date.

        Course Duration Limits are enabled for a course on a date if they are enabled either specifically,
        or via a containing context, such as the org, site, or globally, and if the configuration
        is specified to be ``enabled_as_of`` before ``target_datetime``.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            course_key: The CourseKey of the course being queried.
            target_datetime: The datetime to checked enablement as of. Defaults to the current date and time.
        """

        if FEATURE_BASED_ENROLLMENT_GLOBAL_KILL_FLAG.is_enabled():
            return False

        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if target_datetime is None:
            target_datetime = timezone.now()

        current_config = cls.current(course_key=course_key)
        return current_config.enabled_as_of_datetime(target_datetime=target_datetime)
예제 #5
0
def get_course_block_access_transformers(user):
    """
    Default list of transformers for manipulating course block structures
    based on the user's access to the course blocks.

    Arguments:
        user (django.contrib.auth.models.User) - User object for
            which the block structure is to be transformed.

    """
    if CONTENT_TYPE_GATING_FLAG.is_enabled():
        # [REV/Revisit] remove this duplicated code when flag is removed
        course_block_access_transformers = [
            library_content.ContentLibraryTransformer(),
            start_date.StartDateTransformer(),
            ContentTypeGateTransformer(),
            user_partitions.UserPartitionTransformer(),
            visibility.VisibilityTransformer(),
        ]
    else:
        course_block_access_transformers = [
            library_content.ContentLibraryTransformer(),
            start_date.StartDateTransformer(),
            user_partitions.UserPartitionTransformer(),
            visibility.VisibilityTransformer(),
        ]

    if has_individual_student_override_provider():
        course_block_access_transformers += [
            load_override_data.OverrideDataTransformer(user)
        ]

    return course_block_access_transformers
예제 #6
0
    def can_load():
        """
        Can this user load this course?

        NOTE: this is not checking whether user is actually enrolled in the course.
        """
        # N.B. I'd love a better way to handle this pattern, without breaking the
        # shortcircuiting logic. Maybe AccessResponse needs to grow a
        # fluent interface?
        #
        # return (
        #     _visible_to_nonstaff_users(courselike).and(
        #         check_course_open_for_learner, user, courselike
        #     ).and(
        #         _can_view_courseware_with_prerequisites, user, courselike
        #     )
        # ).or(
        #     _has_staff_access_to_descriptor, user, courselike, courselike.id
        # )
        visible_to_nonstaff = _visible_to_nonstaff_users(courselike)
        if not visible_to_nonstaff:
            staff_access = _has_staff_access_to_descriptor(
                user, courselike, courselike.id)
            if staff_access:
                return staff_access
            else:
                return visible_to_nonstaff

        open_for_learner = check_course_open_for_learner(user, courselike)
        if not open_for_learner:
            staff_access = _has_staff_access_to_descriptor(
                user, courselike, courselike.id)
            if staff_access:
                return staff_access
            else:
                return open_for_learner

        view_with_prereqs = _can_view_courseware_with_prerequisites(
            user, courselike)
        if not view_with_prereqs:
            staff_access = _has_staff_access_to_descriptor(
                user, courselike, courselike.id)
            if staff_access:
                return staff_access
            else:
                return view_with_prereqs

        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            has_not_expired = check_course_expired(user, courselike)
            if not has_not_expired:
                staff_access = _has_staff_access_to_descriptor(
                    user, courselike, courselike.id)
                if staff_access:
                    return staff_access
                else:
                    return has_not_expired

        return ACCESS_GRANTED
예제 #7
0
    def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None):
        """
        Return whether Content Type Gating is enabled for this enrollment.

        Content Type Gating is enabled for an enrollment if it is enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """
        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if enrollment is not None and (user is not None or course_key is not None):
            raise ValueError('Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError('Both user and course_key must be specified if no enrollment is provided')

        if enrollment is None and user is None and course_key is None:
            raise ValueError('At least one of enrollment or user and course_key must be specified')

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        if enrollment is None:
            return cls.enabled_for_course(course_key=course_key, target_datetime=timezone.now())
        else:
            # TODO: clean up as part of REV-100
            experiment_data_holdback_key = EXPERIMENT_DATA_HOLDBACK_KEY.format(user)
            is_in_holdback = False
            no_masquerade = get_course_masquerade(user, course_key) is None
            student_masquerade = is_masquerading_as_specific_student(user, course_key)
            if user and (no_masquerade or student_masquerade):
                try:
                    holdback_value = ExperimentData.objects.get(
                        user=user,
                        experiment_id=EXPERIMENT_ID,
                        key=experiment_data_holdback_key,
                    ).value
                    is_in_holdback = holdback_value == 'True'
                except ExperimentData.DoesNotExist:
                    pass
            if is_in_holdback:
                return False
            current_config = cls.current(course_key=enrollment.course_id)
            return current_config.enabled_as_of_datetime(target_datetime=enrollment.created)
예제 #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.
        """

        # 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.
        if get_course_masquerade(user, course_key) and not is_masquerading_as_specific_student(user, course_key):
            return get_masquerading_user_group(course_key, user, user_partition)

        # For now, treat everyone as a Full-access user, until we have the rest of the
        # feature gating logic in place.

        if not CONTENT_TYPE_GATING_FLAG.is_enabled():
            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

        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
예제 #9
0
    def enabled_as_of_datetime(self, target_datetime):
        """
        Return whether this Content Type Gating configuration context is enabled as of a date and time.

        Arguments:
            target_datetime (:class:`datetime.datetime`): The datetime that ``enabled_as_of`` must be equal to or before
        """
        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        # Explicitly cast this to bool, so that when self.enabled is None the method doesn't return None
        return bool(self.enabled and self.enabled_as_of <= target_datetime)
예제 #10
0
    def enabled_as_of_date(self, target_date):
        """
        Return whether this Content Type Gating configuration context is enabled as of a date.

        Arguments:
            target_date (:class:`datetime.date`): The date that ``enabled_as_of`` must be equal to or before
        """
        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        # Explicitly cast this to bool, so that when self.enabled is None the method doesn't return None
        return bool(self.enabled and self.enabled_as_of <= target_date)
예제 #11
0
    def enabled_for_enrollment(cls,
                               enrollment=None,
                               user=None,
                               course_key=None):
        """
        Return whether Content Type Gating is enabled for this enrollment.

        Content Type Gating is enabled for an enrollment if it is enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """
        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if enrollment is not None and (user is not None
                                       or course_key is not None):
            raise ValueError(
                'Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError(
                'Both user and course_key must be specified if no enrollment is provided'
            )

        if enrollment is None and user is None and course_key is None:
            raise ValueError(
                'At least one of enrollment or user and course_key must be specified'
            )

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        if enrollment is None:
            return cls.enabled_for_course(course_key=course_key,
                                          target_date=datetime.utcnow().date())
        else:
            current_config = cls.current(course_key=enrollment.course_id)
            return current_config.enabled_as_of_date(
                target_date=enrollment.created.date())
예제 #12
0
    def enabled_as_of_datetime(self, target_datetime):
        """
        Return whether this Course Duration Limit configuration context is enabled as of a date and time.

        Arguments:
            target_datetime (:class:`datetime.datetime`): The datetime that ``enabled_as_of`` must be equal to or before
        """

        if FEATURE_BASED_ENROLLMENT_GLOBAL_KILL_FLAG.is_enabled():
            return True

        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        # Explicitly cast this to bool, so that when self.enabled is None the method doesn't return None
        return bool(self.enabled and self.enabled_as_of <= target_datetime)
예제 #13
0
def register_course_expired_message(request, course):
    """
    Add a banner notifying the user of the user course expiration date if it exists.
    """
    if CONTENT_TYPE_GATING_FLAG.is_enabled():
        expiration_date = get_user_course_expiration_date(request.user, course)
        if expiration_date:
            upgrade_message = _(
                'Your access to this course expires on {expiration_date}. \
                    <a href="{upgrade_link}">Upgrade now</a> for unlimited access.'
            )
            PageLevelMessages.register_info_message(
                request,
                HTML(upgrade_message).format(
                    expiration_date=expiration_date.strftime('%b %-d'),
                    upgrade_link=verified_upgrade_deadline_link(
                        user=request.user, course=course)))
예제 #14
0
    def get(self, block, name, default):
        if not CONTENT_TYPE_GATING_FLAG.is_enabled():
            return default

        if name != 'group_access':
            return default

        if not (getattr(block, 'graded', False) and block.has_score):
            return default

        # Read the group_access from the fallback field-data service
        with disable_overrides():
            original_group_access = block.group_access

        if original_group_access is None:
            original_group_access = {}
        original_group_access.setdefault(
            CONTENT_GATING_PARTITION_ID,
            [settings.CONTENT_TYPE_GATE_GROUP_IDS['full_access']])

        return original_group_access
예제 #15
0
    def get(self, block, name, default):
        if not CONTENT_TYPE_GATING_FLAG.is_enabled():
            return default

        if name != 'group_access':
            return default

        graded = getattr(block, 'graded', False)
        has_score = block.has_score
        weight_not_zero = getattr(block, 'weight', 0) != 0
        problem_eligible_for_content_gating = graded and has_score and weight_not_zero
        if not problem_eligible_for_content_gating:
            return default

        # We want to fetch the value set by course authors since it should take precedence.
        # We cannot simply call "block.group_access" to fetch that value even if we disable
        # field overrides since it will set the group access field to "dirty" with
        # the value read from the course content. Since most content does not have any
        # value for this field it will usually be the default empty dict. This field
        # override changes the value, however, resulting in the LMS thinking that the
        # field data needs to be written back out to the store. This doesn't work,
        # however, since this is a read-only setting in the LMS context. After this
        # call to get() returns, the _dirty_fields dict will be set correctly to contain
        # the value from this field override. This prevents the system from attempting
        # to save the overridden value when it thinks it has changed when it hasn't.
        original_group_access = None
        if self.fallback_field_data.has(block, 'group_access'):
            raw_value = self.fallback_field_data.get(block, 'group_access')
            group_access_field = block.fields.get('group_access')
            if group_access_field is not None:
                original_group_access = group_access_field.from_json(raw_value)

        if original_group_access is None:
            original_group_access = {}
        original_group_access.setdefault(
            CONTENT_GATING_PARTITION_ID,
            [settings.CONTENT_TYPE_GATE_GROUP_IDS['full_access']]
        )

        return original_group_access
예제 #16
0
    def enabled_for_course(cls, course_key, target_datetime=None):
        """
        Return whether Content Type Gating is enabled for this course as of a particular date.

        Content Type Gating is enabled for a course on a date if it is enabled either specifically,
        or via a containing context, such as the org, site, or globally, and if the configuration
        is specified to be ``enabled_as_of`` before ``target_datetime``.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            course_key: The CourseKey of the course being queried.
            target_datetime: The datetime to checked enablement as of. Defaults to the current date and time.
        """
        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if target_datetime is None:
            target_datetime = timezone.now()

        current_config = cls.current(course_key=course_key)
        return current_config.enabled_as_of_datetime(target_datetime=target_datetime)
예제 #17
0
    def enabled_for_course(cls, course_key, target_date=None):
        """
        Return whether Content Type Gating is enabled for this course as of a particular date.

        Content Type Gating is enabled for a course on a date if it is enabled either specifically,
        or via a containing context, such as the org, site, or globally, and if the configuration
        is specified to be ``enabled_as_of`` before ``target_date``.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            course_key: The CourseKey of the course being queried.
            target_date: The date to checked enablement as of. Defaults to the current date.
        """
        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if target_date is None:
            target_date = datetime.utcnow().date()

        current_config = cls.current(course_key=course_key)
        return current_config.enabled_as_of_date(target_date=target_date)
예제 #18
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)

    flag_enabled = CONTENT_TYPE_GATING_FLAG.is_enabled()
    course_key = xblock.scope_ids.usage_id.course_key
    is_library = isinstance(course_key, LibraryLocator)
    if not is_library and (
        flag_enabled or ContentTypeGatingConfig.current(course_key=course_key).studio_override_enabled
    ):
        selectable_partitions += get_user_partition_info(xblock, schemes=["content_type_gate"], 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 = _('{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,
    }
예제 #19
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)

        bundle_data = {}
        bundles_on_track_selection = WaffleFlag(
            WaffleFlagNamespace(name=u'experiments'),
            u'bundles_on_track_selection')
        if bundles_on_track_selection.is_enabled():
            # enrollment in the course on this page
            current_enrollment = list(
                CourseEnrollment.enrollments_for_user(
                    request.user).filter(course_id=course_key))
            if current_enrollment:
                meter = ProgramProgressMeter(request.site,
                                             request.user,
                                             enrollments=current_enrollment)
                meter_inverted_programs = meter.invert_programs()
                if len(meter_inverted_programs) > 0:
                    # program for the course on this page
                    programs_for_course = meter_inverted_programs.get(
                        course_id)
                    if programs_for_course:
                        program_for_course = programs_for_course[0]
                        program_uuid = program_for_course.get('uuid')
                        if program_for_course:
                            # program data with bundle info
                            program_data = ProgramDataExtender(
                                program_for_course,
                                request.user,
                                mobile_only=False).extend()
                            skus = program_data.get('skus')
                            ecommerce_service = EcommerceService()
                            program_bundle_url = ecommerce_service.get_checkout_page_url(
                                *skus, program_uuid=program_uuid)
                            bundle_data = {
                                'program_marketing_site_url':
                                program_data.get('marketing_url'),
                                'program_bundle_url':
                                program_bundle_url,
                                'discount_data':
                                program_data.get('discount_data'),
                                'program_type':
                                program_data.get('type'),
                                'program_title':
                                program_data.get('title'),
                                'program_price':
                                program_data.get('full_program_price'),
                            }

        context = {
            "bundle_data":
            bundle_data,
            "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":
            CONTENT_TYPE_GATING_FLAG.is_enabled(),
        }
        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)
예제 #20
0
    def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None):
        """
        Return whether Course Duration Limits are enabled for this enrollment.

        Course Duration Limits are enabled for an enrollment if they are enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """
        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if enrollment is not None and (user is not None or course_key is not None):
            raise ValueError('Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError('Both user and course_key must be specified if no enrollment is provided')

        if enrollment is None and user is None and course_key is None:
            raise ValueError('At least one of enrollment or user and course_key must be specified')

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        # if the user is has a role of staff, instructor or beta tester their access should not expire
        if user is None and enrollment is not None:
            user = enrollment.user

        if user:
            course_masquerade = get_course_masquerade(user, course_key)
            if course_masquerade:
                verified_mode_id = settings.COURSE_ENROLLMENT_MODES.get(CourseMode.VERIFIED, {}).get('id')
                is_verified = (course_masquerade.user_partition_id == ENROLLMENT_TRACK_PARTITION_ID
                               and course_masquerade.group_id == verified_mode_id)
                is_full_access = (course_masquerade.user_partition_id == CONTENT_GATING_PARTITION_ID
                                  and course_masquerade.group_id == CONTENT_TYPE_GATE_GROUP_IDS['full_access'])
                is_staff = get_masquerade_role(user, course_key) == 'staff'
                if is_verified or is_full_access or is_staff:
                    return False
            else:
                staff_role = CourseStaffRole(course_key).has_user(user)
                instructor_role = CourseInstructorRole(course_key).has_user(user)
                beta_tester_role = CourseBetaTesterRole(course_key).has_user(user)

                if staff_role or instructor_role or beta_tester_role:
                    return False

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        if enrollment is None:
            return cls.enabled_for_course(course_key=course_key, target_datetime=timezone.now())
        else:
            # TODO: clean up as part of REV-100
            experiment_data_holdback_key = EXPERIMENT_DATA_HOLDBACK_KEY.format(user)
            is_in_holdback = False
            no_masquerade = get_course_masquerade(user, course_key) is None
            student_masquerade = is_masquerading_as_specific_student(user, course_key)
            if user and (no_masquerade or student_masquerade):
                try:
                    holdback_value = ExperimentData.objects.get(
                        user=user,
                        experiment_id=EXPERIMENT_ID,
                        key=experiment_data_holdback_key,
                    ).value
                    is_in_holdback = holdback_value == 'True'
                except ExperimentData.DoesNotExist:
                    pass
            if is_in_holdback:
                return False
            current_config = cls.current(course_key=enrollment.course_id)
            return current_config.enabled_as_of_datetime(target_datetime=enrollment.created)
예제 #21
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)

    flag_enabled = CONTENT_TYPE_GATING_FLAG.is_enabled()
    course_key = xblock.scope_ids.usage_id.course_key
    is_library = isinstance(course_key, LibraryLocator)
    if not is_library and (flag_enabled or ContentTypeGatingConfig.current(
            course_key=course_key).studio_override_enabled):
        selectable_partitions += get_user_partition_info(
            xblock, schemes=["content_type_gate"], 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 = _(
                        '{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,
    }
예제 #22
0
    def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None):
        """
        Return whether Course Duration Limits are enabled for this enrollment.

        Course Duration Limits are enabled for an enrollment if they are enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """

        if FEATURE_BASED_ENROLLMENT_GLOBAL_KILL_FLAG.is_enabled():
            return False

        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if enrollment is not None and (user is not None or course_key is not None):
            raise ValueError('Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError('Both user and course_key must be specified if no enrollment is provided')

        if enrollment is None and user is None and course_key is None:
            raise ValueError('At least one of enrollment or user and course_key must be specified')

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        if user is None and enrollment is not None:
            user = enrollment.user

        if user and user.id:
            course_masquerade = get_course_masquerade(user, course_key)
            if course_masquerade:
                if cls.has_full_access_role_in_masquerade(user, course_key, course_masquerade):
                    return False
            elif has_staff_roles(user, course_key):
                return False

        is_masquerading = get_course_masquerade(user, course_key)
        no_masquerade = is_masquerading is None
        student_masquerade = is_masquerading_as_specific_student(user, course_key)

        # check if user is in holdback
        if (no_masquerade or student_masquerade) and is_in_holdback(user):
            return False

        not_student_masquerade = is_masquerading and not student_masquerade

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        # When masquerading as a user group rather than a specific learner,
        # course duration limits will be on if they are on for the course.
        # When masquerading as a specific learner, course duration limits
        # will be on if they are currently on for the learner.
        if enrollment is None or not_student_masquerade:
            return cls.enabled_for_course(course_key=course_key, target_datetime=timezone.now())
        else:
            current_config = cls.current(course_key=enrollment.course_id)
            return current_config.enabled_as_of_datetime(target_datetime=enrollment.created)
예제 #23
0
    def enabled_for_enrollment(cls,
                               enrollment=None,
                               user=None,
                               course_key=None):
        """
        Return whether Content Type Gating is enabled for this enrollment.

        Content Type Gating is enabled for an enrollment if it is enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """
        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if enrollment is not None and (user is not None
                                       or course_key is not None):
            raise ValueError(
                'Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError(
                'Both user and course_key must be specified if no enrollment is provided'
            )

        if enrollment is None and user is None and course_key is None:
            raise ValueError(
                'At least one of enrollment or user and course_key must be specified'
            )

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        if user is None and enrollment is not None:
            user = enrollment.user

        no_masquerade = get_course_masquerade(user, course_key) is None
        student_masquerade = is_masquerading_as_specific_student(
            user, course_key)
        # We can only use the user variable for the code below when the request is not in a masquerade state
        # or is masquerading as a specific user.
        # When a request is not in a masquerade state the user variable represents the correct user.
        # When a request is in a masquerade state and not masquerading as a specific user,
        # then then user variable will be the incorrect (original) user, not the masquerade user.
        # If a request is masquerading as a specific user, the user variable will represent the correct user.
        user_variable_represents_correct_user = (no_masquerade
                                                 or student_masquerade)
        if user and user.id:
            # TODO: Move masquerade checks to enabled_for_enrollment from content_type_gating/partitions.py
            # TODO: Consolidate masquerade checks into shared function like has_staff_roles below
            if user_variable_represents_correct_user and has_staff_roles(
                    user, course_key):
                return False

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        if enrollment is None:
            return cls.enabled_for_course(course_key=course_key,
                                          target_datetime=timezone.now())
        else:
            # TODO: clean up as part of REV-100
            experiment_data_holdback_key = EXPERIMENT_DATA_HOLDBACK_KEY.format(
                user)
            is_in_holdback = False
            if user and (user_variable_represents_correct_user):
                try:
                    holdback_value = ExperimentData.objects.get(
                        user=user,
                        experiment_id=EXPERIMENT_ID,
                        key=experiment_data_holdback_key,
                    ).value
                    is_in_holdback = holdback_value == 'True'
                except ExperimentData.DoesNotExist:
                    pass
            if is_in_holdback:
                return False
            current_config = cls.current(course_key=enrollment.course_id)
            return current_config.enabled_as_of_datetime(
                target_datetime=enrollment.created)
예제 #24
0
    def enabled_for_enrollment(cls,
                               enrollment=None,
                               user=None,
                               course_key=None):
        """
        Return whether Course Duration Limits are enabled for this enrollment.

        Course Duration Limits are enabled for an enrollment if they are enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """
        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if enrollment is not None and (user is not None
                                       or course_key is not None):
            raise ValueError(
                'Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError(
                'Both user and course_key must be specified if no enrollment is provided'
            )

        if enrollment is None and user is None and course_key is None:
            raise ValueError(
                'At least one of enrollment or user and course_key must be specified'
            )

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        # if the user is has a role of staff, instructor or beta tester their access should not expire
        if user is None and enrollment is not None:
            user = enrollment.user

        if user:
            course_masquerade = get_course_masquerade(user, course_key)
            if course_masquerade:
                verified_mode_id = settings.COURSE_ENROLLMENT_MODES.get(
                    CourseMode.VERIFIED, {}).get('id')
                is_verified = (course_masquerade.user_partition_id
                               == ENROLLMENT_TRACK_PARTITION_ID and
                               course_masquerade.group_id == verified_mode_id)
                is_full_access = (
                    course_masquerade.user_partition_id
                    == CONTENT_GATING_PARTITION_ID
                    and course_masquerade.group_id
                    == CONTENT_TYPE_GATE_GROUP_IDS['full_access'])
                is_staff = get_masquerade_role(user, course_key) == 'staff'
                if is_verified or is_full_access or is_staff:
                    return False
            else:
                staff_role = CourseStaffRole(course_key).has_user(user)
                instructor_role = CourseInstructorRole(course_key).has_user(
                    user)
                beta_tester_role = CourseBetaTesterRole(course_key).has_user(
                    user)

                if staff_role or instructor_role or beta_tester_role:
                    return False

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        if enrollment is None:
            return cls.enabled_for_course(course_key=course_key,
                                          target_datetime=timezone.now())
        else:
            # TODO: clean up as part of REV-100
            experiment_data_holdback_key = EXPERIMENT_DATA_HOLDBACK_KEY.format(
                user)
            is_in_holdback = False
            no_masquerade = get_course_masquerade(user, course_key) is None
            student_masquerade = is_masquerading_as_specific_student(
                user, course_key)
            if user and (no_masquerade or student_masquerade):
                try:
                    holdback_value = ExperimentData.objects.get(
                        user=user,
                        experiment_id=EXPERIMENT_ID,
                        key=experiment_data_holdback_key,
                    ).value
                    is_in_holdback = holdback_value == 'True'
                except ExperimentData.DoesNotExist:
                    pass
            if is_in_holdback:
                return False
            current_config = cls.current(course_key=enrollment.course_id)
            return current_config.enabled_as_of_datetime(
                target_datetime=enrollment.created)
예제 #25
0
    def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None, user_partition=None):
        """
        Return whether Content Type Gating is enabled for this enrollment.

        Content Type Gating is enabled for an enrollment if it is enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """
        if FEATURE_BASED_ENROLLMENT_GLOBAL_KILL_FLAG.is_enabled():
            return False

        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if enrollment is not None and (user is not None or course_key is not None):
            raise ValueError('Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError('Both user and course_key must be specified if no enrollment is provided')

        if enrollment is None and user is None and course_key is None:
            raise ValueError('At least one of enrollment or user and course_key must be specified')

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        if user is None and enrollment is not None:
            user = enrollment.user

        course_masquerade = get_course_masquerade(user, course_key)
        no_masquerade = course_masquerade is None
        student_masquerade = is_masquerading_as_specific_student(user, course_key)
        user_variable_represents_correct_user = (no_masquerade or student_masquerade)

        if course_masquerade:
            if cls.has_full_access_role_in_masquerade(user, course_key, course_masquerade, student_masquerade,
                                                      user_partition):
                return False
        # When a request is not in a masquerade state the user variable represents the correct user.
        elif user and user.id and has_staff_roles(user, course_key):
            return False

        # check if user is in holdback
        if user_variable_represents_correct_user and is_in_holdback(user):
            return False

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        # Also, ignore enrollment creation date if the user is masquerading.
        if enrollment is None or course_masquerade:
            return cls.enabled_for_course(course_key=course_key, target_datetime=timezone.now())
        else:
            current_config = cls.current(course_key=enrollment.course_id)
            return current_config.enabled_as_of_datetime(target_datetime=enrollment.created)