def _certificate_html_view_configuration(self, configuration_string, enabled=True): """ This will create a certificate html configuration """ config = CertificateHtmlViewConfiguration(enabled=enabled, configuration=configuration_string) config.save() return config
def test_clean_bad_json(self): """ Tests if bad JSON string was given. """ self.config = CertificateHtmlViewConfiguration( configuration='{"bad":"test"') self.assertRaises(ValidationError, self.config.clean)
def setUp(self): super(CertificateHtmlViewConfigurationTest, self).setUp() self.configuration_string = """{ "default": { "url": "http://www.edx.org", "logo_src": "http://www.edx.org/static/images/logo.png" }, "honor": { "logo_src": "http://www.edx.org/static/images/honor-logo.png" } }""" self.config = CertificateHtmlViewConfiguration(configuration=self.configuration_string)
def test_certificate_without_branding_urls(self): """ Test that links from CertificateHtmlViewConfiguration are used if corresponding microsite or marketing urls are not present. """ self._add_course_certificates(count=1, signatory_count=1, is_active=True) self.course.save() self.store.update_item(self.course, self.user.id) configuration = CertificateHtmlViewConfiguration.get_config() test_url = get_certificate_url( user_id=self.user.id, course_id=unicode(self.course.id) ) response = self.client.get(test_url, HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME) # ABOUT is not present in MICROSITE_CONFIGURATION['test_microsite']["urls"] or MKTG_URL_LINK_MAP, # so web certificate will use CertificateHtmlViewConfiguration url. self.assertContains( response, configuration['microsites']['testmicrosite']['company_about_url'], ) # PRIVACY is not present in MICROSITE_CONFIGURATION['test_microsite']["urls"] or MKTG_URL_LINK_MAP, # so web certificate will use CertificateHtmlViewConfiguration url. self.assertContains( response, configuration['microsites']['testmicrosite']['company_privacy_url'], ) # TOS_AND_HONOR is not present in MICROSITE_CONFIGURATION['test_microsite']["urls"] or MKTG_URL_LINK_MAP, # so web certificate will use CertificateHtmlViewConfiguration url. self.assertContains( response, configuration['microsites']['testmicrosite']['company_tos_url'], )
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 """ preview_mode = request.GET.get('preview', None) platform_name = microsite.get_value("platform_name", settings.PLATFORM_NAME) configuration = CertificateHtmlViewConfiguration.get_config() # Create the initial view context, bootstrapping with Django settings and passed-in values context = {} _update_context_with_basic_info(context, course_id, platform_name, configuration) invalid_template_path = 'certificates/invalid.html' # Kick the user back to the "Invalid" screen if the feature is disabled if not has_html_certificates_enabled(course_id): return render_to_response(invalid_template_path, context) # Load the course and user objects try: course_key = CourseKey.from_string(course_id) user = User.objects.get(id=user_id) course = modulestore().get_course(course_key) # For any other expected exceptions, kick the user back to the "Invalid" screen except (InvalidKeyError, ItemNotFoundError, User.DoesNotExist): return render_to_response(invalid_template_path, context) # Load user's certificate user_certificate = _get_user_certificate(request, user, course_key, course, preview_mode) if not user_certificate: return render_to_response(invalid_template_path, context) # 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: return render_to_response(invalid_template_path, context) 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, platform_name) # 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, user_certificate, platform_name) # Append badge info _update_badge_context(context, course, user) # Append microsite overrides _update_microsite_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) # FINALLY, render appropriate certificate return _render_certificate_template(request, context, course, user_certificate)
def render_html_view(request): """ This view generates an HTML representation of the specified student's certificate If a certificate is not available, we display a "Sorry!" screen instead """ # Initialize the template context and bootstrap with default values from configuration context = {} configuration = CertificateHtmlViewConfiguration.get_config() context = configuration.get('default', {}) invalid_template_path = 'certificates/invalid.html' # Translators: This text is bound to the HTML 'title' element of the page and appears # in the browser title bar when a requested certificate is not found or recognized context['document_title'] = _("Invalid Certificate") # Feature Flag check if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False): return render_to_response(invalid_template_path, context) course_id = request.GET.get('course', None) context['course'] = course_id if not course_id: return render_to_response(invalid_template_path, context) # Course Lookup try: course_key = CourseKey.from_string(course_id) except InvalidKeyError: return render_to_response(invalid_template_path, context) course = modulestore().get_course(course_key) if not course: return render_to_response(invalid_template_path, context) # Certificate Lookup try: certificate = GeneratedCertificate.objects.get( user=request.user, course_id=course_key ) except GeneratedCertificate.DoesNotExist: return render_to_response(invalid_template_path, context) # Override the defaults with any mode-specific static values context.update(configuration.get(certificate.mode, {})) # Override further with any course-specific static values context.update(course.cert_html_view_overrides) # Populate dynamic output values using the course/certificate data loaded above user_fullname = request.user.profile.name platform_name = context.get('platform_name') context['accomplishment_copy_name'] = user_fullname context['accomplishment_copy_course_org'] = course.org context['accomplishment_copy_course_name'] = course.display_name context['certificate_id_number'] = certificate.verify_uuid context['certificate_verify_url'] = "{prefix}{uuid}{suffix}".format( prefix=context.get('certificate_verify_url_prefix'), uuid=certificate.verify_uuid, suffix=context.get('certificate_verify_url_suffix') ) context['logo_alt'] = platform_name accd_course_org_html = '<span class="detail--xuniversity">{partner_name}</span>'.format(partner_name=course.org) accd_platform_name_html = '<span class="detail--company">{platform_name}</span>'.format(platform_name=platform_name) # Translators: This line appears on the certificate after the name of a course, and provides more # information about the organizations providing the course material to platform users context['accomplishment_copy_course_description'] = _('a course of study offered by {partner_name}, ' 'through {platform_name}.').format( partner_name=accd_course_org_html, platform_name=accd_platform_name_html ) context['accomplishment_more_title'] = _("More Information About {user_name}'s Certificate:").format( user_name=user_fullname ) # Translators: This line appears on the page just before the generation date for the certificate context['certificate_date_issued_title'] = _("Issued On:") # Translators: The format of the date includes the full name of the month context['certificate_date_issued'] = _('{month} {day}, {year}').format( month=certificate.modified_date.strftime("%B"), day=certificate.modified_date.day, year=certificate.modified_date.year ) # Translators: The Certificate ID Number is an alphanumeric value unique to each individual certificate context['certificate_id_number_title'] = _('Certificate ID Number') context['certificate_info_title'] = _('About {platform_name} Certificates').format( platform_name=platform_name ) # Translators: This text describes the purpose (and therefore, value) of a course certificate # 'verifying your identity' refers to the process for establishing the authenticity of the student context['certificate_info_description'] = _("{platform_name} acknowledges achievements through certificates, which " "are awarded for various activities {platform_name} students complete " "under the <a href='{tos_url}'>{platform_name} Honor Code</a>. Some " "certificates require completing additional steps, such as " "<a href='{verified_cert_url}'> verifying your identity</a>.").format( platform_name=platform_name, tos_url=context.get('company_tos_url'), verified_cert_url=context.get('company_verified_certificate_url') ) # Translators: Certificate Types correspond to the different enrollment options available for a given course context['certificate_type_title'] = _('{certificate_type} Certificate').format( certificate_type=context.get('certificate_type') ) context['certificate_verify_title'] = _("How {platform_name} Validates Student Certificates").format( platform_name=platform_name ) # Translators: This text describes the validation mechanism for a certificate file (known as GPG security) context['certificate_verify_description'] = _('Certificates issued by {platform_name} are signed by a gpg key so ' 'that they can be validated independently by anyone with the ' '{platform_name} public key. For independent verification, ' '{platform_name} uses what is called a ' '"detached signature""".').format(platform_name=platform_name) context['certificate_verify_urltext'] = _("Validate this certificate for yourself") # Translators: This text describes (at a high level) the mission and charter the edX platform and organization context['company_about_description'] = _("{platform_name} offers interactive online classes and MOOCs from the " "world's best universities, including MIT, Harvard, Berkeley, University " "of Texas, and many others. {platform_name} is a non-profit online " "initiative created by founding partners Harvard and MIT.").format( platform_name=platform_name ) context['company_about_title'] = _("About {platform_name}").format(platform_name=platform_name) context['company_about_urltext'] = _("Learn more about {platform_name}").format(platform_name=platform_name) context['company_courselist_urltext'] = _("Learn with {platform_name}").format(platform_name=platform_name) context['company_careers_urltext'] = _("Work at {platform_name}").format(platform_name=platform_name) context['company_contact_urltext'] = _("Contact {platform_name}").format(platform_name=platform_name) context['company_privacy_urltext'] = _("Privacy Policy") context['company_tos_urltext'] = _("Terms of Service & Honor Code") # Translators: This text appears near the top of the certficate and describes the guarantee provided by edX context['document_banner'] = _("{platform_name} acknowledges the following student accomplishment").format( platform_name=platform_name ) context['logo_subtitle'] = _("Certificate Validation") if certificate.mode == 'honor': # Translators: This text describes the 'Honor' course certificate type. context['certificate_type_description'] = _("An {cert_type} Certificate signifies that an {platform_name} " "learner has agreed to abide by {platform_name}'s honor code and " "completed all of the required tasks for this course under its " "guidelines.").format( cert_type=context.get('certificate_type'), platform_name=platform_name ) elif certificate.mode == 'verified': # Translators: This text describes the 'ID Verified' course certificate type, which is a higher level of # verification offered by edX. This type of verification is useful for professional education/certifications context['certificate_type_description'] = _("An {cert_type} Certificate signifies that an {platform_name} " "learner has agreed to abide by {platform_name}'s honor code and " "completed all of the required tasks for this course under its " "guidelines, as well as having their photo ID checked to verify " "their identity.").format( cert_type=context.get('certificate_type'), platform_name=platform_name ) elif certificate.mode == 'xseries': # Translators: This text describes the 'XSeries' course certificate type. An XSeries is a collection of # courses related to each other in a meaningful way, such as a specific topic or theme, or even an organization context['certificate_type_description'] = _("An {cert_type} Certificate demonstrates a high level of " "achievement in a program of study, and includes verification of " "the student's identity.").format( cert_type=context.get('certificate_type') ) # Translators: This is the copyright line which appears at the bottom of the certificate page/screen context['copyright_text'] = _('© {year} {platform_name}. All rights reserved.').format( year=datetime.now().year, platform_name=platform_name ) # 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_name} {course_number}').format( platform_name=platform_name, user_name=user_fullname, partner_name=course.org, course_number=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'] = _("Valid {partner_name} {course_number} Certificate | {platform_name}").format( partner_name=course.org, course_number=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 a {platform_name} {certificate_type} " "Certificate of Completion in ").format( platform_name=platform_name, certificate_type=context.get("certificate_type") ) return render_to_response("certificates/valid.html", context)
def render_html_view(request, user_id, course_id): """ This public view generates an HTML representation of the specified student's certificate If a certificate is not available, we display a "Sorry!" screen instead """ # Create the view context and bootstrap with Django settings and passed-in values context = {} context['platform_name'] = settings.PLATFORM_NAME context['course_id'] = course_id # Update the view context with the default ConfigurationModel settings configuration = CertificateHtmlViewConfiguration.get_config() context.update(configuration.get('default', {})) # Translators: This text is bound to the HTML 'title' element of the page and appears # in the browser title bar when a requested certificate is not found or recognized context['document_title'] = _("Invalid Certificate") invalid_template_path = 'certificates/invalid.html' # Kick the user back to the "Invalid" screen if the feature is disabled if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False): return render_to_response(invalid_template_path, context) # Load the core building blocks for the view context try: course_key = CourseKey.from_string(course_id) user = User.objects.get(id=user_id) course = modulestore().get_course(course_key) if not course: raise CourseDoesNotExist # Attempt to load the user's generated certificate data user_certificate = GeneratedCertificate.objects.get( user=user, course_id=course_key ) # If there's no generated certificate data for this user, we need to see if we're in 'preview' mode... # If we are, we'll need to create a mock version of the user_certificate container for previewing except GeneratedCertificate.DoesNotExist: if request.GET.get('preview', None): user_certificate = GeneratedCertificate( mode=request.GET.get('preview'), verify_uuid=unicode(uuid4().hex), modified_date=datetime.now().date() ) else: return render_to_response(invalid_template_path, context) # For any other expected exceptions, kick the user back to the "Invalid" screen except (InvalidKeyError, CourseDoesNotExist, User.DoesNotExist): return render_to_response(invalid_template_path, context) # Okay, now we have all of the pieces, time to put everything together # 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 active_configuration = get_active_web_certificate(course, request.GET.get('preview')) if active_configuration is None and request.GET.get('preview') is None: return render_to_response(invalid_template_path, context) else: 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/Override the existing view context values with request-time values _update_certificate_context(context, course, user, user_certificate) # Append/Override the existing view context values with any course-specific static values from Advanced Settings context.update(course.cert_html_view_overrides) # Override further with any course-specific static values context.update(course.cert_html_view_overrides) return render_to_response("certificates/valid.html", context)
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() # Create the initial view context, bootstrapping with Django settings and passed-in values context = {} _update_context_with_basic_info(context, course_id, platform_name, configuration) invalid_template_path = 'certificates/invalid.html' # 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_to_response(invalid_template_path, context) # 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_to_response(invalid_template_path, context) # 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_to_response(invalid_template_path, context) # 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_to_response(invalid_template_path, context) # 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_to_response(invalid_template_path, context) 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 _update_context_with_catalog_data(context, course_key) # 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) # FINALLY, render appropriate certificate return _render_certificate_template(request, context, course, user_certificate)
def test_clean_bad_json(self): """ Tests if bad JSON string was given. """ self.config = CertificateHtmlViewConfiguration(configuration='{"bad":"test"') self.assertRaises(ValidationError, self.config.clean)
class CertificateHtmlViewConfigurationTest(TestCase): """ Test the CertificateHtmlViewConfiguration model. """ def setUp(self): super(CertificateHtmlViewConfigurationTest, self).setUp() self.configuration_string = """{ "default": { "url": "http://www.edx.org", "logo_src": "http://www.edx.org/static/images/logo.png" }, "honor": { "logo_src": "http://www.edx.org/static/images/honor-logo.png" } }""" self.config = CertificateHtmlViewConfiguration(configuration=self.configuration_string) def test_create(self): """ Tests creation of configuration. """ self.config.save() self.assertEquals(self.config.configuration, self.configuration_string) def test_clean_bad_json(self): """ Tests if bad JSON string was given. """ self.config = CertificateHtmlViewConfiguration(configuration='{"bad":"test"') self.assertRaises(ValidationError, self.config.clean) def test_get(self): """ Tests get configuration from saved string. """ self.config.enabled = True self.config.save() expected_config = { "default": { "url": "http://www.edx.org", "logo_src": "http://www.edx.org/static/images/logo.png" }, "honor": { "logo_src": "http://www.edx.org/static/images/honor-logo.png" } } self.assertEquals(self.config.get_config(), expected_config) def test_get_not_enabled_returns_blank(self): """ Tests get configuration that is not enabled. """ self.config.enabled = False self.config.save() self.assertEquals(len(self.config.get_config()), 0) @override_settings(FEATURES=FEATURES_INVALID_FILE_PATH) def test_get_no_database_no_file(self): """ Tests get configuration that is not enabled. """ self.config.configuration = '' self.config.save() self.assertEquals(self.config.get_config(), {})
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) # 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)
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) # 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)
def render_html_view(request, user_id, course_id): """ This public view generates an HTML representation of the specified student's certificate If a certificate is not available, we display a "Sorry!" screen instead """ # Create the initial view context, bootstrapping with Django settings and passed-in values context = {} context['platform_name'] = microsite.get_value("platform_name", settings.PLATFORM_NAME) context['course_id'] = course_id # Update the view context with the default ConfigurationModel settings configuration = CertificateHtmlViewConfiguration.get_config() # if we are in a microsite, then let's first see if there is an override # section in our config config_key = microsite.get_value('microsite_config_key', 'default') # if there is no special microsite override, then let's use default if config_key not in configuration: config_key = 'default' context.update(configuration.get(config_key, {})) # Translators: 'All rights reserved' is a legal term used in copyrighting to protect published content reserved = _("All rights reserved") context[ 'copyright_text'] = '© {year} {platform_name}. {reserved}.'.format( year=settings.COPYRIGHT_YEAR, platform_name=context.get('platform_name'), reserved=reserved) # Translators: This text is bound to the HTML 'title' element of the page and appears # in the browser title bar when a requested certificate is not found or recognized context['document_title'] = _("Invalid Certificate") # Translators: The & characters represent an ampersand character and can be ignored context['company_tos_urltext'] = _("Terms of Service & Honor Code") # Translators: A 'Privacy Policy' is a legal document/statement describing a website's use of personal information context['company_privacy_urltext'] = _("Privacy Policy") # Translators: This line appears as a byline to a header image and describes the purpose of the page context['logo_subtitle'] = _("Certificate Validation") invalid_template_path = 'certificates/invalid.html' # Kick the user back to the "Invalid" screen if the feature is disabled if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False): return render_to_response(invalid_template_path, context) # Load the core building blocks for the view context try: course_key = CourseKey.from_string(course_id) user = User.objects.get(id=user_id) course = modulestore().get_course(course_key) if not course: raise CourseDoesNotExist # Attempt to load the user's generated certificate data user_certificate = GeneratedCertificate.objects.get( user=user, course_id=course_key) # If there's no generated certificate data for this user, we need to see if we're in 'preview' mode... # If we are, we'll need to create a mock version of the user_certificate container for previewing except GeneratedCertificate.DoesNotExist: if request.GET.get('preview', None): user_certificate = GeneratedCertificate( mode=request.GET.get('preview'), verify_uuid=unicode(uuid4().hex), modified_date=datetime.now().date()) else: return render_to_response(invalid_template_path, context) # For any other expected exceptions, kick the user back to the "Invalid" screen except (InvalidKeyError, CourseDoesNotExist, User.DoesNotExist): return render_to_response(invalid_template_path, context) # Badge Request Event Tracking Logic if 'evidence_visit' in request.GET: try: badge = BadgeAssertion.objects.get(user=user, course_id=course_key) tracker.emit( 'edx.badge.assertion.evidence_visited', { 'user_id': user.id, 'course_id': unicode(course_key), 'enrollment_mode': badge.mode, 'assertion_id': badge.id, 'assertion_image_url': badge.data['image'], 'assertion_json_url': badge.data['json']['id'], 'issuer': badge.data['issuer'], }) except BadgeAssertion.DoesNotExist: logger.warn( "Could not find badge for %s on course %s.", user.id, course_key, ) # Okay, now we have all of the pieces, time to put everything together # 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, request.GET.get('preview')) if active_configuration is None: return render_to_response(invalid_template_path, context) else: 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/Override the existing view context values with request-time values _update_certificate_context(context, course, user, user_certificate) # Microsites will need to be able to override any hard coded # content that was put into the context in the # _update_certificate_context() call above. For example the # 'company_about_description' talks about edX, which we most likely # do not want to keep in a microsite # # So we need to re-apply any configuration/content that # we are sourceing from the database. This is somewhat duplicative of # the code at the beginning of this method, but we # need the configuration at the top as some error code paths # require that to be set up early on in the pipeline # microsite_config_key = microsite.get_value('microsite_config_key') if microsite_config_key: context.update(configuration.get(microsite_config_key, {})) # track certificate evidence_visited event for analytics when certificate_user and accessing_user are different if request.user and request.user.id != user.id: emit_certificate_event( 'evidence_visited', user, course_id, course, { 'certificate_id': user_certificate.verify_uuid, 'enrollment_mode': user_certificate.mode, 'social_network': CertificateSocialNetworks.linkedin }) # Append/Override the existing view context values with any course-specific static values from Advanced Settings context.update(course.cert_html_view_overrides) # FINALLY, generate and send the output the client return render_to_response("certificates/valid.html", context)
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 = microsite.get_value("platform_name", settings.PLATFORM_NAME) configuration = CertificateHtmlViewConfiguration.get_config() # Create the initial view context, bootstrapping with Django settings and passed-in values context = {} _update_context_with_basic_info(context, course_id, platform_name, configuration) invalid_template_path = 'certificates/invalid.html' # Kick the user back to the "Invalid" screen if the feature is disabled if not has_html_certificates_enabled(course_id): log.info( "Invalid cert: HTML certificates disabled for %s. User id: %d", course_id, user_id, ) return render_to_response(invalid_template_path, context) # Load the course and user objects try: course_key = CourseKey.from_string(course_id) user = User.objects.get(id=user_id) course = modulestore().get_course(course_key) # For any other expected exceptions, kick the user back to the "Invalid" screen except (InvalidKeyError, ItemNotFoundError, User.DoesNotExist) 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_to_response(invalid_template_path, context) # 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_to_response(invalid_template_path, context) # 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_to_response(invalid_template_path, context) 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, platform_name) # 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, user_certificate, platform_name) # Append badge info _update_badge_context(context, course, user) # Append microsite overrides _update_microsite_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) # FINALLY, render appropriate certificate return _render_certificate_template(request, context, course, user_certificate)
def render_html_view(request, user_id, course_id): """ This public view generates an HTML representation of the specified student's certificate If a certificate is not available, we display a "Sorry!" screen instead """ # Create the initial view context, bootstrapping with Django settings and passed-in values context = {} context['platform_name'] = microsite.get_value("platform_name", settings.PLATFORM_NAME) context['course_id'] = course_id # Update the view context with the default ConfigurationModel settings configuration = CertificateHtmlViewConfiguration.get_config() # if we are in a microsite, then let's first see if there is an override # section in our config config_key = microsite.get_value('microsite_config_key', 'default') # if there is no special microsite override, then let's use default if config_key not in configuration: config_key = 'default' context.update(configuration.get(config_key, {})) # Translators: 'All rights reserved' is a legal term used in copyrighting to protect published content reserved = _("All rights reserved") context['copyright_text'] = '© {year} {platform_name}. {reserved}.'.format( year=settings.COPYRIGHT_YEAR, platform_name=context.get('platform_name'), reserved=reserved ) # Translators: This text is bound to the HTML 'title' element of the page and appears # in the browser title bar when a requested certificate is not found or recognized context['document_title'] = _("Invalid Certificate") # Translators: The & characters represent an ampersand character and can be ignored context['company_tos_urltext'] = _("Terms of Service & Honor Code") # Translators: A 'Privacy Policy' is a legal document/statement describing a website's use of personal information context['company_privacy_urltext'] = _("Privacy Policy") # Translators: This line appears as a byline to a header image and describes the purpose of the page context['logo_subtitle'] = _("Certificate Validation") invalid_template_path = 'certificates/invalid.html' # Kick the user back to the "Invalid" screen if the feature is disabled if not has_html_certificates_enabled(course_id): return render_to_response(invalid_template_path, context) # Load the core building blocks for the view context try: course_key = CourseKey.from_string(course_id) user = User.objects.get(id=user_id) course = modulestore().get_course(course_key) if not course: raise CourseDoesNotExist # Attempt to load the user's generated certificate data user_certificate = GeneratedCertificate.objects.get( user=user, course_id=course_key ) # If there's no generated certificate data for this user, we need to see if we're in 'preview' mode... # If we are, we'll need to create a mock version of the user_certificate container for previewing except GeneratedCertificate.DoesNotExist: if request.GET.get('preview', None): user_certificate = GeneratedCertificate( mode=request.GET.get('preview'), verify_uuid=unicode(uuid4().hex), modified_date=datetime.now().date() ) else: return render_to_response(invalid_template_path, context) # For any other expected exceptions, kick the user back to the "Invalid" screen except (InvalidKeyError, CourseDoesNotExist, User.DoesNotExist): return render_to_response(invalid_template_path, context) # Badge Request Event Tracking Logic if 'evidence_visit' in request.GET: try: badge = BadgeAssertion.objects.get(user=user, course_id=course_key) tracker.emit( 'edx.badge.assertion.evidence_visited', { 'user_id': user.id, 'course_id': unicode(course_key), 'enrollment_mode': badge.mode, 'assertion_id': badge.id, 'assertion_image_url': badge.data['image'], 'assertion_json_url': badge.data['json']['id'], 'issuer': badge.data['issuer'], } ) except BadgeAssertion.DoesNotExist: log.warn( "Could not find badge for %s on course %s.", user.id, course_key, ) # Okay, now we have all of the pieces, time to put everything together # 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, request.GET.get('preview')) if active_configuration is None: return render_to_response(invalid_template_path, context) else: 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/Override the existing view context values with request-time values _update_certificate_context(context, course, user, user_certificate) # 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.enabled: context['linked_in_url'] = linkedin_config.add_to_profile_url( course.id, course.display_name, user_certificate.mode, request.build_absolute_uri(get_certificate_url( user_id=user.id, course_id=unicode(course.id) )) ) # Microsites will need to be able to override any hard coded # content that was put into the context in the # _update_certificate_context() call above. For example the # 'company_about_description' talks about edX, which we most likely # do not want to keep in a microsite # # So we need to re-apply any configuration/content that # we are sourceing from the database. This is somewhat duplicative of # the code at the beginning of this method, but we # need the configuration at the top as some error code paths # require that to be set up early on in the pipeline # microsite_config_key = microsite.get_value('microsite_config_key') if microsite_config_key: context.update(configuration.get(microsite_config_key, {})) # track certificate evidence_visited event for analytics when certificate_user and accessing_user are different if request.user and request.user.id != user.id: emit_certificate_event('evidence_visited', user, course_id, course, { 'certificate_id': user_certificate.verify_uuid, 'enrollment_mode': user_certificate.mode, 'social_network': CertificateSocialNetworks.linkedin }) # Append/Override the existing view context values with any course-specific static values from Advanced Settings context.update(course.cert_html_view_overrides) # FINALLY, generate and send the output the client if settings.FEATURES.get('CUSTOM_CERTIFICATE_TEMPLATES_ENABLED', False): custom_template = get_certificate_template(course_key, user_certificate.mode) if custom_template: template = Template(custom_template) context = RequestContext(request, context) return HttpResponse(template.render(context)) return render_to_response("certificates/valid.html", context)
class CertificateHtmlViewConfigurationTest(TestCase): """ Test the CertificateHtmlViewConfiguration model. """ def setUp(self): super(CertificateHtmlViewConfigurationTest, self).setUp() self.configuration_string = """{ "default": { "url": "http://www.edx.org", "logo_src": "http://www.edx.org/static/images/logo.png" }, "honor": { "logo_src": "http://www.edx.org/static/images/honor-logo.png" } }""" self.config = CertificateHtmlViewConfiguration( configuration=self.configuration_string) def test_create(self): """ Tests creation of configuration. """ self.config.save() self.assertEquals(self.config.configuration, self.configuration_string) def test_clean_bad_json(self): """ Tests if bad JSON string was given. """ self.config = CertificateHtmlViewConfiguration( configuration='{"bad":"test"') self.assertRaises(ValidationError, self.config.clean) def test_get(self): """ Tests get configuration from saved string. """ self.config.enabled = True self.config.save() expected_config = { "default": { "url": "http://www.edx.org", "logo_src": "http://www.edx.org/static/images/logo.png" }, "honor": { "logo_src": "http://www.edx.org/static/images/honor-logo.png" } } self.assertEquals(self.config.get_config(), expected_config) def test_get_not_enabled_returns_blank(self): """ Tests get configuration that is not enabled. """ self.config.enabled = False self.config.save() self.assertEquals(len(self.config.get_config()), 0) @override_settings(FEATURES=FEATURES_INVALID_FILE_PATH) def test_get_no_database_no_file(self): """ Tests get configuration that is not enabled. """ self.config.configuration = '' self.config.save() self.assertEquals(self.config.get_config(), {})
def render_html_view(request): """ This view generates an HTML representation of the specified student's certificate If a certificate is not available, we display a "Sorry!" screen instead """ # Initialize the template context and bootstrap with default values from configuration context = {} configuration = CertificateHtmlViewConfiguration.get_config() context = configuration.get('default', {}) invalid_template_path = 'certificates/invalid.html' # Translators: This text is bound to the HTML 'title' element of the page and appears # in the browser title bar when a requested certificate is not found or recognized context['document_title'] = _("Invalid Certificate") # Feature Flag check if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False): return render_to_response(invalid_template_path, context) course_id = request.GET.get('course', None) context['course'] = course_id if not course_id: return render_to_response(invalid_template_path, context) # Course Lookup try: course_key = CourseKey.from_string(course_id) except InvalidKeyError: return render_to_response(invalid_template_path, context) course = modulestore().get_course(course_key) if not course: return render_to_response(invalid_template_path, context) # Certificate Lookup try: certificate = GeneratedCertificate.objects.get(user=request.user, course_id=course_key) except GeneratedCertificate.DoesNotExist: return render_to_response(invalid_template_path, context) # Override the defaults with any mode-specific static values context.update(configuration.get(certificate.mode, {})) # Override further with any course-specific static values context.update(course.cert_html_view_overrides) # Populate dynamic output values using the course/certificate data loaded above user_fullname = request.user.profile.name platform_name = context.get('platform_name') context['accomplishment_copy_name'] = user_fullname context['accomplishment_copy_course_org'] = course.org context['accomplishment_copy_course_name'] = course.display_name context['certificate_id_number'] = certificate.verify_uuid context['certificate_verify_url'] = "{prefix}{uuid}{suffix}".format( prefix=context.get('certificate_verify_url_prefix'), uuid=certificate.verify_uuid, suffix=context.get('certificate_verify_url_suffix')) context['logo_alt'] = platform_name accd_course_org_html = '<span class="detail--xuniversity">{partner_name}</span>'.format( partner_name=course.org) accd_platform_name_html = '<span class="detail--company">{platform_name}</span>'.format( platform_name=platform_name) # Translators: This line appears on the certificate after the name of a course, and provides more # information about the organizations providing the course material to platform users context['accomplishment_copy_course_description'] = _( 'a course of study offered by {partner_name}, ' 'through {platform_name}.').format( partner_name=accd_course_org_html, platform_name=accd_platform_name_html) context['accomplishment_more_title'] = _( "More Information About {user_name}'s Certificate:").format( user_name=user_fullname) # Translators: This line appears on the page just before the generation date for the certificate context['certificate_date_issued_title'] = _("Issued On:") # Translators: The format of the date includes the full name of the month context['certificate_date_issued'] = _('{month} {day}, {year}').format( month=certificate.modified_date.strftime("%B"), day=certificate.modified_date.day, year=certificate.modified_date.year) # Translators: The Certificate ID Number is an alphanumeric value unique to each individual certificate context['certificate_id_number_title'] = _('Certificate ID Number') context['certificate_info_title'] = _('About {platform_name} Certificates' ).format(platform_name=platform_name) # Translators: This text describes the purpose (and therefore, value) of a course certificate # 'verifying your identity' refers to the process for establishing the authenticity of the student context['certificate_info_description'] = _( "{platform_name} acknowledges achievements through certificates, which " "are awarded for various activities {platform_name} students complete " "under the <a href='{tos_url}'>{platform_name} Honor Code</a>. Some " "certificates require completing additional steps, such as " "<a href='{verified_cert_url}'> verifying your identity</a>.").format( platform_name=platform_name, tos_url=context.get('company_tos_url'), verified_cert_url=context.get('company_verified_certificate_url')) # Translators: Certificate Types correspond to the different enrollment options available for a given course context['certificate_type_title'] = _( '{certificate_type} Certfificate').format( certificate_type=context.get('certificate_type')) context['certificate_verify_title'] = _( "How {platform_name} Validates Student Certificates").format( platform_name=platform_name) # Translators: This text describes the validation mechanism for a certificate file (known as GPG security) context['certificate_verify_description'] = _( 'Certificates issued by {platform_name} are signed by a gpg key so ' 'that they can be validated independently by anyone with the ' '{platform_name} public key. For independent verification, ' '{platform_name} uses what is called a ' '"detached signature""".').format(platform_name=platform_name) context['certificate_verify_urltext'] = _( "Validate this certificate for yourself") # Translators: This text describes (at a high level) the mission and charter the edX platform and organization context['company_about_description'] = _( "{platform_name} offers interactive online classes and MOOCs from the " "world's best universities, including MIT, Harvard, Berkeley, University " "of Texas, and many others. {platform_name} is a non-profit online " "initiative created by founding partners Harvard and MIT.").format( platform_name=platform_name) context['company_about_title'] = _("About {platform_name}").format( platform_name=platform_name) context['company_about_urltext'] = _( "Learn more about {platform_name}").format(platform_name=platform_name) context['company_courselist_urltext'] = _( "Learn with {platform_name}").format(platform_name=platform_name) context['company_careers_urltext'] = _("Work at {platform_name}").format( platform_name=platform_name) context['company_contact_urltext'] = _("Contact {platform_name}").format( platform_name=platform_name) context['company_privacy_urltext'] = _("Privacy Policy") context['company_tos_urltext'] = _("Terms of Service & Honor Code") # Translators: This text appears near the top of the certficate and describes the guarantee provided by edX context['document_banner'] = _( "{platform_name} acknowledges the following student accomplishment" ).format(platform_name=platform_name) context['logo_subtitle'] = _("Certificate Validation") if certificate.mode == 'honor': # Translators: This text describes the 'Honor' course certificate type. context['certificate_type_description'] = _( "An {cert_type} Certificate signifies that an {platform_name} " "learner has agreed to abide by {platform_name}'s honor code and " "completed all of the required tasks for this course under its " "guidelines.").format(cert_type=context.get('certificate_type'), platform_name=platform_name) elif certificate.mode == 'verified': # Translators: This text describes the 'ID Verified' course certificate type, which is a higher level of # verification offered by edX. This type of verification is useful for professional education/certifications context['certificate_type_description'] = _( "An {cert_type} Certificate signifies that an {platform_name} " "learner has agreed to abide by {platform_name}'s honor code and " "completed all of the required tasks for this course under its " "guidelines, as well as having their photo ID checked to verify " "their identity.").format( cert_type=context.get('certificate_type'), platform_name=platform_name) elif certificate.mode == 'xseries': # Translators: This text describes the 'XSeries' course certificate type. An XSeries is a collection of # courses related to each other in a meaningful way, such as a specific topic or theme, or even an organization context['certificate_type_description'] = _( "An {cert_type} Certificate demonstrates a high level of " "achievement in a program of study, and includes verification of " "the student's identity.").format( cert_type=context.get('certificate_type')) # Translators: This is the copyright line which appears at the bottom of the certificate page/screen context['copyright_text'] = _( '© {year} {platform_name}. All rights reserved.').format( year=datetime.now().year, platform_name=platform_name) # 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_name} {course_number}').format( platform_name=platform_name, user_name=user_fullname, partner_name=course.org, course_number=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'] = _( "Valid {partner_name} {course_number} Certificate | {platform_name}" ).format(partner_name=course.org, course_number=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 a {platform_name} {certificate_type} " "Certificate of Completion in ").format( platform_name=platform_name, certificate_type=context.get("certificate_type")) return render_to_response("certificates/valid.html", context)