Commit 64372dd5 by Adam Committed by GitHub

Merge pull request #13038 from OmarIthawi/edraak/bulk-email-from-addr

Translatable bulk_email from Address based on platform`s default lang
parents c1b148dd 8fe16541
......@@ -103,7 +103,7 @@
<option value="" selected> - </option>
<option value="en">English</option>
</select>
<span class="tip tip-stacked">Identify the course language here. This is used to assist users find courses that are taught in a specific language.</span>
<span class="tip tip-stacked">Identify the course language here. This is used to assist users find courses that are taught in a specific language. It is also used to localize the 'From:' field in bulk emails.</span>
</li>
</ol>
......
......@@ -284,7 +284,7 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url | n, js_escaped_string}'
<option value="${lang}">${label}</option>
% endfor
</select>
<span class="tip tip-stacked">${_("Identify the course language here. This is used to assist users find courses that are taught in a specific language.")}</span>
<span class="tip tip-stacked">${_("Identify the course language here. This is used to assist users find courses that are taught in a specific language. It is also used to localize the 'From:' field in bulk emails.")}</span>
</li>
</ol>
</div>
......
......@@ -37,6 +37,7 @@ Other tags that may be used (surrounded by one curly brace on each side):
{platform_name} : the name of the platform
{course_title} : the name of the course
{course_root} : the URL path to the root of the course
{course_language} : the course language. The default is None.
{course_url} : the course's full URL
{email} : the user's email address
{account_settings_url} : URL at which users can change account preferences
......
......@@ -35,6 +35,7 @@ from django.contrib.auth.models import User
from django.core.mail import EmailMultiAlternatives, get_connection
from django.core.mail.message import forbid_multi_line_headers
from django.core.urlresolvers import reverse
from django.utils.translation import override as override_language, ugettext as _
from bulk_email.models import CourseEmail, Optout
from courseware.courses import get_course
......@@ -109,6 +110,7 @@ def _get_course_email_context(course):
email_context = {
'course_title': course_title,
'course_root': course_root,
'course_language': course.language,
'course_url': course_url,
'course_image_url': image_url,
'course_end_date': course_end_date,
......@@ -350,7 +352,7 @@ def _filter_optouts_from_recipients(to_list, course_id):
return to_list, num_optout
def _get_source_address(course_id, course_title, truncate=True):
def _get_source_address(course_id, course_title, course_language, truncate=True):
"""
Calculates an email address to be used as the 'from-address' for sent emails.
......@@ -373,7 +375,17 @@ def _get_source_address(course_id, course_title, truncate=True):
# character appears.
course_name = re.sub(r"[^\w.-]", '_', course_id.course)
from_addr_format = u'"{course_title}" Course Staff <{course_name}-{from_email}>'
# Use course.language if present
language = course_language if course_language else settings.LANGUAGE_CODE
with override_language(language):
# RFC2821 requires the byte order of the email address to be the name then email
# e.g. "John Doe <email@example.com>"
# Although the display will be flipped in RTL languages, the byte order is still the same.
from_addr_format = u'{name} {email}'.format(
# Translators: Bulk email from address e.g. ("Physics 101" Course Staff)
name=_('"{course_title}" Course Staff'),
email=u'<{course_name}-{from_email}>',
)
def format_address(course_title_no_quotes):
"""
......@@ -475,10 +487,11 @@ def _send_course_email(entry_id, email_id, to_list, global_email_context, subtas
subtask_status.increment(skipped=num_optout)
course_title = global_email_context['course_title']
course_language = global_email_context['course_language']
# use the email from address in the CourseEmail, if it is present, otherwise compute it
from_addr = course_email.from_addr if course_email.from_addr else \
_get_source_address(course_email.course_id, course_title)
_get_source_address(course_email.course_id, course_title, course_language)
# use the CourseEmailTemplate that was associated with the CourseEmail
course_email_template = course_email.get_template()
......
......@@ -8,6 +8,7 @@ from mock import patch, Mock
from nose.plugins.attrib import attr
import os
from unittest import skipIf
import ddt
from django.conf import settings
from django.core import mail
......@@ -15,6 +16,7 @@ from django.core.mail.message import forbid_multi_line_headers
from django.core.urlresolvers import reverse
from django.core.management import call_command
from django.test.utils import override_settings
from django.utils.translation import get_language
from bulk_email.models import Optout, BulkEmailFlag
from bulk_email.tasks import _get_source_address, _get_course_email_context
......@@ -132,6 +134,99 @@ class EmailSendFromDashboardTestCase(SharedModuleStoreTestCase):
BulkEmailFlag.objects.all().delete()
class SendEmailWithMockedUgettextMixin(object):
"""
Mock uggetext for EmailSendFromDashboardTestCase.
"""
def send_email(self):
"""
Sends a dummy email to check the `from_addr` translation.
"""
test_email = {
'action': 'send',
'send_to': '["myself"]',
'subject': 'test subject for myself',
'message': 'test message for myself'
}
def mock_ugettext(text):
"""
Mocks ugettext to return the lang code with the original string.
e.g.
>>> mock_ugettext('Hello') == '@AR Hello@'
"""
return u'@{lang} {text}@'.format(
lang=get_language().upper(),
text=text,
)
with patch('bulk_email.tasks._', side_effect=mock_ugettext):
self.client.post(self.send_mail_url, test_email)
return mail.outbox[0]
@attr(shard=1)
@patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True, 'REQUIRE_COURSE_EMAIL_AUTH': False})
@ddt.ddt
class LocalizedFromAddressPlatformLangTestCase(SendEmailWithMockedUgettextMixin, EmailSendFromDashboardTestCase):
"""
Tests to ensure that the bulk email has the "From" address localized according to LANGUAGE_CODE.
"""
@override_settings(LANGUAGE_CODE='en')
def test_english_platform(self):
"""
Ensures that the source-code language (English) works well.
"""
self.assertIsNone(self.course.language) # Sanity check
message = self.send_email()
self.assertRegexpMatches(message.from_email, '.*Course Staff.*')
@override_settings(LANGUAGE_CODE='eo')
def test_esperanto_platform(self):
"""
Tests the fake Esperanto language to ensure proper gettext calls.
"""
self.assertIsNone(self.course.language) # Sanity check
message = self.send_email()
self.assertRegexpMatches(message.from_email, '@EO .* Course Staff@')
@attr(shard=1)
@patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True, 'REQUIRE_COURSE_EMAIL_AUTH': False})
@ddt.ddt
class LocalizedFromAddressCourseLangTestCase(SendEmailWithMockedUgettextMixin, EmailSendFromDashboardTestCase):
"""
Test if the bulk email "From" address uses the course.language if present instead of LANGUAGE_CODE.
This is similiar to LocalizedFromAddressTestCase but creating a different test case to allow
changing the class-wide course object.
"""
@classmethod
def setUpClass(cls):
"""
Creates a different course.
"""
super(LocalizedFromAddressCourseLangTestCase, cls).setUpClass()
course_title = u"ẗëṡẗ イэ"
cls.course = CourseFactory.create(
display_name=course_title,
language='ar',
default_store=ModuleStoreEnum.Type.split
)
@override_settings(LANGUAGE_CODE='eo')
def test_esperanto_platform_arabic_course(self):
"""
The course language should override the platform's.
"""
message = self.send_email()
self.assertRegexpMatches(message.from_email, '@AR .* Course Staff@')
@attr(shard=1)
@patch('bulk_email.models.html_to_text', Mock(return_value='Mocking CourseEmail.text_message', autospec=True))
class TestEmailSendFromDashboardMockedHtmlToText(EmailSendFromDashboardTestCase):
......@@ -394,7 +489,7 @@ class TestEmailSendFromDashboardMockedHtmlToText(EmailSendFromDashboardTestCase)
instructor = InstructorFactory(course_key=course.id)
unexpected_from_addr = _get_source_address(
course.id, course.display_name, truncate=False
course.id, course.display_name, course_language=None, truncate=False
)
__, encoded_unexpected_from_addr = forbid_multi_line_headers(
"from", unexpected_from_addr, 'utf-8'
......
......@@ -440,6 +440,7 @@ class TestBulkEmailInstructorTask(InstructorTaskCourseTestCase):
result = _get_course_email_context(self.course)
self.assertIn('course_title', result)
self.assertIn('course_root', result)
self.assertIn('course_language', result)
self.assertIn('course_url', result)
self.assertIn('course_image_url', result)
self.assertIn('course_end_date', result)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment