def test_available_vs_display_date(self, feature_enabled, is_self_paced, uses_avail_date): self.course.self_paced = is_self_paced with configure_waffle_namespace(feature_enabled): # With no available_date set, both return modified_date assert self.certificate.modified_date == api.available_date_for_certificate( self.course, self.certificate) assert self.certificate.modified_date == api.display_date_for_certificate( self.course, self.certificate) # With an available date set in the past, both return the available date (if configured) self.course.certificate_available_date = datetime(2017, 2, 1, tzinfo=pytz.UTC) maybe_avail = self.course.certificate_available_date if uses_avail_date else self.certificate.modified_date assert maybe_avail == api.available_date_for_certificate( self.course, self.certificate) assert maybe_avail == api.display_date_for_certificate( self.course, self.certificate) # With a future available date, they each return a different date self.course.certificate_available_date = datetime.max.replace( tzinfo=pytz.UTC) maybe_avail = self.course.certificate_available_date if uses_avail_date else self.certificate.modified_date assert maybe_avail == api.available_date_for_certificate( self.course, self.certificate) assert self.certificate.modified_date == api.display_date_for_certificate( self.course, self.certificate)
def _update_certificate_context(context, user_certificate, platform_name): """ Build up the certificate web view context using the provided values (Helper method to keep the view clean) """ # Populate dynamic output values using the course/certificate data loaded above certificate_type = context.get('certificate_type') # Override the defaults with any mode-specific static values context['certificate_id_number'] = user_certificate.verify_uuid context['certificate_verify_url'] = "{prefix}{uuid}{suffix}".format( prefix=context.get('certificate_verify_url_prefix'), uuid=user_certificate.verify_uuid, suffix=context.get('certificate_verify_url_suffix')) # Translators: The format of the date includes the full name of the month course = get_course_by_id( user_certificate.course_id) if user_certificate.course_id else None date = display_date_for_certificate(course, user_certificate) context['certificate_date_issued'] = _('{month} {day}, {year}').format( month=strftime_localized(date, "%B"), day=date.day, year=date.year) # Translators: This text represents the verification of the certificate context['document_meta_description'] = _( 'This is a valid {platform_name} certificate for {user_name}, ' 'who participated in {partner_short_name} {course_number}').format( platform_name=platform_name, user_name=context['accomplishment_copy_name'], partner_short_name=context['organization_short_name'], course_number=context['course_number']) # Translators: This text is bound to the HTML 'title' element of the page and appears in the browser title bar context['document_title'] = _( "{partner_short_name} {course_number} Certificate | {platform_name}" ).format(partner_short_name=context['organization_short_name'], course_number=context['course_number'], platform_name=platform_name) # Translators: This text fragment appears after the student's name (displayed in a large font) on the certificate # screen. The text describes the accomplishment represented by the certificate information displayed to the user context['accomplishment_copy_description_full'] = _( "successfully completed, received a passing grade, and was " "awarded this {platform_name} {certificate_type} " "Certificate of Completion in ").format( platform_name=platform_name, certificate_type=context.get("certificate_type")) certificate_type_description = get_certificate_description( user_certificate.mode, certificate_type, platform_name) if certificate_type_description: context['certificate_type_description'] = certificate_type_description # Translators: This text describes the purpose (and therefore, value) of a course certificate context['certificate_info_description'] = _( "{platform_name} acknowledges achievements through " "certificates, which are awarded for course activities " "that {platform_name} students complete.").format( platform_name=platform_name, tos_url=context.get('company_tos_url'), verified_cert_url=context.get('company_verified_certificate_url'))
def _update_certificate_context(context, course, user_certificate, platform_name): """ Build up the certificate web view context using the provided values (Helper method to keep the view clean) """ # Populate dynamic output values using the course/certificate data loaded above certificate_type = context.get('certificate_type') # Override the defaults with any mode-specific static values context['certificate_id_number'] = user_certificate.verify_uuid context['certificate_verify_url'] = "{prefix}{uuid}{suffix}".format( prefix=context.get('certificate_verify_url_prefix'), uuid=user_certificate.verify_uuid, suffix=context.get('certificate_verify_url_suffix') ) # Translators: The format of the date includes the full name of the month date = display_date_for_certificate(course, user_certificate) context['certificate_date_issued'] = _('{month} {day}, {year}').format( month=strftime_localized(date, "%B"), day=date.day, year=date.year ) # Translators: This text represents the verification of the certificate context['document_meta_description'] = _('This is a valid {platform_name} certificate for {user_name}, ' 'who participated in {partner_short_name} {course_number}').format( platform_name=platform_name, user_name=context['accomplishment_copy_name'], partner_short_name=context['organization_short_name'], course_number=context['course_number'] ) # Translators: This text is bound to the HTML 'title' element of the page and appears in the browser title bar context['document_title'] = _("{partner_short_name} {course_number} Certificate | {platform_name}").format( partner_short_name=context['organization_short_name'], course_number=context['course_number'], platform_name=platform_name ) # Translators: This text fragment appears after the student's name (displayed in a large font) on the certificate # screen. The text describes the accomplishment represented by the certificate information displayed to the user context['accomplishment_copy_description_full'] = _("successfully completed, received a passing grade, and was " "awarded this {platform_name} {certificate_type} " "Certificate of Completion in ").format( platform_name=platform_name, certificate_type=context.get("certificate_type")) certificate_type_description = get_certificate_description(user_certificate.mode, certificate_type, platform_name) if certificate_type_description: context['certificate_type_description'] = certificate_type_description # Translators: This text describes the purpose (and therefore, value) of a course certificate context['certificate_info_description'] = _("{platform_name} acknowledges achievements through " "certificates, which are awarded for course activities " "that {platform_name} students complete.").format( platform_name=platform_name, tos_url=context.get('company_tos_url'), verified_cert_url=context.get('company_verified_certificate_url'))
def test_available_vs_display_date( self, feature_enabled, is_self_paced, uses_avail_date ): self.course.self_paced = is_self_paced with configure_waffle_namespace(feature_enabled): # With no available_date set, both return modified_date self.assertEqual(self.certificate.modified_date, api.available_date_for_certificate(self.course, self.certificate)) self.assertEqual(self.certificate.modified_date, api.display_date_for_certificate(self.course, self.certificate)) # With an available date set in the past, both return the available date (if configured) self.course.certificate_available_date = datetime(2017, 2, 1, tzinfo=pytz.UTC) maybe_avail = self.course.certificate_available_date if uses_avail_date else self.certificate.modified_date self.assertEqual(maybe_avail, api.available_date_for_certificate(self.course, self.certificate)) self.assertEqual(maybe_avail, api.display_date_for_certificate(self.course, self.certificate)) # With a future available date, they each return a different date self.course.certificate_available_date = datetime.max.replace(tzinfo=pytz.UTC) maybe_avail = self.course.certificate_available_date if uses_avail_date else self.certificate.modified_date self.assertEqual(maybe_avail, api.available_date_for_certificate(self.course, self.certificate)) self.assertEqual(self.certificate.modified_date, api.display_date_for_certificate(self.course, self.certificate))
def get_arabic_certificate_extra_context(request, course_id): """ Compute `date_issued` and `gender` for the Arabic Course Certificates. This method is used directly from arabic certificate template. This is done to avoid core changes and customise the Arabic certificates. To display gender specific text, we need to send gender in context. Date formatting is also custom for Arabic certificates so this method returns date in the required format as well. Args: request (WSGIRequest): request object course_id (str): Course Id Returns: tuple: Gender of the learner and Date issued. """ certificate = None certificate_uuid = request.resolver_match.kwargs.get('certificate_uuid', None) if certificate_uuid: certificate = GeneratedCertificate.eligible_certificates.filter( verify_uuid=certificate_uuid, status=CertificateStatuses.downloadable ).first() user = certificate.user if certificate else request.user course_key = CourseKey.from_string(course_id) course = get_course_by_id(course_key) if not certificate: preview_mode = request.GET.get('preview', None) certificate = _get_user_certificate(request, user, course_key, course, preview_mode) date_format = u'{day} {month} {year}' if course.language == ARABIC_LANGUAGE_CODE else u'{month} {day}, {year}' display_date = display_date_for_certificate(course, certificate) with translation.override(ARABIC_LANGUAGE_CODE): date_issued = _(date_format).format( # pylint: disable=translation-of-non-string month=strftime_localized(display_date, '%B'), day=display_date.day, year=display_date.year ) return user.profile.gender, date_issued
def get_certificates(self): """ Get certificates data for user. Returns: list: Contains dicts which has data for all the courses. """ user = self.request.user certificates = GeneratedCertificate.eligible_certificates.filter( user=user).order_by('course_id') certificate_data = [] for cert in certificates: formatted_cert = format_certificate_for_user(user.username, cert) if formatted_cert: course = get_course_by_id(cert.course_id) formatted_cert['course_display_name'] = course.display_name formatted_cert['date_created'] = strftime_localized( display_date_for_certificate(course, cert), '%b %d, %Y') certificate_data.append(formatted_cert) return certificate_data
def award_course_certificate(self, username, course_run_key): """ This task is designed to be called whenever a student GeneratedCertificate is updated. It can be called independently for a username and a course_run, but is invoked on each GeneratedCertificate.save. """ LOGGER.info('Running task award_course_certificate for username %s', username) countdown = 2**self.request.retries # If the credentials config model is disabled for this # feature, it may indicate a condition where processing of such tasks # has been temporarily disabled. Since this is a recoverable situation, # mark this task for retry instead of failing it altogether. if not CredentialsApiConfig.current().is_learner_issuance_enabled: LOGGER.warning( 'Task award_course_certificate cannot be executed when credentials issuance is disabled in API config', ) raise self.retry(countdown=countdown, max_retries=MAX_RETRIES) try: course_key = CourseKey.from_string(course_run_key) try: user = User.objects.get(username=username) except User.DoesNotExist: LOGGER.exception( 'Task award_course_certificate was called with invalid username %s', username) # Don't retry for this case - just conclude the task. return # Get the cert for the course key and username if it's both passing and available in professional/verified try: certificate = GeneratedCertificate.eligible_certificates.get( user=user.id, course_id=course_key) except GeneratedCertificate.DoesNotExist: LOGGER.exception( 'Task award_course_certificate was called without Certificate found for %s to user %s', course_key, username) return if certificate.mode in CourseMode.VERIFIED_MODES + CourseMode.CREDIT_MODES: try: course_overview = CourseOverview.get_from_id(course_key) except (CourseOverview.DoesNotExist, IOError): LOGGER.exception( 'Task award_course_certificate was called without course overview data for course %s', course_key) return credentials_client = get_credentials_api_client( User.objects.get( username=settings.CREDENTIALS_SERVICE_USERNAME), org=course_key.org, ) # FIXME This may result in visible dates that do not update alongside the Course Overview if that changes # This is a known limitation of this implementation and was chosen to reduce the amount of replication, # endpoints, celery tasks, and jenkins jobs that needed to be written for this functionality visible_date = display_date_for_certificate( course_overview, certificate) post_course_certificate(credentials_client, username, certificate, visible_date) LOGGER.info('Awarded certificate for course %s to user %s', course_key, username) except Exception as exc: LOGGER.exception( 'Failed to determine course certificates to be awarded for user %s', username) raise self.retry(exc=exc, countdown=countdown, max_retries=MAX_RETRIES)
def award_course_certificate(self, username, course_run_key): """ This task is designed to be called whenever a student GeneratedCertificate is updated. It can be called independently for a username and a course_run, but is invoked on each GeneratedCertificate.save. """ LOGGER.info('Running task award_course_certificate for username %s', username) countdown = 2 ** self.request.retries # If the credentials config model is disabled for this # feature, it may indicate a condition where processing of such tasks # has been temporarily disabled. Since this is a recoverable situation, # mark this task for retry instead of failing it altogether. if not CredentialsApiConfig.current().is_learner_issuance_enabled: LOGGER.warning( 'Task award_course_certificate cannot be executed when credentials issuance is disabled in API config', ) raise self.retry(countdown=countdown, max_retries=MAX_RETRIES) try: course_key = CourseKey.from_string(course_run_key) try: user = User.objects.get(username=username) except User.DoesNotExist: LOGGER.exception('Task award_course_certificate was called with invalid username %s', username) # Don't retry for this case - just conclude the task. return # Get the cert for the course key and username if it's both passing and available in professional/verified try: certificate = GeneratedCertificate.eligible_certificates.get( user=user.id, course_id=course_key ) except GeneratedCertificate.DoesNotExist: LOGGER.exception( 'Task award_course_certificate was called without Certificate found for %s to user %s', course_key, username ) return if certificate.mode in CourseMode.VERIFIED_MODES + CourseMode.CREDIT_MODES: try: course_overview = CourseOverview.get_from_id(course_key) except (CourseOverview.DoesNotExist, IOError): LOGGER.exception( 'Task award_course_certificate was called without course overview data for course %s', course_key ) return credentials_client = get_credentials_api_client(User.objects.get( username=settings.CREDENTIALS_SERVICE_USERNAME), org=course_key.org, ) # FIXME This may result in visible dates that do not update alongside the Course Overview if that changes # This is a known limitation of this implementation and was chosen to reduce the amount of replication, # endpoints, celery tasks, and jenkins jobs that needed to be written for this functionality visible_date = display_date_for_certificate(course_overview, certificate) post_course_certificate(credentials_client, username, certificate, visible_date) LOGGER.info('Awarded certificate for course %s to user %s', course_key, username) except Exception as exc: LOGGER.exception('Failed to determine course certificates to be awarded for user %s', username) raise self.retry(exc=exc, countdown=countdown, max_retries=MAX_RETRIES)