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 has_access(request.user, 'instructor', course) or has_access( request.user, 'staff', 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=unicode(uuid4().hex), modified_date=modified_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 _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_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_if_exists( passing_certificates.keys()).items(): if certificates_viewable_for_course(course_overview): course_certificate = passing_certificates[course_key] 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_ordered_certificates_for_user(self, request, username): """ Returns a user's certificates sorted by course name. """ course_certificates = certificate_api.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'] try: course_overview = CourseOverview.get_from_id(course_key) course_certificate['course'] = course_overview if certificates_viewable_for_course(course_overview): # add certificate into passing 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: passing_certificates.append(course_certificate) except CourseOverview.DoesNotExist: # This is unlikely to fail as the course should exist. # Ideally the cert should have all the information that # it needs. This might be solved by the Credentials API. pass passing_certificates.sort(key=lambda certificate: certificate['course'] .display_name_with_default) return passing_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 has_access(request.user, 'instructor', course) or has_access(request.user, 'staff', 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=unicode(uuid4().hex), modified_date=modified_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 certificate_downloadable_status(student, course_key): """ Check the student existing certificates against a given course. if status is not generating and not downloadable or error then user can view the generate button. Args: student (user object): logged-in user course_key (CourseKey): ID associated with the course Returns: Dict containing student passed status also download url, uuid for cert if available """ current_status = certificate_status_for_student(student, course_key) # If the certificate status is an error user should view that status is "generating". # On the back-end, need to monitor those errors and re-submit the task. response_data = { 'is_downloadable': False, 'is_generating': True if current_status['status'] in [ CertificateStatuses.generating, # pylint: disable=simplifiable-if-expression CertificateStatuses.error ] else False, 'is_unverified': True if current_status['status'] == CertificateStatuses.unverified else False, # pylint: disable=simplifiable-if-expression 'download_url': None, 'uuid': None, } course_overview = CourseOverview.get_from_id(course_key) if (not certificates_viewable_for_course(course_overview) and (current_status['status'] in CertificateStatuses.PASSED_STATUSES) and course_overview.certificate_available_date): response_data['earned_but_not_available'] = True may_view_certificate = course_overview.may_certify() if current_status[ 'status'] == CertificateStatuses.downloadable and may_view_certificate: response_data['is_downloadable'] = True response_data['download_url'] = current_status[ 'download_url'] or get_certificate_url(student.id, course_key, current_status['uuid']) response_data['is_pdf_certificate'] = bool( current_status['download_url']) response_data['uuid'] = current_status['uuid'] return response_data
def _get_ordered_certificates_for_user(self, request, username): """ Returns a user's certificates sorted by course name. """ course_certificates = certificate_api.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'] try: course_overview = CourseOverview.get_from_id(course_key) course_certificate['course'] = course_overview if certificates_viewable_for_course(course_overview): passing_certificates.append(course_certificate) except CourseOverview.DoesNotExist: # This is unlikely to fail as the course should exist. # Ideally the cert should have all the information that # it needs. This might be solved by the Credentials API. pass passing_certificates.sort(key=lambda certificate: certificate['course'].display_name_with_default) return passing_certificates
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. """ # 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 ('unavailable', 'processing', 'generating', 'notpassing', 'auditing') if ( not certificates_viewable_for_course(course_overview) and (status in CertificateStatuses.PASSED_STATUSES) 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']) }) 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( u"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() # posting certificates to LinkedIn is not currently # supported in White Labels if linkedin_config.enabled and not theming_helpers.is_request_in_themed_site(): status_dict['linked_in_url'] = linkedin_config.add_to_profile_url( course_overview.id, 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'] = text_type(max_grade) return status_dict
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. """ # 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 ('unavailable', 'processing', 'generating', 'notpassing', 'auditing') if ( not certificates_viewable_for_course(course_overview) and (status in CertificateStatuses.PASSED_STATUSES) 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']) }) 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( u"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() # posting certificates to LinkedIn is not currently # supported in White Labels if linkedin_config.enabled and not theming_helpers.is_request_in_themed_site(): status_dict['linked_in_url'] = linkedin_config.add_to_profile_url( course_overview.id, 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 status_dict['grade'] = text_type(max(cert_grade_percent, persisted_grade_percent)) return status_dict
def render_html_view(request, user_id, course_id): """ This public view generates an HTML representation of the specified user and course If a certificate is not available, we display a "Sorry!" screen instead """ try: user_id = int(user_id) except ValueError: raise Http404 preview_mode = request.GET.get('preview', None) platform_name = configuration_helpers.get_value("platform_name", settings.PLATFORM_NAME) configuration = CertificateHtmlViewConfiguration.get_config() # Kick the user back to the "Invalid" screen if the feature is disabled globally if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False): return _render_invalid_certificate(course_id, platform_name, configuration) # Load the course and user objects try: course_key = CourseKey.from_string(course_id) user = User.objects.get(id=user_id) course = get_course_by_id(course_key) # For any course or user exceptions, kick the user back to the "Invalid" screen except (InvalidKeyError, User.DoesNotExist, Http404) as exception: error_str = ( "Invalid cert: error finding course %s or user with id " "%d. Specific error: %s" ) log.info(error_str, course_id, user_id, str(exception)) return _render_invalid_certificate(course_id, platform_name, configuration) if not certificates_viewable_for_course(course): log.info( "Invalid cert: Certificate for %s is not viewable yet.", course_id, ) return _render_invalid_certificate(course_id, platform_name, configuration) # Kick the user back to the "Invalid" screen if the feature is disabled for the course if not course.cert_html_view_enabled: log.info( "Invalid cert: HTML certificates disabled for %s. User id: %d", course_id, user_id, ) return _render_invalid_certificate(course_id, platform_name, configuration) # Load user's certificate user_certificate = _get_user_certificate(request, user, course_key, course, preview_mode) if not user_certificate: log.info( "Invalid cert: User %d does not have eligible cert for %s.", user_id, course_id, ) return _render_invalid_certificate(course_id, platform_name, configuration) # Get the active certificate configuration for this course # If we do not have an active certificate, we'll need to send the user to the "Invalid" screen # Passing in the 'preview' parameter, if specified, will return a configuration, if defined active_configuration = get_active_web_certificate(course, preview_mode) if active_configuration is None: log.info( "Invalid cert: course %s does not have an active configuration. User id: %d", course_id, user_id, ) return _render_invalid_certificate(course_id, platform_name, configuration) # Get data from Discovery service that will be necessary for rendering this Certificate. catalog_data = _get_catalog_data_for_course(course_key) # Determine whether to use the standard or custom template to render the certificate. custom_template = None custom_template_language = None if settings.FEATURES.get('CUSTOM_CERTIFICATE_TEMPLATES_ENABLED', False): custom_template, custom_template_language = _get_custom_template_and_language( course.id, user_certificate.mode, catalog_data.pop('content_language', None) ) # Determine the language that should be used to render the certificate. # For the standard certificate template, use the user language. For custom templates, use # the language associated with the template. user_language = translation.get_language() certificate_language = custom_template_language if custom_template else user_language # Generate the certificate context in the correct language, then render the template. with translation.override(certificate_language): context = {'user_language': user_language} _update_context_with_basic_info(context, course_id, platform_name, configuration) context['certificate_data'] = active_configuration # Append/Override the existing view context values with any mode-specific ConfigurationModel values context.update(configuration.get(user_certificate.mode, {})) # Append organization info _update_organization_context(context, course) # Append course info _update_course_context(request, context, course, course_key, platform_name) # Append course run info from discovery context.update(catalog_data) # Append user info _update_context_with_user_info(context, user, user_certificate) # Append social sharing info _update_social_context(request, context, course, user, user_certificate, platform_name) # Append/Override the existing view context values with certificate specific values _update_certificate_context(context, course, user_certificate, platform_name) # Append badge info _update_badge_context(context, course, user) # Append site configuration overrides _update_configuration_context(context, configuration) # Add certificate header/footer data to current context context.update(get_certificate_header_context(is_secure=request.is_secure())) context.update(get_certificate_footer_context()) # Append/Override the existing view context values with any course-specific static values from Advanced Settings context.update(course.cert_html_view_overrides) # Track certificate view events _track_certificate_events(request, context, course, user, user_certificate) # Render the certificate return _render_valid_certificate(request, context, custom_template)