Files
edx-platform/lms/djangoapps/certificates/views/webview.py
Dmytro 9738f61519 fix: "Course org display str" option doesnt influence certificate (#34465)
"Course organization display string" option in Advanced settings doesn't influence certificate
Co-authored-by: Dima Alipov <dimaalipov@MacBook-Pro-Dima.local>
2024-04-23 09:21:32 -04:00

704 lines
33 KiB
Python

"""
Certificate HTML webview.
"""
import logging
import urllib
from datetime import datetime
from uuid import uuid4
import pytz
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.template import RequestContext
from django.utils import translation
from django.utils.encoding import smart_str
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from openedx_filters.learning.filters import CertificateRenderStarted
from organizations import api as organizations_api
from edx_django_utils.plugins import pluggable_override
from common.djangoapps.edxmako.shortcuts import render_to_response
from common.djangoapps.edxmako.template import Template
from common.djangoapps.student.models import LinkedInAddToProfileConfiguration
from common.djangoapps.util.date_utils import strftime_localized
from common.djangoapps.util.views import handle_500
from lms.djangoapps.certificates.api import (
certificates_viewable_for_course,
display_date_for_certificate,
get_active_web_certificate,
get_certificate_footer_context,
get_certificate_header_context,
get_certificate_template
)
from lms.djangoapps.certificates.models import (
CertificateGenerationCourseSetting,
CertificateHtmlViewConfiguration,
CertificateSocialNetworks,
CertificateStatuses,
GeneratedCertificate
)
from lms.djangoapps.certificates.permissions import PREVIEW_CERTIFICATES
from lms.djangoapps.certificates.utils import (
emit_certificate_event,
get_certificate_url,
get_preferred_certificate_name
)
from openedx.core.djangoapps.catalog.api import get_course_run_details
from openedx.core.djangoapps.content.course_overviews.api import get_course_overview_or_none
from openedx.core.djangoapps.lang_pref.api import get_closest_released_language
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.lib.courses import course_image_url
from openedx.core.lib.courses import get_course_by_id
from xmodule.data import CertificatesDisplayBehaviors # lint-amnesty, pylint: disable=wrong-import-order
log = logging.getLogger(__name__)
_ = translation.gettext
INVALID_CERTIFICATE_TEMPLATE_PATH = 'certificates/invalid.html'
def get_certificate_description(mode, certificate_type, platform_name, course_key):
"""
:return certificate_type_description on the basis of current mode
"""
certificate_type_description = None
if mode == 'honor':
# Translators: This text describes the 'Honor' course certificate type.
certificate_type_description = _("An {cert_type} certificate signifies that a "
"learner has agreed to abide by the honor code established by "
"{platform_name} and has completed all of the required tasks for this course "
"under its guidelines.").format(cert_type=certificate_type,
platform_name=platform_name)
elif 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
certificate_type_description = _("A {cert_type} certificate signifies that a "
"learner has agreed to abide by the honor code established by "
"{platform_name} and has completed all of the required tasks for this course "
"under its guidelines. ").format(cert_type=certificate_type,
platform_name=platform_name)
if settings.FEATURES.get('ENABLE_CERTIFICATES_IDV_REQUIREMENT'):
certificate_type_description += _("A {cert_type} certificate also indicates that the "
"identity of the learner has been checked and "
"is valid.").format(cert_type=certificate_type)
elif 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
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=certificate_type)
return certificate_type_description
def _update_certificate_context(context, course, course_overview, 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')
)
# We prefer a CourseOverview for this function because it validates and corrects certificate_available_date
# and certificates_display_behavior values. However, not all certificates are guaranteed to have a CourseOverview
# associated with them, so we fall back on the course in that case. This shouldn't cause a problem because courses
# that are missing CourseOverviews are generally old courses, and thus their display values are no longer relevant
if course_overview:
date = display_date_for_certificate(course_overview, user_certificate)
else:
date = display_date_for_certificate(course, user_certificate)
# Translators: The format of the date includes the full name of the month
context['certificate_date_issued'] = strftime_localized(date, settings.CERTIFICATE_DATE_FORMAT)
# 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, course.location.course_key
)
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,
)
def _update_context_with_basic_info(context, course_id, platform_name, configuration):
"""
Updates context dictionary with basic info required before rendering simplest
certificate templates.
"""
context['platform_name'] = platform_name
context['course_id'] = course_id
# Update the view context with the default ConfigurationModel settings
context.update(configuration.get('default', {}))
# Translators: 'All rights reserved' is a legal term used in copyrighting to protect published content
reserved = _("All rights reserved")
context['copyright_text'] = '&copy; {year} {platform_name}. {reserved}.'.format(
year=datetime.now(pytz.timezone(settings.TIME_ZONE)).year,
platform_name=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")
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")
# Translators: Accomplishments describe the awards/certifications obtained by students on this platform
context['accomplishment_copy_about'] = _('About {platform_name} Accomplishments').format(
platform_name=platform_name
)
# Translators: This line appears on the page just before the generation date for the certificate
context['certificate_date_issued_title'] = _("Issued On:")
# 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
)
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"&quot;".').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.").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)
# Translators: This text appears near the top of the certificate and describes the guarantee provided by edX
context['document_banner'] = _("{platform_name} acknowledges the following student accomplishment").format(
platform_name=platform_name
)
def _update_course_context(request, context, course, platform_name):
"""
Updates context dictionary with course info.
"""
context['full_course_image_url'] = request.build_absolute_uri(course_image_url(course))
course_title_from_cert = context['certificate_data'].get('course_title', '')
accomplishment_copy_course_name = course_title_from_cert if course_title_from_cert else course.display_name
context['accomplishment_copy_course_name'] = accomplishment_copy_course_name
course_number = course.display_coursenumber if course.display_coursenumber else course.number
context['course_number'] = course_number
context['idv_enabled_for_certificates'] = settings.FEATURES.get('ENABLE_CERTIFICATES_IDV_REQUIREMENT')
if context['organization_long_name']:
# Translators: This text represents the description of course
context['accomplishment_copy_course_description'] = _('a course of study offered by {partner_short_name}, '
'an online learning initiative of '
'{partner_long_name}.').format(
partner_short_name=context['organization_short_name'],
partner_long_name=context['organization_long_name'],
platform_name=platform_name)
else:
# Translators: This text represents the description of course
context['accomplishment_copy_course_description'] = _('a course of study offered by '
'{partner_short_name}.').format(
partner_short_name=context['organization_short_name'],
platform_name=platform_name)
def _update_social_context(request, context, course, user_certificate, platform_name):
"""
Updates context dictionary with info required for social sharing.
"""
share_settings = configuration_helpers.get_value("SOCIAL_SHARING_SETTINGS", settings.SOCIAL_SHARING_SETTINGS)
context['facebook_share_enabled'] = share_settings.get('CERTIFICATE_FACEBOOK', False)
context['facebook_app_id'] = configuration_helpers.get_value("FACEBOOK_APP_ID", settings.FACEBOOK_APP_ID)
context['facebook_share_text'] = share_settings.get(
'CERTIFICATE_FACEBOOK_TEXT',
_("I completed the {course_title} course on {platform_name}.").format(
course_title=context['accomplishment_copy_course_name'],
platform_name=platform_name
)
)
context['twitter_share_enabled'] = share_settings.get('CERTIFICATE_TWITTER', False)
context['twitter_share_text'] = share_settings.get(
'CERTIFICATE_TWITTER_TEXT',
_("I completed a course at {platform_name}. Take a look at my certificate.").format(
platform_name=platform_name
)
)
share_url = request.build_absolute_uri(get_certificate_url(course_id=course.id, uuid=user_certificate.verify_uuid))
context['share_url'] = share_url
twitter_url = ''
if context.get('twitter_share_enabled', False):
twitter_url = 'https://twitter.com/intent/tweet?text={twitter_share_text}&url={share_url}'.format(
twitter_share_text=smart_str(context['twitter_share_text']),
share_url=urllib.parse.quote_plus(smart_str(share_url))
)
context['twitter_url'] = twitter_url
context['linked_in_url'] = None
# If enabled, show the LinkedIn "add to profile" button
# Clicking this button sends the user to LinkedIn where they
# can add the certificate information to their profile.
linkedin_config = LinkedInAddToProfileConfiguration.current()
if linkedin_config.is_enabled():
context['linked_in_url'] = linkedin_config.add_to_profile_url(
course.display_name, user_certificate.mode, smart_str(share_url), certificate=user_certificate
)
def _update_context_with_user_info(context, user, user_certificate):
"""
Updates context dictionary with user related info.
"""
user_fullname = get_preferred_certificate_name(user)
context['username'] = user.username
context['course_mode'] = user_certificate.mode
context['accomplishment_user_id'] = user.id
context['accomplishment_copy_name'] = user_fullname
context['accomplishment_copy_username'] = user.username
context['accomplishment_more_title'] = _("More Information About {user_name}'s Certificate:").format(
user_name=user_fullname
)
# Translators: This line is displayed to a user who has completed a course and achieved a certification
context['accomplishment_banner_opening'] = _("{fullname}, you earned a certificate!").format(
fullname=user_fullname
)
# Translators: This line congratulates the user and instructs them to share their accomplishment on social networks
context['accomplishment_banner_congrats'] = _("Congratulations! This page summarizes what "
"you accomplished. Show it off to family, friends, and colleagues "
"in your social and professional networks.")
# Translators: This line leads the reader to understand more about the certificate that a student has been awarded
context['accomplishment_copy_more_about'] = _("More about {fullname}'s accomplishment").format(
fullname=user_fullname
)
def _get_user_certificate(request, user, course_key, course_overview, preview_mode=None):
"""
Retrieves user's certificate from db. Creates one in case of preview mode.
Returns None if there is no certificate generated for given user
otherwise returns `GeneratedCertificate` instance.
We use the course_overview instead of the course block here, so we get the certificate_available_date and
certificates_display_behavior validation logic, rather than the raw data from the course block.
"""
user_certificate = None
if preview_mode:
# certificate is being previewed from studio
if request.user.has_perm(PREVIEW_CERTIFICATES, course_overview):
if not settings.FEATURES.get("ENABLE_V2_CERT_DISPLAY_SETTINGS"):
if course_overview.certificate_available_date and not course_overview.self_paced:
modified_date = course_overview.certificate_available_date
else:
modified_date = datetime.now().date()
else:
if (
course_overview.certificates_display_behavior == CertificatesDisplayBehaviors.END_WITH_DATE
and course_overview.certificate_available_date
and not course_overview.self_paced
):
modified_date = course_overview.certificate_available_date
elif course_overview.certificates_display_behavior == CertificatesDisplayBehaviors.END:
modified_date = course_overview.end
else:
modified_date = datetime.now().date()
user_certificate = GeneratedCertificate(
mode=preview_mode,
verify_uuid=str(uuid4().hex),
modified_date=modified_date,
created_date=datetime.now().date(),
)
elif certificates_viewable_for_course(course_overview):
# certificate is being viewed by learner or public
try:
user_certificate = GeneratedCertificate.eligible_certificates.get(
user=user,
course_id=course_key,
status=CertificateStatuses.downloadable
)
except GeneratedCertificate.DoesNotExist:
pass
return user_certificate
def _track_certificate_events(request, course, user, user_certificate):
"""
Tracks web certificate view related events.
"""
# 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, str(course.id), event_data={
'certificate_id': user_certificate.verify_uuid,
'enrollment_mode': user_certificate.mode,
'social_network': CertificateSocialNetworks.linkedin
})
def _update_organization_context(context, course):
"""
Updates context with organization related info.
"""
partner_long_name, organization_logo = None, None
course_org_display = course.display_organization
organizations = organizations_api.get_course_organizations(course_key=course.id)
if organizations:
# TODO Need to add support for multiple organizations, Currently we are interested in the first one.
organization = organizations[0]
partner_long_name = organization.get('name', partner_long_name)
course_org_display = course_org_display or organization.get('short_name')
organization_logo = organization.get('logo', None)
partner_short_name = course_org_display or course.org
context['organization_long_name'] = partner_long_name
context['organization_short_name'] = partner_short_name
context['accomplishment_copy_course_org'] = partner_short_name
context['organization_logo'] = organization_logo
def unsupported_url(request, user_id, course_id): # pylint: disable=unused-argument
"""
This view returns the un-supported url page aimed to let the user aware that
url is no longer supported
"""
platform_name = configuration_helpers.get_value("platform_name", settings.PLATFORM_NAME)
configuration = CertificateHtmlViewConfiguration.get_config()
return _render_invalid_certificate(
request, course_id, platform_name, configuration, cert_path='certificates/url_unsupported.html'
)
@login_required
def render_preview_certificate(request, course_id):
"""
This view renders the course certificate in preview mode
"""
return render_html_view(request, str(course_id))
def render_cert_by_uuid(request, certificate_uuid):
"""
This public view generates an HTML representation of the specified certificate
"""
try:
certificate = GeneratedCertificate.eligible_certificates.get(
verify_uuid=certificate_uuid,
status=CertificateStatuses.downloadable
)
return render_html_view(request, str(certificate.course_id), certificate)
except GeneratedCertificate.DoesNotExist as e:
raise Http404 from e
@handle_500(
template_path="certificates/server-error.html",
test_func=lambda request: request.GET.get('preview', None)
)
@pluggable_override('OVERRIDE_RENDER_CERTIFICATE_VIEW')
def render_html_view(request, course_id, certificate=None): # pylint: disable=too-many-statements
"""
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
It can be overridden by setting `OVERRIDE_RENDER_CERTIFICATE_VIEW` to an alternative implementation.
"""
user = certificate.user if certificate else request.user
user_id = user.id
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(request, course_id, platform_name, configuration)
# Load the course and user objects
try:
course_key = CourseKey.from_string(course_id)
course = get_course_by_id(course_key)
# For any course or user exceptions, kick the user back to the "Invalid" screen
except (InvalidKeyError, Http404) as exception:
error_str = (
"Invalid cert: error finding course %s "
"Specific error: %s"
)
log.info(error_str, course_id, str(exception))
return _render_invalid_certificate(request, course_id, platform_name, configuration)
course_overview = get_course_overview_or_none(course_key)
# 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(request, course_id, platform_name, configuration)
# Load user's certificate
user_certificate = _get_user_certificate(request, user, course_key, course_overview, 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(request, 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(request, 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):
log.info("Custom certificate for course %s", course_id)
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
log.info(
"certificate language is: %s for the course: %s",
certificate_language,
course_key
)
# 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, 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_certificate, platform_name)
# Append/Override the existing view context values with certificate specific values
_update_certificate_context(context, course, course_overview, user_certificate, platform_name)
# 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, course, user, user_certificate)
try:
# .. filter_implemented_name: CertificateRenderStarted
# .. filter_type: org.openedx.learning.certificate.render.started.v1
context, custom_template = CertificateRenderStarted.run_filter(
context=context,
custom_template=custom_template,
)
except CertificateRenderStarted.RenderAlternativeInvalidCertificate as exc:
response = _render_invalid_certificate(
request,
course_id,
platform_name,
configuration,
cert_path=exc.template_name or INVALID_CERTIFICATE_TEMPLATE_PATH,
)
except CertificateRenderStarted.RedirectToPage as exc:
response = HttpResponseRedirect(exc.redirect_to)
except CertificateRenderStarted.RenderCustomResponse as exc:
response = exc.response
else:
response = _render_valid_certificate(request, context, custom_template)
# Render the certificate
return response
def _get_catalog_data_for_course(course_key):
"""
Retrieve data from the Discovery service necessary for rendering a certificate for a specific course.
"""
course_certificate_settings = CertificateGenerationCourseSetting.get(course_key)
if not course_certificate_settings:
return {}
catalog_data = {}
course_run_fields = []
if course_certificate_settings.language_specific_templates_enabled:
course_run_fields.append('content_language')
if course_certificate_settings.include_hours_of_effort:
course_run_fields.extend(['weeks_to_complete', 'max_effort'])
if course_run_fields:
course_run_data = get_course_run_details(course_key, course_run_fields)
if course_run_data.get('weeks_to_complete') and course_run_data.get('max_effort'):
try:
weeks_to_complete = int(course_run_data['weeks_to_complete'])
max_effort = int(course_run_data['max_effort'])
catalog_data['hours_of_effort'] = weeks_to_complete * max_effort
except ValueError:
log.exception('Error occurred while parsing course run details')
catalog_data['content_language'] = course_run_data.get('content_language')
log.info(
"catalog data received for course: %s is : %s",
course_key,
catalog_data,
)
return catalog_data
def _get_custom_template_and_language(course_id, course_mode, course_language):
"""
Return the custom certificate template, if any, that should be rendered for the provided course/mode/language
combination, along with the language that should be used to render that template.
"""
closest_released_language = get_closest_released_language(course_language) if course_language else None
log.info(
"closest released language for %s is %s and course language was: %s",
course_id,
closest_released_language,
course_language
)
template = get_certificate_template(course_id, course_mode, closest_released_language)
if template and template.language:
return (template, closest_released_language)
elif template:
user_language = translation.get_language()
return (template, user_language)
else:
return (None, None)
def _render_invalid_certificate(request, course_id, platform_name, configuration,
cert_path=INVALID_CERTIFICATE_TEMPLATE_PATH):
"""
Renders the invalid certificate view with default header and footer.
"""
context = {}
_update_context_with_basic_info(context, course_id, platform_name, 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())
return render_to_response(cert_path, context)
def _render_valid_certificate(request, context, custom_template=None):
"""
Renders certificate
"""
if custom_template:
template = Template(
custom_template.template,
output_encoding='utf-8',
input_encoding='utf-8',
default_filters=['decode.utf8'],
encoding_errors='replace',
)
context = RequestContext(request, context)
return HttpResponse(template.render(context))
else:
return render_to_response("certificates/valid.html", context)