Пример #1
0
    def test_modes_for_course_multiple(self):
        """
        Finding the modes when there's multiple modes
        """
        mode1 = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd', None,
                     None, None, None)
        mode2 = Mode(u'verified', u'Verified Certificate', 10, '', 'usd', None,
                     None, None, None)
        set_modes = [mode1, mode2]
        for mode in set_modes:
            self.create_mode(mode.slug, mode.name, mode.min_price,
                             mode.suggested_prices)

        modes = CourseMode.modes_for_course(self.course_key)
        self.assertEqual(modes, set_modes)
        self.assertEqual(mode1,
                         CourseMode.mode_for_course(self.course_key, u'honor'))
        self.assertEqual(
            mode2, CourseMode.mode_for_course(self.course_key, u'verified'))
        self.assertIsNone(CourseMode.mode_for_course(self.course_key, 'DNE'))
Пример #2
0
    def test_verified_mode_creation(self, mode_slug, mode_display_name,
                                    min_price, suggested_prices, currency):
        parameters = {}
        parameters['mode_slug'] = mode_slug
        parameters['mode_display_name'] = mode_display_name
        parameters['min_price'] = min_price
        parameters['suggested_prices'] = suggested_prices
        parameters['currency'] = currency

        url = reverse('create_mode', args=[str(self.course.id)])
        response = self.client.get(url, parameters)

        assert response.status_code == 200

        expected_mode = [
            Mode(mode_slug, mode_display_name, min_price, suggested_prices,
                 currency, None, None, None, None)
        ]
        course_mode = CourseMode.modes_for_course(self.course.id)

        assert course_mode == expected_mode
Пример #3
0
def _enrollment_mode_display(enrollment_mode, course_id):
    """Checking enrollment mode and status and returns the display mode
     Args:
        enrollment_mode (str): enrollment mode.
        verification_status (str) : verification status of student

    Returns:
        display_mode (str) : display mode for certs
    """
    course_mode_slugs = [mode.slug for mode in CourseMode.modes_for_course(course_id)]

    if enrollment_mode == CourseMode.VERIFIED:
        display_mode = DISPLAY_VERIFIED
        if DISPLAY_HONOR in course_mode_slugs:
            display_mode = DISPLAY_HONOR
    elif enrollment_mode in [CourseMode.PROFESSIONAL, CourseMode.NO_ID_PROFESSIONAL_MODE]:
        display_mode = DISPLAY_PROFESSIONAL
    else:
        display_mode = enrollment_mode

    return display_mode
Пример #4
0
    def test_no_certificate_available_date_for_audit_course(self):
        """
        Tests that Certificate Available Date is not visible in the course "Important Course Dates" section
        if the course only has audit mode.
        """
        course = create_course_run()
        audit_user = create_user()

        # Enroll learner in the audit mode and verify the course only has 1 mode (audit)
        CourseEnrollmentFactory(course_id=course.id, user=audit_user, mode=CourseMode.AUDIT)
        CourseMode.objects.get(course_id=course.id, mode_slug=CourseMode.VERIFIED).delete()
        all_course_modes = CourseMode.modes_for_course(course.id)
        assert len(all_course_modes) == 1
        assert all_course_modes[0].slug == CourseMode.AUDIT

        course.certificate_available_date = datetime.now(utc) + timedelta(days=7)
        course.save()

        # Verify Certificate Available Date is not enabled for learner.
        block = CertificateAvailableDate(course, audit_user)
        assert not block.is_allowed
        assert block.date is not None
Пример #5
0
def _default_course_mode(course_id):
    """Return the default enrollment for a course.

    Special case the default enrollment to return if nothing else is found.

    Arguments:
        course_id (str): The course to check against for available course modes.

    Returns:
        str
    """
    course_modes = CourseMode.modes_for_course(CourseKey.from_string(course_id))
    available_modes = [m.slug for m in course_modes]

    if CourseMode.DEFAULT_MODE_SLUG in available_modes:
        return CourseMode.DEFAULT_MODE_SLUG
    elif 'audit' in available_modes:
        return 'audit'
    elif 'honor' in available_modes:
        return 'honor'

    return CourseMode.DEFAULT_MODE_SLUG
