Exemplo n.º 1
0
def _is_certificate_earned_but_not_available(course_overview, status):
    """
    Returns True if the user is passing the course, but the certificate is not visible due to display behavior or
    available date

    Params:
        course_overview (CourseOverview): The course to check we're checking the certificate for
        status (str): The certificate status the user has in the course

    Returns:
        (bool): True if the user earned the certificate but it's hidden due to display behavior, else False

    """
    if settings.FEATURES.get("ENABLE_V2_CERT_DISPLAY_SETTINGS"):
        return (
            not certificates_viewable_for_course(course_overview)
            and CertificateStatuses.is_passing_status(status)
            and course_overview.certificates_display_behavior in (
                CertificatesDisplayBehaviors.END_WITH_DATE,
                CertificatesDisplayBehaviors.END
            )
        )
    else:
        return (
            not certificates_viewable_for_course(course_overview) and
            CertificateStatuses.is_passing_status(status) and
            course_overview.certificate_available_date
        )
Exemplo n.º 2
0
    def _get_certificates_for_user(self, username):
        """
        Returns a user's viewable certificates sorted by course name.
        """
        course_certificates = get_certificates_for_user(username)
        passing_certificates = {}
        for course_certificate in course_certificates:
            if course_certificate.get('is_passing', False):
                course_key = course_certificate['course_key']
                passing_certificates[course_key] = course_certificate

        viewable_certificates = []
        for course_key, course_overview in CourseOverview.get_from_ids(
            list(passing_certificates.keys())
        ).items():
            if not course_overview:
                # For deleted XML courses in which learners have a valid certificate.
                # i.e. MITx/7.00x/2013_Spring
                course_overview = self._get_pseudo_course_overview(course_key)
            if certificates_viewable_for_course(course_overview):
                course_certificate = passing_certificates[course_key]
                # add certificate into viewable certificate list only if it's a PDF certificate
                # or there is an active certificate configuration.
                if course_certificate['is_pdf_certificate'] or course_overview.has_any_active_web_certificate:
                    course_certificate['course_display_name'] = course_overview.display_name_with_default
                    course_certificate['course_organization'] = course_overview.display_org_with_default
                    viewable_certificates.append(course_certificate)

        viewable_certificates.sort(key=lambda certificate: certificate['created'])
        return viewable_certificates
Exemplo n.º 3
0
def _get_user_certificate(request,
                          user,
                          course_key,
                          course,
                          preview_mode=None):
    """
    Retrieves user's certificate from db. Creates one in case of preview mode.
    Returns None if there is no certificate generated for given user
    otherwise returns `GeneratedCertificate` instance.
    """
    user_certificate = None
    if preview_mode:
        # certificate is being previewed from studio
        if request.user.has_perm(PREVIEW_CERTIFICATES, course):
            if course.certificate_available_date and not course.self_paced:
                modified_date = course.certificate_available_date
            else:
                modified_date = datetime.now().date()
            user_certificate = GeneratedCertificate(
                mode=preview_mode,
                verify_uuid=str(uuid4().hex),
                modified_date=modified_date,
                created_date=datetime.now().date(),
            )
    elif certificates_viewable_for_course(course):
        # certificate is being viewed by learner or public
        try:
            user_certificate = GeneratedCertificate.eligible_certificates.get(
                user=user,
                course_id=course_key,
                status=CertificateStatuses.downloadable)
        except GeneratedCertificate.DoesNotExist:
            pass

    return user_certificate
Exemplo n.º 4
0
def can_show_certificate_message(course, student, course_grade,
                                 certificates_enabled_for_course):
    """
    Returns True if a course certificate message can be shown
    """
    is_allowlisted = certs_api.is_on_allowlist(student, course.id)
    auto_cert_gen_enabled = auto_certificate_generation_enabled()
    has_active_enrollment = CourseEnrollment.is_enrolled(student, course.id)
    certificates_are_viewable = certs_api.certificates_viewable_for_course(
        course)

    return ((auto_cert_gen_enabled or certificates_enabled_for_course)
            and has_active_enrollment and certificates_are_viewable
            and (course_grade.passed or is_allowlisted))
