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
e70a5e26
Commit
e70a5e26
authored
Jan 21, 2015
by
Sarina Canelake
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6082 from openfun/edx/translate-enrollment-emails
Render enrollment emails in the student's language
parents
1eaf84a4
f3419bb5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
198 additions
and
16 deletions
+198
-16
AUTHORS
+2
-2
lms/djangoapps/instructor/enrollment.py
+50
-10
lms/djangoapps/instructor/tests/test_api_email_localization.py
+84
-0
lms/djangoapps/instructor/tests/test_enrollment.py
+52
-1
lms/djangoapps/instructor/views/api.py
+10
-3
No files found.
AUTHORS
View file @
e70a5e26
...
...
@@ -188,4 +188,5 @@ Wenjie Wu <wuwenjie718@gmail.com>
Aamir <aamir.nu.206@gmail.com>
Steve Jackson <sjackso@ixoreus.net>
Steffan Sluis <steffansluis@gmail.com>
Siem Kok <siem@feedbackfruits.com>
\ No newline at end of file
Siem Kok <siem@feedbackfruits.com>
Régis Behmo <regis.behmo@openfun.fr>
lms/djangoapps/instructor/enrollment.py
View file @
e70a5e26
...
...
@@ -9,13 +9,16 @@ from django.contrib.auth.models import User
from
django.conf
import
settings
from
django.core.urlresolvers
import
reverse
from
django.core.mail
import
send_mail
from
django.utils.translation
import
override
as
override_language
from
student.models
import
CourseEnrollment
,
CourseEnrollmentAllowed
from
courseware.models
import
StudentModule
from
edxmako.shortcuts
import
render_to_string
from
lang_pref
import
LANGUAGE_KEY
from
submissions
import
api
as
sub_api
# installed from the edx-submissions repository
from
student.models
import
anonymous_id_for_user
from
openedx.core.djangoapps.user_api.models
import
UserPreference
from
microsite_configuration
import
microsite
...
...
@@ -71,7 +74,15 @@ class EmailEnrollmentState(object):
}
def
enroll_email
(
course_id
,
student_email
,
auto_enroll
=
False
,
email_students
=
False
,
email_params
=
None
):
def
get_user_email_language
(
user
):
"""
Return the language most appropriate for writing emails to user. Returns
None if the preference has not been set, or if the user does not exist.
"""
return
UserPreference
.
get_preference
(
user
,
LANGUAGE_KEY
)
def
enroll_email
(
course_id
,
student_email
,
auto_enroll
=
False
,
email_students
=
False
,
email_params
=
None
,
language
=
None
):
"""
Enroll a student by email.
...
...
@@ -81,6 +92,7 @@ def enroll_email(course_id, student_email, auto_enroll=False, email_students=Fal
enrolled in the course automatically.
`email_students` determines if student should be notified of action by email.
`email_params` parameters used while parsing email templates (a `dict`).
`language` is the language used to render the email.
returns two EmailEnrollmentState's
representing state before and after the action.
...
...
@@ -99,7 +111,7 @@ def enroll_email(course_id, student_email, auto_enroll=False, email_students=Fal
email_params
[
'message'
]
=
'enrolled_enroll'
email_params
[
'email_address'
]
=
student_email
email_params
[
'full_name'
]
=
previous_state
.
full_name
send_mail_to_student
(
student_email
,
email_params
)
send_mail_to_student
(
student_email
,
email_params
,
language
=
language
)
else
:
cea
,
_
=
CourseEnrollmentAllowed
.
objects
.
get_or_create
(
course_id
=
course_id
,
email
=
student_email
)
cea
.
auto_enroll
=
auto_enroll
...
...
@@ -107,20 +119,21 @@ def enroll_email(course_id, student_email, auto_enroll=False, email_students=Fal
if
email_students
:
email_params
[
'message'
]
=
'allowed_enroll'
email_params
[
'email_address'
]
=
student_email
send_mail_to_student
(
student_email
,
email_params
)
send_mail_to_student
(
student_email
,
email_params
,
language
=
language
)
after_state
=
EmailEnrollmentState
(
course_id
,
student_email
)
return
previous_state
,
after_state
def
unenroll_email
(
course_id
,
student_email
,
email_students
=
False
,
email_params
=
None
):
def
unenroll_email
(
course_id
,
student_email
,
email_students
=
False
,
email_params
=
None
,
language
=
None
):
"""
Unenroll a student by email.
`student_email` is student's emails e.g. "foo@bar.com"
`email_students` determines if student should be notified of action by email.
`email_params` parameters used while parsing email templates (a `dict`).
`language` is the language used to render the email.
returns two EmailEnrollmentState's
representing state before and after the action.
...
...
@@ -133,7 +146,7 @@ def unenroll_email(course_id, student_email, email_students=False, email_params=
email_params
[
'message'
]
=
'enrolled_unenroll'
email_params
[
'email_address'
]
=
student_email
email_params
[
'full_name'
]
=
previous_state
.
full_name
send_mail_to_student
(
student_email
,
email_params
)
send_mail_to_student
(
student_email
,
email_params
,
language
=
language
)
if
previous_state
.
allowed
:
CourseEnrollmentAllowed
.
objects
.
get
(
course_id
=
course_id
,
email
=
student_email
)
.
delete
()
...
...
@@ -141,7 +154,7 @@ def unenroll_email(course_id, student_email, email_students=False, email_params=
email_params
[
'message'
]
=
'allowed_unenroll'
email_params
[
'email_address'
]
=
student_email
# Since no User object exists for this student there is no "full_name" available.
send_mail_to_student
(
student_email
,
email_params
)
send_mail_to_student
(
student_email
,
email_params
,
language
=
language
)
after_state
=
EmailEnrollmentState
(
course_id
,
student_email
)
...
...
@@ -169,7 +182,7 @@ def send_beta_role_email(action, user, email_params):
else
:
raise
ValueError
(
"Unexpected action received '{}' - expected 'add' or 'remove'"
.
format
(
action
))
send_mail_to_student
(
user
.
email
,
email_params
)
send_mail_to_student
(
user
.
email
,
email_params
,
language
=
get_user_email_language
(
user
)
)
def
reset_student_attempts
(
course_id
,
student
,
module_state_key
,
delete_module
=
False
):
...
...
@@ -279,7 +292,7 @@ def get_email_params(course, auto_enroll, secure=True):
return
email_params
def
send_mail_to_student
(
student
,
param_dict
):
def
send_mail_to_student
(
student
,
param_dict
,
language
=
None
):
"""
Construct the email using templates and then send it.
`student` is the student's email address (a `str`),
...
...
@@ -297,6 +310,10 @@ def send_mail_to_student(student, param_dict):
`is_shib_course`: (a `boolean`)
]
`language` is the language used to render the email. If None the language
of the currently-logged in user (that is, the user sending the email) will
be used.
Returns a boolean indicating whether the email was sent successfully.
"""
...
...
@@ -349,8 +366,9 @@ def send_mail_to_student(student, param_dict):
subject_template
,
message_template
=
email_template_dict
.
get
(
message_type
,
(
None
,
None
))
if
subject_template
is
not
None
and
message_template
is
not
None
:
subject
=
render_to_string
(
subject_template
,
param_dict
)
message
=
render_to_string
(
message_template
,
param_dict
)
subject
,
message
=
render_message_to_string
(
subject_template
,
message_template
,
param_dict
,
language
=
language
)
if
subject
and
message
:
# Remove leading and trailing whitespace from body
...
...
@@ -366,6 +384,28 @@ def send_mail_to_student(student, param_dict):
send_mail
(
subject
,
message
,
from_address
,
[
student
],
fail_silently
=
False
)
def
render_message_to_string
(
subject_template
,
message_template
,
param_dict
,
language
=
None
):
"""
Render a mail subject and message templates using the parameters from
param_dict and the given language. If language is None, the platform
default language is used.
Returns two strings that correspond to the rendered, translated email
subject and message.
"""
with
override_language
(
language
):
return
get_subject_and_message
(
subject_template
,
message_template
,
param_dict
)
def
get_subject_and_message
(
subject_template
,
message_template
,
param_dict
):
"""
Return the rendered subject and message with the appropriate parameters.
"""
subject
=
render_to_string
(
subject_template
,
param_dict
)
message
=
render_to_string
(
message_template
,
param_dict
)
return
subject
,
message
def
uses_shib
(
course
):
"""
Used to return whether course has Shibboleth as the enrollment domain
...
...
lms/djangoapps/instructor/tests/test_api_email_localization.py
0 → 100644
View file @
e70a5e26
# -*- coding: utf-8 -*-
"""
Unit tests for the localization of emails sent by instructor.api methods.
"""
from
django.core
import
mail
from
django.core.urlresolvers
import
reverse
from
django.test
import
TestCase
from
courseware.tests.factories
import
InstructorFactory
from
lang_pref
import
LANGUAGE_KEY
from
student.models
import
CourseEnrollment
from
student.tests.factories
import
UserFactory
from
openedx.core.djangoapps.user_api.models
import
UserPreference
from
xmodule.modulestore.tests.factories
import
CourseFactory
class
TestInstructorAPIEnrollmentEmailLocalization
(
TestCase
):
"""
Test whether the enroll, unenroll and beta role emails are sent in the
proper language, i.e: the student's language.
"""
def
setUp
(
self
):
# Platform language is English, instructor's language is Chinese,
# student's language is French, so the emails should all be sent in
# French.
self
.
course
=
CourseFactory
.
create
()
self
.
instructor
=
InstructorFactory
(
course_key
=
self
.
course
.
id
)
UserPreference
.
set_preference
(
self
.
instructor
,
LANGUAGE_KEY
,
'zh-cn'
)
self
.
client
.
login
(
username
=
self
.
instructor
.
username
,
password
=
'test'
)
self
.
student
=
UserFactory
.
create
()
UserPreference
.
set_preference
(
self
.
student
,
LANGUAGE_KEY
,
'fr'
)
def
update_enrollement
(
self
,
action
,
student_email
):
"""
Update the current student enrollment status.
"""
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
args
=
{
'identifiers'
:
student_email
,
'email_students'
:
'true'
,
'action'
:
action
}
response
=
self
.
client
.
post
(
url
,
args
)
return
response
def
check_outbox_is_french
(
self
):
"""
Check that the email outbox contains exactly one message for which both
the message subject and body contain a certain French string.
"""
return
self
.
check_outbox
(
u"Vous avez été"
)
def
check_outbox
(
self
,
expected_message
):
"""
Check that the email outbox contains exactly one message for which both
the message subject and body contain a certain string.
"""
self
.
assertEqual
(
1
,
len
(
mail
.
outbox
))
self
.
assertIn
(
expected_message
,
mail
.
outbox
[
0
]
.
subject
)
self
.
assertIn
(
expected_message
,
mail
.
outbox
[
0
]
.
body
)
def
test_enroll
(
self
):
self
.
update_enrollement
(
"enroll"
,
self
.
student
.
email
)
self
.
check_outbox_is_french
()
def
test_unenroll
(
self
):
CourseEnrollment
.
enroll
(
self
.
student
,
self
.
course
.
id
)
self
.
update_enrollement
(
"unenroll"
,
self
.
student
.
email
)
self
.
check_outbox_is_french
()
def
test_set_beta_role
(
self
):
url
=
reverse
(
'bulk_beta_modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
student
.
email
,
'action'
:
'add'
,
'email_students'
:
'true'
})
self
.
check_outbox_is_french
()
def
test_enroll_unsubscribed_student
(
self
):
# Student is unknown, so the platform language should be used
self
.
update_enrollement
(
"enroll"
,
"newuser@hotmail.com"
)
self
.
check_outbox
(
"You have been"
)
lms/djangoapps/instructor/tests/test_enrollment.py
View file @
e70a5e26
# -*- coding: utf-8 -*-
"""
Unit tests for instructor.enrollment methods.
"""
...
...
@@ -9,6 +10,8 @@ from courseware.models import StudentModule
from
django.conf
import
settings
from
django.test
import
TestCase
from
django.test.utils
import
override_settings
from
django.utils.translation
import
get_language
from
django.utils.translation
import
override
as
override_language
from
student.tests.factories
import
UserFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.django_utils
import
TEST_DATA_MOCK_MODULESTORE
...
...
@@ -20,7 +23,8 @@ from instructor.enrollment import (
get_email_params
,
reset_student_attempts
,
send_beta_role_email
,
unenroll_email
unenroll_email
,
render_message_to_string
,
)
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
...
...
@@ -472,3 +476,50 @@ class TestGetEmailParams(ModuleStoreTestCase):
self
.
assertEqual
(
result
[
'course_about_url'
],
None
)
self
.
assertEqual
(
result
[
'registration_url'
],
self
.
registration_url
)
self
.
assertEqual
(
result
[
'course_url'
],
self
.
course_url
)
class
TestRenderMessageToString
(
TestCase
):
"""
Test that email templates can be rendered in a language chosen manually.
"""
def
setUp
(
self
):
self
.
subject_template
=
'emails/enroll_email_allowedsubject.txt'
self
.
message_template
=
'emails/enroll_email_allowedmessage.txt'
self
.
course
=
CourseFactory
.
create
()
def
get_email_params
(
self
):
"""
Returns a dictionary of parameters used to render an email.
"""
email_params
=
get_email_params
(
self
.
course
,
True
)
email_params
[
"email_address"
]
=
"user@example.com"
email_params
[
"full_name"
]
=
"Jean Reno"
return
email_params
def
get_subject_and_message
(
self
,
language
):
"""
Returns the subject and message rendered in the specified language.
"""
return
render_message_to_string
(
self
.
subject_template
,
self
.
message_template
,
self
.
get_email_params
(),
language
=
language
)
def
test_subject_and_message_translation
(
self
):
subject
,
message
=
self
.
get_subject_and_message
(
'fr'
)
language_after_rendering
=
get_language
()
you_have_been_invited_in_french
=
u"Vous avez été invité"
self
.
assertIn
(
you_have_been_invited_in_french
,
subject
)
self
.
assertIn
(
you_have_been_invited_in_french
,
message
)
self
.
assertEqual
(
settings
.
LANGUAGE_CODE
,
language_after_rendering
)
def
test_platform_language_is_used_for_logged_in_user
(
self
):
with
override_language
(
'zh_CN'
):
# simulate a user login
subject
,
message
=
self
.
get_subject_and_message
(
None
)
self
.
assertIn
(
"You have been"
,
subject
)
self
.
assertIn
(
"You have been"
,
message
)
lms/djangoapps/instructor/views/api.py
View file @
e70a5e26
...
...
@@ -57,11 +57,12 @@ from instructor_task.api_helper import AlreadyRunningError
from
instructor_task.models
import
ReportStore
import
instructor.enrollment
as
enrollment
from
instructor.enrollment
import
(
get_user_email_language
,
enroll_email
,
send_mail_to_student
,
get_email_params
,
send_beta_role_email
,
unenroll_email
unenroll_email
,
)
from
instructor.access
import
list_with_level
,
allow_access
,
revoke_access
,
update_forum_role
from
instructor.offline_gradecalc
import
student_grades
...
...
@@ -497,12 +498,14 @@ def students_update_enrollment(request, course_id):
# First try to get a user object from the identifer
user
=
None
email
=
None
language
=
None
try
:
user
=
get_student_from_identifier
(
identifier
)
except
User
.
DoesNotExist
:
email
=
identifier
else
:
email
=
user
.
email
language
=
get_user_email_language
(
user
)
try
:
# Use django.core.validators.validate_email to check email address
...
...
@@ -511,9 +514,13 @@ def students_update_enrollment(request, course_id):
validate_email
(
email
)
# Raises ValidationError if invalid
if
action
==
'enroll'
:
before
,
after
=
enroll_email
(
course_id
,
email
,
auto_enroll
,
email_students
,
email_params
)
before
,
after
=
enroll_email
(
course_id
,
email
,
auto_enroll
,
email_students
,
email_params
,
language
=
language
)
elif
action
==
'unenroll'
:
before
,
after
=
unenroll_email
(
course_id
,
email
,
email_students
,
email_params
)
before
,
after
=
unenroll_email
(
course_id
,
email
,
email_students
,
email_params
,
language
=
language
)
else
:
return
HttpResponseBadRequest
(
strip_tags
(
"Unrecognized action '{}'"
.
format
(
action
)
...
...
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