Пример #6
0
    def test_multiple_mode_creation(self):
        # Create an honor mode
        base_url = reverse('create_mode', args=[str(self.course.id)])
        self.client.get(base_url)

        # Excluding the currency parameter implicitly tests the mode creation endpoint's ability to
        # use default values when parameters are partially missing.
        parameters = {}
        parameters['mode_slug'] = 'verified'
        parameters['mode_display_name'] = 'Verified Certificate'
        parameters['min_price'] = 10
        parameters['suggested_prices'] = '10,20'

        # Create a verified mode
        url = reverse('create_mode', args=[str(self.course.id)])
        self.client.get(url, parameters)

        honor_mode = Mode('honor', 'Honor Code Certificate', 0, '', 'usd', None, None, None, None)
        verified_mode = Mode('verified', 'Verified Certificate', 10, '10,20', 'usd', None, None, None, None)
        expected_modes = [honor_mode, verified_mode]
        course_modes = CourseMode.modes_for_course(self.course.id)

        assert course_modes == expected_modes
Пример #7
0
def certificate_status(generated_certificate):
    """
    This returns a dictionary with a key for status, and other information.

    If the status is "downloadable", the dictionary also contains
    "download_url".

    If the student has been graded, the dictionary also contains their
    grade for the course with the key "grade".
    """
    # Import here instead of top of file since this module gets imported before
    # the course_modes app is loaded, resulting in a Django deprecation warning.
    from common.djangoapps.course_modes.models import CourseMode  # pylint: disable=redefined-outer-name, reimported

    if generated_certificate:
        cert_status = {
            'status': generated_certificate.status,
            'mode': generated_certificate.mode,
            'uuid': generated_certificate.verify_uuid,
        }
        if generated_certificate.grade:
            cert_status['grade'] = generated_certificate.grade

        if generated_certificate.mode == 'audit':
            course_mode_slugs = [mode.slug for mode in CourseMode.modes_for_course(generated_certificate.course_id)]
            # Short term fix to make sure old audit users with certs still see their certs
            # only do this if there if no honor mode
            if 'honor' not in course_mode_slugs:
                cert_status['status'] = CertificateStatuses.auditing
                return cert_status

        if generated_certificate.status == CertificateStatuses.downloadable:
            cert_status['download_url'] = generated_certificate.download_url

        return cert_status
    else:
        return {'status': CertificateStatuses.unavailable, 'mode': GeneratedCertificate.MODES.honor, 'uuid': None}
Пример #8
0
    def get_group_for_user(cls, course_key, user, user_partition, **kwargs):  # pylint: disable=unused-argument
        """
        Returns the Group from the specified user partition to which the user
        is assigned, via enrollment mode. If a user is in a Credit mode, the Verified or
        Professional mode for the course is returned instead.

        If a course is using the Verified Track Cohorting pilot feature, this method
        returns None regardless of the user's enrollment mode.
        """
        if is_course_using_cohort_instead(course_key):
            return None

        # 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)

        mode_slug, is_active = CourseEnrollment.enrollment_mode_for_user(user, course_key)
        if mode_slug and is_active:
            course_mode = CourseMode.mode_for_course(
                course_key,
                mode_slug,
                modes=CourseMode.modes_for_course(course_key, include_expired=True, only_selectable=False),
            )
            if course_mode and CourseMode.is_credit_mode(course_mode):
                # We want the verified track even if the upgrade deadline has passed, since we
                # are determining what content to show the user, not whether the user can enroll
                # in the verified track.
                course_mode = CourseMode.verified_mode_for_course(course_key, include_expired=True)
            if not course_mode:
                course_mode = CourseMode.DEFAULT_MODE
            return Group(ENROLLMENT_GROUP_IDS[course_mode.slug]["id"], six.text_type(course_mode.name))
        else:
            return None