Exemplo n.º 5
0
def _get_user_certificate(request,
                          user,
                          course_key,
                          course_overview,
                          preview_mode=None):
    """
    Retrieves user's certificate from db. Creates one in case of preview mode.
    Returns None if there is no certificate generated for given user
    otherwise returns `GeneratedCertificate` instance.

    We use the course_overview instead of the course descriptor here, so we get the certificate_available_date and
    certificates_display_behavior validation logic, rather than the raw data from the course descriptor.
    """
    user_certificate = None
    if preview_mode:
        # certificate is being previewed from studio
        if request.user.has_perm(PREVIEW_CERTIFICATES, course_overview):
            if not settings.FEATURES.get("ENABLE_V2_CERT_DISPLAY_SETTINGS"):
                if course_overview.certificate_available_date and not course_overview.self_paced:
                    modified_date = course_overview.certificate_available_date
                else:
                    modified_date = datetime.now().date()
            else:
                if (course_overview.certificates_display_behavior
                        == CertificatesDisplayBehaviors.END_WITH_DATE
                        and course_overview.certificate_available_date
                        and not course_overview.self_paced):
                    modified_date = course_overview.certificate_available_date
                elif course_overview.certificates_display_behavior == CertificatesDisplayBehaviors.END:
                    modified_date = course_overview.end
                else:
                    modified_date = datetime.now().date()
            user_certificate = GeneratedCertificate(
                mode=preview_mode,
                verify_uuid=str(uuid4().hex),
                modified_date=modified_date,
                created_date=datetime.now().date(),
            )
    elif certificates_viewable_for_course(course_overview):
        # certificate is being viewed by learner or public
        try:
            user_certificate = GeneratedCertificate.eligible_certificates.get(
                user=user,
                course_id=course_key,
                status=CertificateStatuses.downloadable)
        except GeneratedCertificate.DoesNotExist:
            pass

    return user_certificate
Exemplo n.º 6
0
    def course_runs_with_state(self):
        """
        Determine which course runs have been completed and failed by the user.

        A course run is considered completed for a user if they have a certificate in the correct state and
        the certificate is available.

        Returns:
            dict with a list of completed and failed runs
        """
        course_run_certificates = certificate_api.get_certificates_for_user(
            self.user.username)

        completed_runs, failed_runs = [], []
        for certificate in course_run_certificates:
            course_key = certificate['course_key']
            course_data = {
                'course_run_id': str(course_key),
                'type':
                self._certificate_mode_translation(certificate['type']),
            }

            try:
                course_overview = CourseOverview.get_from_id(course_key)
            except CourseOverview.DoesNotExist:
                may_certify = True
            else:
                may_certify = certificate_api.certificates_viewable_for_course(
                    course_overview)

            if (CertificateStatuses.is_passing_status(certificate['status'])
                    and may_certify):
                completed_runs.append(course_data)
            else:
                failed_runs.append(course_data)

        return {'completed': completed_runs, 'failed': failed_runs}
Exemplo n.º 7
0
    def get(self, request, *args, **kwargs):
        course_key_string = kwargs.get('course_key_string')
        course_key = CourseKey.from_string(course_key_string)
        original_user_is_global_staff = self.request.user.is_staff
        original_user_is_staff = has_access(request.user, 'staff',
                                            course_key).has_access

        course = course_detail(request, request.user.username, course_key)

        # We must compute course load access *before* setting up masquerading,
        # else course staff (who are not enrolled) will not be able view
        # their course from the perspective of a learner.
        load_access = check_course_access(
            course,
            request.user,
            'load',
            check_if_enrolled=True,
            check_if_authenticated=True,
        )

        _, request.user = setup_masquerade(
            request,
            course_key,
            staff_access=original_user_is_staff,
            reset_masquerade_data=True,
        )

        username = request.user.username if request.user.username else None
        enrollment = CourseEnrollment.get_enrollment(request.user,
                                                     course_key_string)
        user_is_enrolled = bool(enrollment and enrollment.is_active)

        # User locale settings
        user_timezone_locale = user_timezone_locale_prefs(request)
        user_timezone = user_timezone_locale['user_timezone']

        browser_timezone = self.request.query_params.get(
            'browser_timezone', None)
        celebrations = get_celebrations_dict(
            request.user, enrollment, course,
            user_timezone if not None else browser_timezone)

        # Record course goals user activity for (web) learning mfe course tabs
        UserActivity.record_user_activity(request.user, course_key)

        data = {
            'course_id': course.id,
            'username': username,
            'is_staff': has_access(request.user, 'staff',
                                   course_key).has_access,
            'original_user_is_staff': original_user_is_staff,
            'number': course.display_number_with_default,
            'org': course.display_org_with_default,
            'start': course.start,
            'tabs': get_course_tab_list(request.user, course),
            'title': course.display_name_with_default,
            'is_self_paced': getattr(course, 'self_paced', False),
            'is_enrolled': user_is_enrolled,
            'course_access': load_access.to_json(),
            'celebrations': celebrations,
            'user_timezone': user_timezone,
            'can_view_certificate': certificates_viewable_for_course(course),
        }
        context = self.get_serializer_context()
        context['course'] = course
        context['course_overview'] = course
        context['enrollment'] = enrollment
        serializer = self.get_serializer_class()(data, context=context)
        return Response(serializer.data)
Exemplo n.º 8
0
 def _attach_course_run_may_certify(self, run_mode):
     run_mode[
         'may_certify'] = certificate_api.certificates_viewable_for_course(
             self.course_overview)
