예제 #1
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,
            )
        )
예제 #2
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,
            ))
예제 #3
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
예제 #4
0
    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 {}
예제 #5
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
    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 {}
예제 #7
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
예제 #8
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)
예제 #9
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)
예제 #10
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": ContentTypeGatingConfig.enabled_for_course(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)
예제 #11
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)
예제 #12
0
 def enabled_for(cls, course):  # pylint: disable=arguments-differ
     """Check our stackable config for this specific course"""
     return ContentTypeGatingConfig.enabled_for_course(
         course_key=course.scope_ids.usage_id.course_key)