Пример #9
0
    def create_example_set(cls, course_key):
        """Create a set of example certificates for a course.

        Arguments:
            course_key (CourseKey)

        Returns:
            ExampleCertificateSet

        """
        # Import here instead of top of file since this module gets imported before
        # the course_modes app is loaded, resulting in a Django deprecation warning.
        from common.djangoapps.course_modes.models import CourseMode  # pylint: disable=redefined-outer-name, reimported
        cert_set = cls.objects.create(course_key=course_key)

        ExampleCertificate.objects.bulk_create([
            ExampleCertificate(example_cert_set=cert_set,
                               description=mode.slug,
                               template=cls._template_for_mode(
                                   mode.slug, course_key))
            for mode in CourseMode.modes_for_course(course_key)
        ])

        return cert_set
Пример #10
0
 def has_certificate_modes(self):
     return any(
         mode.slug for mode in CourseMode.modes_for_course(
             course_id=self.course.id, include_expired=True
         ) if mode.slug != CourseMode.AUDIT
     )
Пример #11
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)
Пример #12
0
def certificate_status(generated_certificate):
    """
    This returns a dictionary with a key for status, and other information.
    The status is one of the following:

    unavailable  - No entry for this student--if they are actually in
                   the course, they probably have not been graded for
                   certificate generation yet.
    generating   - A request has been made to generate a certificate,
                   but it has not been generated yet.
    deleting     - A request has been made to delete a certificate.

    deleted      - The certificate has been deleted.
    downloadable - The certificate is available for download.
    notpassing   - The student was graded but is not passing
    restricted   - The student is on the restricted embargo list and
                   should not be issued a certificate. This will
                   be set if allow_certificate is set to False in
                   the userprofile table
    unverified   - The student is in verified enrollment track and
                   the student did not have their identity verified,
                   even though they should be eligible for the cert otherwise.

    If the status is "downloadable", the dictionary also contains
    "download_url".

    If the student has been graded, the dictionary also contains their
    grade for the course with the key "grade".
    """
    # Import here instead of top of file since this module gets imported before
    # the course_modes app is loaded, resulting in a Django deprecation warning.
    from common.djangoapps.course_modes.models import CourseMode

    if generated_certificate:
        cert_status = {
            'status': generated_certificate.status,
            'mode': generated_certificate.mode,
            'uuid': generated_certificate.verify_uuid,
        }
        if generated_certificate.grade:
            cert_status['grade'] = generated_certificate.grade

        if generated_certificate.mode == 'audit':
            course_mode_slugs = [
                mode.slug for mode in CourseMode.modes_for_course(
                    generated_certificate.course_id)
            ]
            # Short term fix to make sure old audit users with certs still see their certs
            # only do this if there if no honor mode
            if 'honor' not in course_mode_slugs:
                cert_status['status'] = CertificateStatuses.auditing
                return cert_status

        if generated_certificate.status == CertificateStatuses.downloadable:
            cert_status['download_url'] = generated_certificate.download_url

        return cert_status
    else:
        return {
            'status': CertificateStatuses.unavailable,
            'mode': GeneratedCertificate.MODES.honor,
            'uuid': None
        }
def _section_send_email(course, access):
    """ Provide data for the corresponding bulk email section """
    course_key = course.id

    # Monkey-patch applicable_aside_types to return no asides for the duration of this render
    with patch.object(course.runtime, 'applicable_aside_types',
                      null_applicable_aside_types):
        # This HtmlBlock is only being used to generate a nice text editor.
        html_module = HtmlBlock(
            course.system, DictFieldData({'data': ''}),
            ScopeIds(None, None, None,
                     course_key.make_usage_key('html', 'fake')))
        fragment = course.system.render(html_module, 'studio_view')
    fragment = wrap_xblock(
        'LmsRuntime',
        html_module,
        'studio_view',
        fragment,
        None,
        extra_data={"course-id": str(course_key)},
        usage_id_serializer=lambda usage_id: quote_slashes(str(usage_id)),
        # Generate a new request_token here at random, because this module isn't connected to any other
        # xblock rendering.
        request_token=uuid.uuid1().hex)
    cohorts = []
    if is_course_cohorted(course_key):
        cohorts = get_course_cohorts(course)
    course_modes = []
    if not VerifiedTrackCohortedCourse.is_verified_track_cohort_enabled(
            course_key):
        course_modes = CourseMode.modes_for_course(course_key,
                                                   include_expired=True,
                                                   only_selectable=False)
    email_editor = fragment.content
    section_data = {
        'section_key':
        'send_email',
        'section_display_name':
        _('Email'),
        'access':
        access,
        'send_email':
        reverse('send_email', kwargs={'course_id': str(course_key)}),
        'editor':
        email_editor,
        'cohorts':
        cohorts,
        'course_modes':
        course_modes,
        'default_cohort_name':
        DEFAULT_COHORT_NAME,
        'list_instructor_tasks_url':
        reverse('list_instructor_tasks', kwargs={'course_id':
                                                 str(course_key)}),
        'email_background_tasks_url':
        reverse('list_background_email_tasks',
                kwargs={'course_id': str(course_key)}),
        'email_content_history_url':
        reverse('list_email_content', kwargs={'course_id': str(course_key)}),
    }
    return section_data
