diff --git a/lms/djangoapps/bulk_email/tasks.py b/lms/djangoapps/bulk_email/tasks.py index 4facbc8655..c2cd02da8a 100644 --- a/lms/djangoapps/bulk_email/tasks.py +++ b/lms/djangoapps/bulk_email/tasks.py @@ -128,6 +128,7 @@ def course_email(hash_for_msg, to_list, course_title, course_url, throttle=False 'emails/email_footer.html', email_context ) + plain_footer = render_to_string( 'emails/email_footer.txt', email_context @@ -173,7 +174,7 @@ def course_email(hash_for_msg, to_list, course_title, course_url, throttle=False course_title, course_url, current_task.request.retries > 0 - ], + ], exc=exc, countdown=(2 ** current_task.request.retries) * 15 ) diff --git a/lms/djangoapps/bulk_email/tests/test_email.py b/lms/djangoapps/bulk_email/tests/test_email.py index 57918936cf..a82756f9f3 100644 --- a/lms/djangoapps/bulk_email/tests/test_email.py +++ b/lms/djangoapps/bulk_email/tests/test_email.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Unit tests for sending course email """ @@ -82,7 +83,10 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox[0].to), 1) self.assertEquals(mail.outbox[0].to[0], self.instructor.email) - self.assertEquals(mail.outbox[0].subject, '[' + self.course.display_name + ']' + ' test subject for myself') + self.assertEquals( + mail.outbox[0].subject, + '[' + self.course.display_name + ']' + ' test subject for myself' + ) def test_send_to_staff(self): """ @@ -101,7 +105,10 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): self.assertContains(response, "Your email was successfully queued for sending.") self.assertEquals(len(mail.outbox), 1 + len(self.staff)) - self.assertItemsEqual([e.to[0] for e in mail.outbox], [self.instructor.email] + [s.email for s in self.staff]) + self.assertItemsEqual( + [e.to[0] for e in mail.outbox], + [self.instructor.email] + [s.email for s in self.staff] + ) def test_send_to_all(self): """ @@ -121,7 +128,94 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): self.assertContains(response, "Your email was successfully queued for sending.") self.assertEquals(len(mail.outbox), 1 + len(self.staff) + len(self.students)) - self.assertItemsEqual([e.to[0] for e in mail.outbox], [self.instructor.email] + [s.email for s in self.staff] + [s.email for s in self.students]) + self.assertItemsEqual( + [e.to[0] for e in mail.outbox], + [self.instructor.email] + [s.email for s in self.staff] + [s.email for s in self.students] + ) + + def test_unicode_subject_send_to_all(self): + """ + Make sure email (with Unicode characters) send to all goes there. + """ + # Now we know we have pulled up the instructor dash's email view + # (in the setUp method), we can test sending an email. + + test_email = { + 'action': 'Send email', + 'to_option': 'all', + 'subject': u'téśt śúbjéćt főŕ áĺĺ', + 'message': 'test message for all' + } + response = self.client.post(self.url, test_email) + + self.assertContains(response, "Your email was successfully queued for sending.") + + self.assertEquals(len(mail.outbox), 1 + len(self.staff) + len(self.students)) + self.assertItemsEqual( + [e.to[0] for e in mail.outbox], + [self.instructor.email] + [s.email for s in self.staff] + [s.email for s in self.students] + ) + self.assertEquals( + mail.outbox[0].subject, + '[' + self.course.display_name + ']' + u' téśt śúbjéćt főŕ áĺĺ' + ) + + def test_unicode_message_send_to_all(self): + """ + Make sure email (with Unicode characters) send to all goes there. + """ + # Now we know we have pulled up the instructor dash's email view + # (in the setUp method), we can test sending an email. + + test_email = { + 'action': 'Send email', + 'to_option': 'all', + 'subject': 'test subject for all', + 'message': u'ẗëṡẗ ṁëṡṡäġë ḟöṛ äḷḷ イ乇丂イ ᄊ乇丂丂ムg乇 キo尺 ムレレ тэѕт мэѕѕаБэ fоѓ аll' + } + response = self.client.post(self.url, test_email) + + self.assertContains(response, "Your email was successfully queued for sending.") + + self.assertEquals(len(mail.outbox), 1 + len(self.staff) + len(self.students)) + self.assertItemsEqual( + [e.to[0] for e in mail.outbox], + [self.instructor.email] + [s.email for s in self.staff] + [s.email for s in self.students] + ) + + self.assertIn( + u'ẗëṡẗ ṁëṡṡäġë ḟöṛ äḷḷ イ乇丂イ ᄊ乇丂丂ムg乇 キo尺 ムレレ тэѕт мэѕѕаБэ fоѓ аll', + mail.outbox[0].body.decode('utf-8') + ) + + def test_unicode_students_send_to_all(self): + """ + Make sure email (with Unicode characters) send to all goes there. + """ + # Now we know we have pulled up the instructor dash's email view + # (in the setUp method), we can test sending an email. + + # Create a student with Unicode in their first & last names + unicode_user = UserFactory(first_name=u'Ⓡⓞⓑⓞⓣ', last_name=u'ՇﻉรՇ') + CourseEnrollmentFactory.create(user=unicode_user, course_id=self.course.id) + self.students.append(unicode_user) + + test_email = { + 'action': 'Send email', + 'to_option': 'all', + 'subject': 'test subject for all', + 'message': 'test message for all' + } + response = self.client.post(self.url, test_email) + + self.assertContains(response, "Your email was successfully queued for sending.") + + self.assertEquals(len(mail.outbox), 1 + len(self.staff) + len(self.students)) + + self.assertItemsEqual( + [e.to[0] for e in mail.outbox], + [self.instructor.email] + [s.email for s in self.staff] + [s.email for s in self.students] + ) @override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) diff --git a/lms/djangoapps/bulk_email/tests/tests.py b/lms/djangoapps/bulk_email/tests/tests.py index 1d1d7bec19..a5dc69f385 100644 --- a/lms/djangoapps/bulk_email/tests/tests.py +++ b/lms/djangoapps/bulk_email/tests/tests.py @@ -10,6 +10,7 @@ from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE from student.tests.factories import AdminFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory +from xmodule.modulestore import XML_MODULESTORE_TYPE from mock import patch @@ -33,6 +34,7 @@ class TestInstructorDashboardEmailView(ModuleStoreTestCase): @patch.dict(settings.MITX_FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True}) def test_email_flag_true(self): + # Assert that the URL for the email view is in the response response = self.client.get(self.url) self.assertTrue(self.email_link in response.content) @@ -50,5 +52,23 @@ class TestInstructorDashboardEmailView(ModuleStoreTestCase): @patch.dict(settings.MITX_FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': False}) def test_email_flag_false(self): + # Assert that the URL for the email view is not in the response response = self.client.get(self.url) self.assertFalse(self.email_link in response.content) + + @patch.dict(settings.MITX_FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True}) + def test_email_flag_true_xml_store(self): + # If the enable email setting is enabled, but this is an XML backed course, + # the email view shouldn't be available on the instructor dashboard. + + # The course factory uses a MongoModuleStore backing, so patch the + # `get_modulestore_type` method to pretend to be XML-backed. + # This is OK; we're simply testing that the `is_mongo_modulestore_type` flag + # in `instructor/views/legacy.py` is doing the correct thing. + + with patch('xmodule.modulestore.mongo.base.MongoModuleStore.get_modulestore_type') as mock_modulestore: + mock_modulestore.return_value = XML_MODULESTORE_TYPE + + # Assert that the URL for the email view is not in the response + response = self.client.get(self.url) + self.assertFalse(self.email_link in response.content) diff --git a/lms/djangoapps/instructor/views/legacy.py b/lms/djangoapps/instructor/views/legacy.py index 5f3e9a6e01..514c64df98 100644 --- a/lms/djangoapps/instructor/views/legacy.py +++ b/lms/djangoapps/instructor/views/legacy.py @@ -25,6 +25,7 @@ from django.utils import timezone from xmodule_modifiers import wrap_xmodule import xmodule.graders as xmgraders +from xmodule.modulestore import MONGO_MODULESTORE_TYPE from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.html_module import HtmlDescriptor @@ -794,6 +795,12 @@ def instructor_dashboard(request, course_id): else: editor = None + # Flag for what backing store this course is (Mongo vs. XML) + if modulestore().get_modulestore_type(course_id) == MONGO_MODULESTORE_TYPE: + is_mongo_modulestore_type = True + else: + is_mongo_modulestore_type = False + # display course stats only if there is no other table to display: course_stats = None if not datatable: @@ -809,11 +816,11 @@ def instructor_dashboard(request, course_id): 'datatable': datatable, 'course_stats': course_stats, 'msg': msg, - 'email_msg': email_msg, 'modeflag': {idash_mode: 'selectedmode'}, - 'to_option': to_option, # email - 'subject': subject, # email - 'editor': editor, # email + 'to_option': to_option, # email + 'subject': subject, # email + 'editor': editor, # email + 'email_msg': email_msg, # email 'problems': problems, # psychometrics 'plots': plots, # psychometrics 'course_errors': modulestore().get_item_errors(course.location), @@ -822,6 +829,7 @@ def instructor_dashboard(request, course_id): 'cohorts_ajax_url': reverse('cohorts', kwargs={'course_id': course_id}), 'analytics_results': analytics_results, + 'is_mongo_modulestore_type': is_mongo_modulestore_type, } if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BETA_DASHBOARD'): diff --git a/lms/templates/courseware/instructor_dashboard.html b/lms/templates/courseware/instructor_dashboard.html index ec5abe6906..82067d4261 100644 --- a/lms/templates/courseware/instructor_dashboard.html +++ b/lms/templates/courseware/instructor_dashboard.html @@ -124,7 +124,7 @@ function goto( mode) ${_("Enrollment")} | ${_("DataDump")} | ${_("Manage Groups")} - %if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_EMAIL'): + %if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_EMAIL') and is_mongo_modulestore_type: | Email %endif %if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_ANALYTICS'):