webview.py 31.4 KB
Newer Older
1
# pylint: disable=bad-continuation
2 3 4
"""
Certificate HTML webview.
"""
5
import logging
6
import urllib
7 8
from datetime import datetime
from uuid import uuid4
9

10
import pytz
11
from django.conf import settings
12
from django.contrib.auth.models import User
13
from django.http import Http404, HttpResponse
14
from django.template import RequestContext
15
from django.utils.encoding import smart_str
16
from django.utils import translation
17
from eventtracking import tracker
18 19
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
20

21
from badges.events.course_complete import get_completion_badge
22
from badges.utils import badges_enabled
23 24 25 26 27 28
from certificates.api import (
    emit_certificate_event,
    get_active_web_certificate,
    get_certificate_footer_context,
    get_certificate_header_context,
    get_certificate_template,
29
    get_certificate_url
30 31
)
from certificates.models import (
32
    CertificateGenerationCourseSetting,
33 34 35 36 37
    CertificateHtmlViewConfiguration,
    CertificateSocialNetworks,
    CertificateStatuses,
    GeneratedCertificate
)
38
from courseware.access import has_access
39
from courseware.courses import get_course_by_id
40
from edxmako.shortcuts import render_to_response
41
from edxmako.template import Template
42
from openedx.core.djangoapps.catalog.utils import get_course_run_details
43
from openedx.core.djangoapps.lang_pref.api import get_closest_released_language
44
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
45
from openedx.core.lib.courses import course_image_url
46
from openedx.core.djangoapps.certificates.api import display_date_for_certificate, certificates_viewable_for_course
47 48
from student.models import LinkedInAddToProfileConfiguration
from util import organizations_helpers as organization_api
49
from util.date_utils import strftime_localized
50
from util.views import handle_500
51

52

53
log = logging.getLogger(__name__)
54 55 56 57
_ = translation.ugettext


INVALID_CERTIFICATE_TEMPLATE_PATH = 'certificates/invalid.html'
58

John Jarvis committed
59

60
def get_certificate_description(mode, certificate_type, platform_name):
61
    """
62
    :return certificate_type_description on the basis of current mode
63
    """
64 65 66
    certificate_type_description = None
    if mode == 'honor':
        # Translators:  This text describes the 'Honor' course certificate type.
67 68 69
        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 "
70 71 72 73 74
                                         "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
75 76 77 78 79 80 81
        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. A {cert_type} certificate also indicates that the "
                                         "identity of the learner has been checked and "
                                         "is valid.").format(cert_type=certificate_type,
                                                             platform_name=platform_name)
82 83 84
    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
85
        certificate_type_description = _("An {cert_type} certificate demonstrates a high level of "
86 87 88
                                         "achievement in a program of study, and includes verification of "
                                         "the student's identity.").format(cert_type=certificate_type)
    return certificate_type_description
89 90


91
def _update_certificate_context(context, course, user_certificate, platform_name):
92 93 94 95
    """
    Build up the certificate web view context using the provided values
    (Helper method to keep the view clean)
    """
96
    # Populate dynamic output values using the course/certificate data loaded above
97 98 99 100
    certificate_type = context.get('certificate_type')

    # Override the defaults with any mode-specific static values
    context['certificate_id_number'] = user_certificate.verify_uuid
101 102
    context['certificate_verify_url'] = "{prefix}{uuid}{suffix}".format(
        prefix=context.get('certificate_verify_url_prefix'),
103
        uuid=user_certificate.verify_uuid,
104 105
        suffix=context.get('certificate_verify_url_suffix')
    )
106 107

    # Translators:  The format of the date includes the full name of the month
108
    date = display_date_for_certificate(course, user_certificate)
109
    context['certificate_date_issued'] = _('{month} {day}, {year}').format(
110 111 112
        month=strftime_localized(date, "%B"),
        day=date.day,
        year=date.year
113
    )
114

115 116 117 118 119 120 121
    # 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']
122 123
    )

124 125 126 127 128
    # 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
129 130
    )

131 132 133
    # 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 "
134
                                                        "awarded this {platform_name} {certificate_type} "
135 136 137
                                                        "Certificate of Completion in ").format(
        platform_name=platform_name,
        certificate_type=context.get("certificate_type"))
