Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
7e52af97
Commit
7e52af97
authored
Nov 02, 2017
by
Anthony Mangano
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Render certificate templates in the correct language
LEARNER-2921
parent
a285ed08
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
166 additions
and
83 deletions
+166
-83
lms/djangoapps/certificates/api.py
+7
-2
lms/djangoapps/certificates/tests/test_webview_views.py
+11
-0
lms/djangoapps/certificates/views/webview.py
+148
-81
No files found.
lms/djangoapps/certificates/api.py
View file @
7e52af97
...
...
@@ -503,7 +503,7 @@ def get_certificate_template(course_key, mode, language):
mode
=
mode
)
template
=
get_language_specific_template_or_default
(
language
,
mode_templates
)
return
template
.
template
if
template
else
None
return
template
if
template
else
None
def
get_language_specific_template_or_default
(
language
,
templates
):
...
...
@@ -540,7 +540,12 @@ def _get_two_letter_language_code(language_code):
Shortens language to only first two characters (e.g. es-419 becomes es)
This is needed because Catalog returns locale language which is not always a 2 letter code.
"""
return
language_code
[:
2
]
if
language_code
else
None
if
language_code
is
None
:
return
None
elif
language_code
==
''
:
return
''
else
:
return
language_code
[:
2
]
def
emit_certificate_event
(
event_name
,
user
,
course_id
,
course
=
None
,
event_data
=
None
):
...
...
lms/djangoapps/certificates/tests/test_webview_views.py
View file @
7e52af97
...
...
@@ -41,6 +41,7 @@ from lms.djangoapps.badges.tests.factories import (
)
from
lms.djangoapps.grades.tests.utils
import
mock_passing_grade
from
openedx.core.djangoapps.certificates.config
import
waffle
from
openedx.core.djangoapps.dark_lang.models
import
DarkLangConfig
from
openedx.core.lib.tests.assertions.events
import
assert_event_matches
from
student.roles
import
CourseStaffRole
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
...
...
@@ -1079,6 +1080,8 @@ class CertificatesViewsTests(CommonCertificatesTestCase):
Tests custom template search and rendering.
This test should check template matching when org={org}, course={course}, mode={mode}.
"""
DarkLangConfig
(
released_languages
=
'es-419, fr'
,
changed_by
=
self
.
user
,
enabled
=
True
)
.
save
()
right_language
=
'es'
wrong_language
=
'fr'
mock_get_org_id
.
return_value
=
1
...
...
@@ -1137,6 +1140,8 @@ class CertificatesViewsTests(CommonCertificatesTestCase):
match org and mode.
This test should check template matching when org={org}, course=Null, mode={mode}.
"""
DarkLangConfig
(
released_languages
=
'es-419, fr'
,
changed_by
=
self
.
user
,
enabled
=
True
)
.
save
()
right_language
=
'es'
wrong_language
=
'fr'
mock_get_org_id
.
return_value
=
1
...
...
@@ -1193,6 +1198,8 @@ class CertificatesViewsTests(CommonCertificatesTestCase):
Tests custom template search when we have a single template for a organization.
This test should check template matching when org={org}, course=Null, mode=null.
"""
DarkLangConfig
(
released_languages
=
'es-419, fr'
,
changed_by
=
self
.
user
,
enabled
=
True
)
.
save
()
right_language
=
'es'
wrong_language
=
'fr'
mock_get_org_id
.
return_value
=
1
...
...
@@ -1248,6 +1255,8 @@ class CertificatesViewsTests(CommonCertificatesTestCase):
Tests custom template search if we have a single template for a course mode.
This test should check template matching when org=null, course=Null, mode={mode}.
"""
DarkLangConfig
(
released_languages
=
'es-419, fr'
,
changed_by
=
self
.
user
,
enabled
=
True
)
.
save
()
right_language
=
'es'
wrong_language
=
'fr'
mock_get_org_id
.
return_value
=
1
...
...
@@ -1303,6 +1312,8 @@ class CertificatesViewsTests(CommonCertificatesTestCase):
Tests custom template search if we have a single template for a course mode.
This test should check template matching when org=null, course=Null, mode={mode}.
"""
DarkLangConfig
(
released_languages
=
'es-419, fr'
,
changed_by
=
self
.
user
,
enabled
=
True
)
.
save
()
right_language
=
'es'
wrong_language
=
'fr'
mock_get_org_id
.
return_value
=
1
...
...
lms/djangoapps/certificates/views/webview.py
View file @
7e52af97
...
...
@@ -14,7 +14,7 @@ from django.contrib.auth.models import User
from
django.http
import
Http404
,
HttpResponse
from
django.template
import
RequestContext
from
django.utils.encoding
import
smart_str
from
django.utils
.translation
import
ugettext
as
_
from
django.utils
import
translation
from
eventtracking
import
tracker
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
...
...
@@ -41,6 +41,7 @@ from courseware.courses import get_course_by_id
from
edxmako.shortcuts
import
render_to_response
from
edxmako.template
import
Template
from
openedx.core.djangoapps.catalog.utils
import
get_course_run_details
from
openedx.core.djangoapps.lang_pref.api
import
released_languages
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.lib.courses
import
course_image_url
from
openedx.core.djangoapps.certificates.api
import
display_date_for_certificate
...
...
@@ -51,6 +52,10 @@ from util.views import handle_500
log
=
logging
.
getLogger
(
__name__
)
_
=
translation
.
ugettext
INVALID_CERTIFICATE_TEMPLATE_PATH
=
'certificates/invalid.html'
def
get_certificate_description
(
mode
,
certificate_type
,
platform_name
):
...
...
@@ -251,29 +256,6 @@ def _update_course_context(request, context, course, course_key, platform_name):
platform_name
=
platform_name
)
def
_update_context_with_catalog_data
(
context
,
course_key
):
"""
Updates context dictionary with relevant course run info from Discovery.
"""
course_certificate_settings
=
CertificateGenerationCourseSetting
.
get
(
course_key
)
if
course_certificate_settings
:
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'
])
context
[
'hours_of_effort'
]
=
weeks_to_complete
*
max_effort
except
ValueError
:
log
.
exception
(
'Error occurred while parsing course run details'
)
context
[
'content_language'
]
=
course_run_data
.
get
(
'content_language'
)
def
_update_social_context
(
request
,
context
,
course
,
user
,
user_certificate
,
platform_name
):
"""
Updates context dictionary with info required for social sharing.
...
...
@@ -432,26 +414,6 @@ def _track_certificate_events(request, context, course, user, user_certificate):
})
def
_render_certificate_template
(
request
,
context
,
course
,
user_certificate
):
"""
Picks appropriate certificate templates and renders it.
"""
if
settings
.
FEATURES
.
get
(
'CUSTOM_CERTIFICATE_TEMPLATES_ENABLED'
,
False
):
custom_template
=
get_certificate_template
(
course
.
id
,
user_certificate
.
mode
,
context
.
get
(
'content_language'
))
if
custom_template
:
template
=
Template
(
custom_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
))
return
render_to_response
(
"certificates/valid.html"
,
context
)
def
_update_configuration_context
(
context
,
configuration
):
"""
Site Configuration will need to be able to override any hard coded
...
...
@@ -535,14 +497,10 @@ def render_html_view(request, user_id, course_id):
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
)
return
_render_invalid_certificate
(
course_id
,
platform_name
,
configuration
)
# Load the course and user objects
try
:
...
...
@@ -557,7 +515,7 @@ def render_html_view(request, user_id, course_id):
"
%
d. Specific error:
%
s"
)
log
.
info
(
error_str
,
course_id
,
user_id
,
str
(
exception
))
return
render_to_response
(
invalid_template_path
,
context
)
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
:
...
...
@@ -566,7 +524,7 @@ def render_html_view(request, user_id, course_id):
course_id
,
user_id
,
)
return
render_to_response
(
invalid_template_path
,
context
)
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
)
...
...
@@ -576,7 +534,7 @@ def render_html_view(request, user_id, course_id):
user_id
,
course_id
,
)
return
render_to_response
(
invalid_template_path
,
context
)
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
...
...
@@ -588,46 +546,155 @@ def render_html_view(request, user_id, course_id):
course_id
,
user_id
,
)
return
render_to_response
(
invalid_template_path
,
context
)
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
}
context
[
'certificate_data'
]
=
active_configuration
_update_context_with_basic_info
(
context
,
course_id
,
platform_name
,
configuration
)
# Append/Override the existing view context values with any mode-specific ConfigurationModel values
context
.
update
(
configuration
.
get
(
user_certificate
.
mode
,
{}))
context
[
'certificate_data'
]
=
active_configuration
# Append organization info
_update_organization_context
(
context
,
course
)
# Append/Override the existing view context values with any mode-specific ConfigurationModel values
context
.
update
(
configuration
.
get
(
user_certificate
.
mode
,
{})
)
# Append course
info
_update_course_context
(
request
,
context
,
course
,
course_key
,
platform_nam
e
)
# Append organization
info
_update_organization_context
(
context
,
cours
e
)
# Append course run info from discovery
_update_context_with_catalog_data
(
context
,
course_key
)
# Append course info
_update_course_context
(
request
,
context
,
course
,
course_key
,
platform_name
)
# Append user info
_update_context_with_user_info
(
context
,
user
,
user_certificate
)
# Append course run info from discovery
context
.
update
(
catalog_data
)
# Append social sharing
info
_update_social_context
(
request
,
context
,
course
,
user
,
user_certificate
,
platform_nam
e
)
# Append user
info
_update_context_with_user_info
(
context
,
user
,
user_certificat
e
)
# Append/Override the existing view context values with certificate specific values
_update_certificate_context
(
context
,
course
,
user_certificate
,
platform_name
)
# Append social sharing info
_update_social_context
(
request
,
context
,
course
,
user
,
user_certificate
,
platform_name
)
# Append badge info
_update_badge_context
(
context
,
course
,
user
)
# Append/Override the existing view context values with certificate specific values
_update_certificate_context
(
context
,
course
,
user_certificate
,
platform_name
)
# Append site configuration overrides
_update_configuration_context
(
context
,
configuration
)
# Append badge info
_update_badge_context
(
context
,
course
,
user
)
# 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 site configuration overrides
_update_configuration_context
(
context
,
configuration
)
# Append/Override the existing view context values with any course-specific static values from Advanced Settings
context
.
update
(
course
.
cert_html_view_overrides
)
# 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
())
# Track certificate view event
s
_track_certificate_events
(
request
,
context
,
course
,
user
,
user_certificate
)
# Append/Override the existing view context values with any course-specific static values from Advanced Setting
s
context
.
update
(
course
.
cert_html_view_overrides
)
# FINALLY, render appropriate certificate
return
_render_certificate_template
(
request
,
context
,
course
,
user_certificate
)
# 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.
"""
closest_released_language
=
_get_closest_released_language
(
course_language
)
if
course_language
else
None
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
_get_closest_released_language
(
target
):
"""
Return the language code that most closely matches the target and is fully supported by the LMS, or None
if there are no fully supported languages that match the target.
"""
match
=
None
languages
=
released_languages
()
for
language
in
languages
:
if
language
.
code
==
target
:
match
=
language
.
code
break
elif
(
match
is
None
)
and
(
language
.
code
[:
2
]
==
target
[:
2
]):
match
=
language
.
code
return
match
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
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment