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 )
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
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
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))
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
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}
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)
def _attach_course_run_may_certify(self, run_mode): run_mode[ 'may_certify'] = certificate_api.certificates_viewable_for_course( self.course_overview)
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