138

139 140 141
    certificate_type_description = get_certificate_description(user_certificate.mode, certificate_type, platform_name)
    if certificate_type_description:
        context['certificate_type_description'] = certificate_type_description
142 143

    # Translators: This text describes the purpose (and therefore, value) of a course certificate
144 145 146
    context['certificate_info_description'] = _("{platform_name} acknowledges achievements through "
                                                "certificates, which are awarded for course activities "
                                                "that {platform_name} students complete.").format(
147 148
        platform_name=platform_name,
        tos_url=context.get('company_tos_url'),
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
        verified_cert_url=context.get('company_verified_certificate_url'))


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")
165
    context['copyright_text'] = u'© {year} {platform_name}. {reserved}.'.format(
166
        year=datetime.now(pytz.timezone(settings.TIME_ZONE)).year,
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
        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")

    # 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")

    # 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
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
    )

    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
213
    context['company_about_description'] = _("{platform_name} offers interactive online classes and MOOCs.").format(
214
        platform_name=platform_name)
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231

    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 certficate and describes the guarantee provided by edX
    context['document_banner'] = _("{platform_name} acknowledges the following student accomplishment").format(
        platform_name=platform_name
    )


232
def _update_course_context(request, context, course, course_key, platform_name):
233 234 235 236 237 238 239 240 241 242 243 244
    """
    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
    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}, '
245 246
                                                              'an online learning initiative of '
                                                              '{partner_long_name}.').format(
247 248 249 250 251
            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
252 253
        context['accomplishment_copy_course_description'] = _('a course of study offered by '
                                                              '{partner_short_name}.').format(
254 255
            partner_short_name=context['organization_short_name'],
            platform_name=platform_name)
256 257


258 259 260 261
def _update_social_context(request, context, course, user, user_certificate, platform_name):
    """
    Updates context dictionary with info required for social sharing.
    """
262
    share_settings = configuration_helpers.get_value("SOCIAL_SHARING_SETTINGS", settings.SOCIAL_SHARING_SETTINGS)
263
    context['facebook_share_enabled'] = share_settings.get('CERTIFICATE_FACEBOOK', False)
264
    context['facebook_app_id'] = configuration_helpers.get_value("FACEBOOK_APP_ID", settings.FACEBOOK_APP_ID)
265 266 267 268 269 270 271 272 273 274
    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',
275
        _("I completed a course at {platform_name}. Take a look at my certificate.").format(
276 277
            platform_name=platform_name
        )
278 279
    )

280
    share_url = request.build_absolute_uri(get_certificate_url(course_id=course.id, uuid=user_certificate.verify_uuid))
281 282 283 284 285 286 287 288 289 290 291 292 293
    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.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()
294 295
    linkedin_share_enabled = share_settings.get('CERTIFICATE_LINKEDIN', linkedin_config.enabled)
    if linkedin_share_enabled:
296 297 298 299
        context['linked_in_url'] = linkedin_config.add_to_profile_url(
            course.id,
            course.display_name,
            user_certificate.mode,
300
            smart_str(share_url)
301 302 303 304 305 306 307 308 309 310 311 312 313
        )


def _update_context_with_user_info(context, user, user_certificate):
    """
    Updates context dictionary with user related info.
    """
    user_fullname = user.profile.name
    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
314

315 316 317
    context['accomplishment_more_title'] = _("More Information About {user_name}'s Certificate:").format(
        user_name=user_fullname
    )
318
    # Translators: This line is displayed to a user who has completed a course and achieved a certification
319
    context['accomplishment_banner_opening'] = _("{fullname}, you earned a certificate!").format(
320 321 322 323
        fullname=user_fullname
    )

    # Translators: This line congratulates the user and instructs them to share their accomplishment on social networks
324 325
    context['accomplishment_banner_congrats'] = _("Congratulations! This page summarizes what "
                                                  "you accomplished. Show it off to family, friends, and colleagues "
326 327 328 329 330 331 332 333
                                                  "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
    )


334
def _get_user_certificate(request, user, course_key, course, preview_mode=None):
335
    """
336 337 338
    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.
