diff --git a/common/lib/html_to_text.py b/common/lib/html_to_text.py new file mode 100644 index 0000000000..b4b2295509 --- /dev/null +++ b/common/lib/html_to_text.py @@ -0,0 +1,23 @@ +"""Provides a function to convert html to plaintext.""" +from subprocess import Popen, PIPE + +def html_to_text(html_message): + """ + Converts an html message to plaintext. + Currently uses lynx in a subprocess; should be refactored to + use something more pythonic. + """ + process = Popen( + ['lynx', '-stdin', '-display_charset=UTF-8', '-assume_charset=UTF-8', '-dump'], + stdin=PIPE, + stdout=PIPE + ) + # use lynx to get plaintext + (plaintext, err_from_stderr) = process.communicate( + input=html_message.encode('utf-8') + ) + + if err_from_stderr: + log.info(err_from_stderr) + + return plaintext diff --git a/lms/djangoapps/bulk_email/models.py b/lms/djangoapps/bulk_email/models.py index 1eed87791e..ef06e83d13 100644 --- a/lms/djangoapps/bulk_email/models.py +++ b/lms/djangoapps/bulk_email/models.py @@ -14,6 +14,7 @@ class Email(models.Model): hash = models.CharField(max_length=128, db_index=True) subject = models.CharField(max_length=128, blank=True) html_message = models.TextField(null=True, blank=True) + text_message = models.TextField(null=True, blank=True) created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) diff --git a/lms/djangoapps/bulk_email/tasks.py b/lms/djangoapps/bulk_email/tasks.py index c2cd02da8a..b34c3983b6 100644 --- a/lms/djangoapps/bulk_email/tasks.py +++ b/lms/djangoapps/bulk_email/tasks.py @@ -8,7 +8,6 @@ import re import time from smtplib import SMTPServerDisconnected, SMTPDataError, SMTPConnectError -from subprocess import Popen, PIPE from django.conf import settings from django.contrib.auth.models import User, Group @@ -98,15 +97,9 @@ def course_email(hash_for_msg, to_list, course_title, course_url, throttle=False subject = "[" + course_title + "] " + msg.subject - process = Popen(['lynx', '-stdin', '-display_charset=UTF-8', '-assume_charset=UTF-8', '-dump'], stdin=PIPE, stdout=PIPE) - (plaintext, err_from_stderr) = process.communicate(input=msg.html_message.encode('utf-8')) # use lynx to get plaintext - course_title_no_quotes = re.sub(r'"', '', course_title) from_addr = '"{0}" Course Staff <{1}>'.format(course_title_no_quotes, settings.DEFAULT_BULK_FROM_EMAIL) - if err_from_stderr: - log.info(err_from_stderr) - try: connection = get_connection() connection.open() @@ -136,14 +129,15 @@ def course_email(hash_for_msg, to_list, course_title, course_url, throttle=False email_msg = EmailMultiAlternatives( subject, - plaintext + plain_footer.encode('utf-8'), + msg.text_message + plain_footer.encode('utf-8'), from_addr, [email], connection=connection ) email_msg.attach_alternative(msg.html_message + html_footer.encode('utf-8'), 'text/html') - if throttle or current_task.request.retries > 0: # throttle if we tried a few times and got the rate limiter + # Throttle if we tried a few times and got the rate limiter + if throttle or current_task.request.retries > 0: time.sleep(0.2) try: diff --git a/lms/djangoapps/bulk_email/tests/test_course_optout.py b/lms/djangoapps/bulk_email/tests/test_course_optout.py index 297e5806f7..499ccb0b95 100644 --- a/lms/djangoapps/bulk_email/tests/test_course_optout.py +++ b/lms/djangoapps/bulk_email/tests/test_course_optout.py @@ -31,6 +31,12 @@ class TestOptoutCourseEmails(ModuleStoreTestCase): self.client.login(username=self.student.username, password="test") + def tearDown(self): + """ + Undo all patches. + """ + patch.stopall() + def navigate_to_email_view(self): """Navigate to the instructor dash's email view""" # Pull up email view on instructor dashboard @@ -54,6 +60,8 @@ class TestOptoutCourseEmails(ModuleStoreTestCase): Make sure student does not receive course email after opting out. """ url = reverse('change_email_settings') + # This is a checkbox, so on the post of opting out (that is, an Un-check of the box), + # the Post that is sent will not contain 'receive_emails' response = self.client.post(url, {'course_id': self.course.id}) self.assertEquals(json.loads(response.content), {'success': True}) diff --git a/lms/djangoapps/bulk_email/tests/test_email.py b/lms/djangoapps/bulk_email/tests/test_email.py index a82756f9f3..6f5c09add9 100644 --- a/lms/djangoapps/bulk_email/tests/test_email.py +++ b/lms/djangoapps/bulk_email/tests/test_email.py @@ -64,6 +64,12 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): selected_email_link = 'Email' self.assertTrue(selected_email_link in response.content) + def tearDown(self): + """ + Undo all patches. + """ + patch.stopall() + def test_send_to_self(self): """ Make sure email send to myself goes to myself. @@ -185,7 +191,7 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): self.assertIn( u'ẗëṡẗ ṁëṡṡäġë ḟöṛ äḷḷ イ乇丂イ ᄊ乇丂丂ムg乇 キo尺 ムレレ тэѕт мэѕѕаБэ fоѓ аll', - mail.outbox[0].body.decode('utf-8') + mail.outbox[0].body ) def test_unicode_students_send_to_all(self): diff --git a/lms/djangoapps/bulk_email/tests/test_err_handling.py b/lms/djangoapps/bulk_email/tests/test_err_handling.py index 9a5555b749..2715804079 100644 --- a/lms/djangoapps/bulk_email/tests/test_err_handling.py +++ b/lms/djangoapps/bulk_email/tests/test_err_handling.py @@ -40,8 +40,10 @@ class TestEmailErrors(ModuleStoreTestCase): self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id}) + def tearDown(self): self.smtp_server_thread.stop() + patch.stopall() @patch('bulk_email.tasks.course_email.retry') def test_data_err_retry(self, retry): diff --git a/lms/djangoapps/bulk_email/tests/tests.py b/lms/djangoapps/bulk_email/tests/tests.py index a5dc69f385..66151806f0 100644 --- a/lms/djangoapps/bulk_email/tests/tests.py +++ b/lms/djangoapps/bulk_email/tests/tests.py @@ -32,6 +32,12 @@ class TestInstructorDashboardEmailView(ModuleStoreTestCase): # URL for email view self.email_link = 'Email' + def tearDown(self): + """ + Undo all patches. + """ + patch.stopall() + @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 diff --git a/lms/djangoapps/instructor/views/legacy.py b/lms/djangoapps/instructor/views/legacy.py index 514c64df98..4143639781 100644 --- a/lms/djangoapps/instructor/views/legacy.py +++ b/lms/djangoapps/instructor/views/legacy.py @@ -55,6 +55,7 @@ from mitxmako.shortcuts import render_to_string from bulk_email.models import CourseEmail +from html_to_text import html_to_text import datetime from hashlib import md5 from bulk_email import tasks @@ -706,12 +707,14 @@ def instructor_dashboard(request, course_id): to_option = request.POST.get("to_option") subject = request.POST.get("subject") html_message = request.POST.get("message") + text_message = html_to_text(html_message) email = CourseEmail(course_id=course_id, sender=request.user, to=to_option, subject=subject, html_message=html_message, + text_message=text_message, hash=md5((html_message + subject + datetime.datetime.isoformat(datetime.datetime.now())).encode('utf-8')).hexdigest()) email.save()