Exemplo n.º 9
0
def _cert_info(user, course_overview, cert_status):
    """
    Implements the logic for cert_info -- split out for testing.

    Arguments:
        user (User): A user.
        course_overview (CourseOverview): A course.
        cert_status (dict): dictionary containing information about certificate status for the user

    Returns:
        dictionary containing:
            'status': one of 'generating', 'downloadable', 'notpassing', 'restricted', 'auditing',
                'processing', 'unverified', 'unavailable', or 'certificate_earned_but_not_available'
            'show_survey_button': bool
            'can_unenroll': if status allows for unenrollment

        The dictionary may also contain:
            'linked_in_url': url to add cert to LinkedIn profile
            'survey_url': url, only if course_overview.end_of_course_survey_url is not None
            'show_cert_web_view': bool if html web certs are enabled and there is an active web cert
            'cert_web_view_url': url if html web certs are enabled and there is an active web cert
            'download_url': url to download a cert
            'grade': if status is in 'generating', 'downloadable', 'notpassing', 'restricted',
                'auditing', or 'unverified'
    """
    # simplify the status for the template using this lookup table
    template_state = {
        CertificateStatuses.generating: 'generating',
        CertificateStatuses.downloadable: 'downloadable',
        CertificateStatuses.notpassing: 'notpassing',
        CertificateStatuses.restricted: 'restricted',
        CertificateStatuses.auditing: 'auditing',
        CertificateStatuses.audit_passing: 'auditing',
        CertificateStatuses.audit_notpassing: 'auditing',
        CertificateStatuses.unverified: 'unverified',
    }

    certificate_earned_but_not_available_status = 'certificate_earned_but_not_available'
    default_status = 'processing'

    default_info = {
        'status': default_status,
        'show_survey_button': False,
        'can_unenroll': True,
    }

    if cert_status is None:
        return default_info

    status = template_state.get(cert_status['status'], default_status)
    is_hidden_status = status in ('processing', 'generating', 'notpassing',
                                  'auditing')

    if (not certificates_viewable_for_course(course_overview)
            and CertificateStatuses.is_passing_status(status)
            and course_overview.certificate_available_date):
        status = certificate_earned_but_not_available_status

    if (course_overview.certificates_display_behavior == 'early_no_info'
            and is_hidden_status):
        return default_info

    status_dict = {
        'status': status,
        'mode': cert_status.get('mode', None),
        'linked_in_url': None,
        'can_unenroll': status not in DISABLE_UNENROLL_CERT_STATES,
    }

    if status != default_status and course_overview.end_of_course_survey_url is not None:
        status_dict.update({
            'show_survey_button':
            True,
            'survey_url':
            process_survey_link(course_overview.end_of_course_survey_url, user)
        })
    else:
        status_dict['show_survey_button'] = False

    if status == 'downloadable':
        # showing the certificate web view button if certificate is downloadable state and feature flags are enabled.
        if has_html_certificates_enabled(course_overview):
            if course_overview.has_any_active_web_certificate:
                status_dict.update({
                    'show_cert_web_view':
                    True,
                    'cert_web_view_url':
                    get_certificate_url(course_id=course_overview.id,
                                        uuid=cert_status['uuid'])
                })
            elif cert_status['download_url']:
                status_dict['download_url'] = cert_status['download_url']
            else:
                # don't show download certificate button if we don't have an active certificate for course
                status_dict['status'] = 'unavailable'
        elif 'download_url' not in cert_status:
            log.warning(
                "User %s has a downloadable cert for %s, but no download url",
                user.username, course_overview.id)
            return default_info
        else:
            status_dict['download_url'] = cert_status['download_url']

            # If enabled, show the LinkedIn "add to profile" button
            # Clicking this button sends the user to LinkedIn where they
            # can add the certificate information to their profile.
            linkedin_config = LinkedInAddToProfileConfiguration.current()
            if linkedin_config.is_enabled():
                status_dict[
                    'linked_in_url'] = linkedin_config.add_to_profile_url(
                        course_overview.display_name,
                        cert_status.get('mode'),
                        cert_status['download_url'],
                    )

    if status in {
            'generating', 'downloadable', 'notpassing', 'restricted',
            'auditing', 'unverified'
    }:
        cert_grade_percent = -1
        persisted_grade_percent = -1
        persisted_grade = CourseGradeFactory().read(user,
                                                    course=course_overview,
                                                    create_if_needed=False)
        if persisted_grade is not None:
            persisted_grade_percent = persisted_grade.percent

        if 'grade' in cert_status:
            cert_grade_percent = float(cert_status['grade'])

        if cert_grade_percent == -1 and persisted_grade_percent == -1:
            # Note: as of 11/20/2012, we know there are students in this state-- cs169.1x,
            # who need to be regraded (we weren't tracking 'notpassing' at first).
            # We can add a log.warning here once we think it shouldn't happen.
            return default_info
        grades_input = [cert_grade_percent, persisted_grade_percent]
        max_grade = (None if all(grade is None for grade in grades_input) else
                     max(filter(lambda x: x is not None, grades_input)))
        status_dict['grade'] = str(max_grade)

    return status_dict