339
    """
340 341 342 343
    user_certificate = None
    if preview_mode:
        # certificate is being previewed from studio
        if has_access(request.user, 'instructor', course) or has_access(request.user, 'staff', course):
344 345 346 347
            if course.certificate_available_date and not course.self_paced:
                modified_date = course.certificate_available_date
            else:
                modified_date = datetime.now().date()
348 349 350
            user_certificate = GeneratedCertificate(
                mode=preview_mode,
                verify_uuid=unicode(uuid4().hex),
351
                modified_date=modified_date
352
            )
353
    elif certificates_viewable_for_course(course):
354 355
        # certificate is being viewed by learner or public
        try:
356
            user_certificate = GeneratedCertificate.eligible_certificates.get(
357
                user=user,
358 359
                course_id=course_key,
                status=CertificateStatuses.downloadable
360
            )
361 362
        except GeneratedCertificate.DoesNotExist:
            pass
363

364
    return user_certificate
365 366


367 368 369 370 371
def _track_certificate_events(request, context, course, user, user_certificate):
    """
    Tracks web certificate view related events.
    """
    # Badge Request Event Tracking Logic
372 373 374 375
    course_key = course.location.course_key

    if 'evidence_visit' in request.GET:
        badge_class = get_completion_badge(course_key, user)
376 377 378 379 380
        if not badge_class:
            log.warning('Visit to evidence URL for badge, but badges not configured for course "%s"', course_key)
            badges = []
        else:
            badges = badge_class.get_for_user(user)
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
        if badges:
            # There should only ever be one of these.
            badge = badges[0]
            tracker.emit(
                'edx.badge.assertion.evidence_visited',
                {
                    'badge_name': badge.badge_class.display_name,
                    'badge_slug': badge.badge_class.slug,
                    'badge_generator': badge.backend,
                    'issuing_component': badge.badge_class.issuing_component,
                    'user_id': user.id,
                    'course_id': unicode(course_key),
                    'enrollment_mode': badge.badge_class.mode,
                    'assertion_id': badge.id,
                    'assertion_image_url': badge.image_url,
                    'assertion_json_url': badge.assertion_url,
                    'issuer': badge.data.get('issuer'),
                }
            )
        else:
            log.warn(
                "Could not find badge for %s on course %s.",
                user.id,
                course_key,
            )
asadiqbal committed
406

407 408
    # track certificate evidence_visited event for analytics when certificate_user and accessing_user are different
    if request.user and request.user.id != user.id:
409
        emit_certificate_event('evidence_visited', user, unicode(course.id), course, {
410 411 412 413 414
            'certificate_id': user_certificate.verify_uuid,
            'enrollment_mode': user_certificate.mode,
            'social_network': CertificateSocialNetworks.linkedin
        })

415

416
def _update_configuration_context(context, configuration):
417
    """
418
    Site Configuration will need to be able to override any hard coded
419 420 421
    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
422
    do not want to keep in configurations.
423 424 425 426 427 428 429
    So we need to re-apply any configuration/content that
    we are sourcing 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
    """

430 431 432 433
    config_key = configuration_helpers.get_value('domain_prefix')
    config = configuration.get("microsites", {})
    if config_key and config:
        context.update(config.get(config_key, {}))
434 435 436 437 438 439


def _update_badge_context(context, course, user):
    """
    Updates context with badge info.
    """
440
    badge = None
441
    if badges_enabled() and course.issue_badges:
442 443 444
        badges = get_completion_badge(course.location.course_key, user).get_for_user(user)
        if badges:
            badge = badges[0]
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
    context['badge'] = badge


def _update_organization_context(context, course):
    """
    Updates context with organization related info.
    """
    partner_long_name, organization_logo = None, None
    partner_short_name = course.display_organization if course.display_organization else course.org
    organizations = organization_api.get_course_organizations(course_id=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)
        partner_short_name = organization.get('short_name', partner_short_name)
        organization_logo = organization.get('logo', None)

    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


468 469 470 471 472
def render_cert_by_uuid(request, certificate_uuid):
    """
    This public view generates an HTML representation of the specified certificate
    """
    try:
473
        certificate = GeneratedCertificate.eligible_certificates.get(
474 475 476 477 478 479 480 481
            verify_uuid=certificate_uuid,
            status=CertificateStatuses.downloadable
        )
        return render_html_view(request, certificate.user.id, unicode(certificate.course_id))
    except GeneratedCertificate.DoesNotExist:
        raise Http404


482 483 484 485 486 487
@handle_500(
    template_path="certificates/server-error.html",
    test_func=lambda request: request.GET.get('preview', None)
)
def render_html_view(request, user_id, course_id):
    """