Пример #14
0
def certificates_list_handler(request, course_key_string):
    """
    A RESTful handler for Course Certificates

    GET
        html: return Certificates list page (Backbone application)
    POST
        json: create new Certificate
    """
    course_key = CourseKey.from_string(course_key_string)
    store = modulestore()
    with store.bulk_operations(course_key):
        try:
            course = _get_course_and_check_access(course_key, request.user)
        except PermissionDenied:
            msg = _('PermissionDenied: Failed in authenticating {user}').format(user=request.user)
            return JsonResponse({"error": msg}, status=403)

        if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'):
            certificate_url = reverse_course_url('certificates_list_handler', course_key)
            course_outline_url = reverse_course_url('course_handler', course_key)
            upload_asset_url = reverse_course_url('assets_handler', course_key)
            activation_handler_url = reverse_course_url(
                handler_name='certificate_activation_handler',
                course_key=course_key
            )
            course_modes = [
                mode.slug for mode in CourseMode.modes_for_course(
                    course_id=course.id, include_expired=True
                ) if mode.slug != 'audit'
            ]

            has_certificate_modes = len(course_modes) > 0

            if has_certificate_modes:
                certificate_web_view_url = get_lms_link_for_certificate_web_view(
                    course_key=course_key,
                    mode=course_modes[0]  # CourseMode.modes_for_course returns default mode if doesn't find anyone.
                )
            else:
                certificate_web_view_url = None

            is_active, certificates = CertificateManager.is_activated(course)

            course_authoring_microfrontend_url = get_proctored_exam_settings_url(course)

            return render_to_response('certificates.html', {
                'context_course': course,
                'certificate_url': certificate_url,
                'course_outline_url': course_outline_url,
                'upload_asset_url': upload_asset_url,
                'certificates': certificates,
                'has_certificate_modes': has_certificate_modes,
                'course_modes': course_modes,
                'certificate_web_view_url': certificate_web_view_url,
                'is_active': is_active,
                'is_global_staff': GlobalStaff().has_user(request.user),
                'certificate_activation_handler_url': activation_handler_url,
                'course_authoring_microfrontend_url': course_authoring_microfrontend_url,
            })
        elif "application/json" in request.META.get('HTTP_ACCEPT'):
            # Retrieve the list of certificates for the specified course
            if request.method == 'GET':
                certificates = CertificateManager.get_certificates(course)
                return JsonResponse(certificates, encoder=EdxJSONEncoder)
            elif request.method == 'POST':
                # Add a new certificate to the specified course
                try:
                    new_certificate = CertificateManager.deserialize_certificate(course, request.body)
                except CertificateValidationError as err:
                    return JsonResponse({"error": str(err)}, status=400)
                if course.certificates.get('certificates') is None:
                    course.certificates['certificates'] = []
                course.certificates['certificates'].append(new_certificate.certificate_data)
                response = JsonResponse(CertificateManager.serialize_certificate(new_certificate), status=201)
                response["Location"] = reverse_course_url(
                    'certificates_detail_handler',
                    course.id,
                    kwargs={'certificate_id': new_certificate.id}
                )
                store.update_item(course, request.user.id)
                CertificateManager.track_event('created', {
                    'course_id': str(course.id),
                    'configuration_id': new_certificate.id
                })
                course = _get_course_and_check_access(course_key, request.user)
                return response
        else:
            return HttpResponse(status=406)