488
    This public view generates an HTML representation of the specified user and course
489 490
    If a certificate is not available, we display a "Sorry!" screen instead
    """
491 492 493 494 495
    try:
        user_id = int(user_id)
    except ValueError:
        raise Http404

496
    preview_mode = request.GET.get('preview', None)
497
    platform_name = configuration_helpers.get_value("platform_name", settings.PLATFORM_NAME)
498 499
    configuration = CertificateHtmlViewConfiguration.get_config()

500 501
    # Kick the user back to the "Invalid" screen if the feature is disabled globally
    if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False):
502
        return _render_invalid_certificate(course_id, platform_name, configuration)
503 504 505 506 507

    # Load the course and user objects
    try:
        course_key = CourseKey.from_string(course_id)
        user = User.objects.get(id=user_id)
508
        course = get_course_by_id(course_key)
509

510 511
    # For any course or user exceptions, kick the user back to the "Invalid" screen
    except (InvalidKeyError, User.DoesNotExist, Http404) as exception:
512 513 514 515 516
        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))
517
        return _render_invalid_certificate(course_id, platform_name, configuration)
518

519 520 521 522 523 524 525
    # 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,
        )
526
        return _render_invalid_certificate(course_id, platform_name, configuration)
527

528 529 530
    # Load user's certificate
    user_certificate = _get_user_certificate(request, user, course_key, course, preview_mode)
    if not user_certificate:
531 532 533 534 535
        log.info(
            "Invalid cert: User %d does not have eligible cert for %s.",
            user_id,
            course_id,
        )
536
        return _render_invalid_certificate(course_id, platform_name, configuration)
537 538 539 540 541 542

    # 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:
543 544 545 546 547
        log.info(
            "Invalid cert: course %s does not have an active configuration. User id: %d",
            course_id,
            user_id,
        )
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571
        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}
572

573
        _update_context_with_basic_info(context, course_id, platform_name, configuration)
574

575
        context['certificate_data'] = active_configuration
576

577 578
        # Append/Override the existing view context values with any mode-specific ConfigurationModel values
        context.update(configuration.get(user_certificate.mode, {}))
579

580 581
        # Append organization info
        _update_organization_context(context, course)
582

583 584
        # Append course info
        _update_course_context(request, context, course, course_key, platform_name)
585

586 587
        # Append course run info from discovery
        context.update(catalog_data)
588

589 590
        # Append user info
        _update_context_with_user_info(context, user, user_certificate)
591

592 593
        # Append social sharing info
        _update_social_context(request, context, course, user, user_certificate, platform_name)
594

595 596
        # Append/Override the existing view context values with certificate specific values
        _update_certificate_context(context, course, user_certificate, platform_name)
597

598 599
        # Append badge info
        _update_badge_context(context, course, user)
600

601 602
        # Append site configuration overrides
        _update_configuration_context(context, configuration)
603

604 605 606
        # 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())
607

608 609
        # Append/Override the existing view context values with any course-specific static values from Advanced Settings
        context.update(course.cert_html_view_overrides)
610

611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
        # 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 _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')

    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.
    """
652
    closest_released_language = get_closest_released_language(course_language) if course_language else None
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
    template = get_certificate_template(course_id, course_mode, closest_released_language)

    if template and template.language:
        return (template, closest_released_language)
    elif template:
        return (template, settings.LANGUAGE_CODE)
    else:
        return (None, None)


def _render_invalid_certificate(course_id, platform_name, configuration):
    context = {}
    _update_context_with_basic_info(context, course_id, platform_name, configuration)
    return render_to_response(INVALID_CERTIFICATE_TEMPLATE_PATH, context)


def _render_valid_certificate(request, context, custom_template=None):
    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)