Commit b3bc4023 by Sarina Canelake

Refactor html->plaintext conversion (for bulk email) into separate library

parent f98d6764
"""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
...@@ -14,6 +14,7 @@ class Email(models.Model): ...@@ -14,6 +14,7 @@ class Email(models.Model):
hash = models.CharField(max_length=128, db_index=True) hash = models.CharField(max_length=128, db_index=True)
subject = models.CharField(max_length=128, blank=True) subject = models.CharField(max_length=128, blank=True)
html_message = models.TextField(null=True, 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) created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)
......
...@@ -8,7 +8,6 @@ import re ...@@ -8,7 +8,6 @@ import re
import time import time
from smtplib import SMTPServerDisconnected, SMTPDataError, SMTPConnectError from smtplib import SMTPServerDisconnected, SMTPDataError, SMTPConnectError
from subprocess import Popen, PIPE
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User, Group 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 ...@@ -98,15 +97,9 @@ def course_email(hash_for_msg, to_list, course_title, course_url, throttle=False
subject = "[" + course_title + "] " + msg.subject 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) 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) 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: try:
connection = get_connection() connection = get_connection()
connection.open() connection.open()
...@@ -136,14 +129,15 @@ def course_email(hash_for_msg, to_list, course_title, course_url, throttle=False ...@@ -136,14 +129,15 @@ def course_email(hash_for_msg, to_list, course_title, course_url, throttle=False
email_msg = EmailMultiAlternatives( email_msg = EmailMultiAlternatives(
subject, subject,
plaintext + plain_footer.encode('utf-8'), msg.text_message + plain_footer.encode('utf-8'),
from_addr, from_addr,
[email], [email],
connection=connection connection=connection
) )
email_msg.attach_alternative(msg.html_message + html_footer.encode('utf-8'), 'text/html') 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) time.sleep(0.2)
try: try:
......
...@@ -31,6 +31,12 @@ class TestOptoutCourseEmails(ModuleStoreTestCase): ...@@ -31,6 +31,12 @@ class TestOptoutCourseEmails(ModuleStoreTestCase):
self.client.login(username=self.student.username, password="test") self.client.login(username=self.student.username, password="test")
def tearDown(self):
"""
Undo all patches.
"""
patch.stopall()
def navigate_to_email_view(self): def navigate_to_email_view(self):
"""Navigate to the instructor dash's email view""" """Navigate to the instructor dash's email view"""
# Pull up email view on instructor dashboard # Pull up email view on instructor dashboard
...@@ -54,6 +60,8 @@ class TestOptoutCourseEmails(ModuleStoreTestCase): ...@@ -54,6 +60,8 @@ class TestOptoutCourseEmails(ModuleStoreTestCase):
Make sure student does not receive course email after opting out. Make sure student does not receive course email after opting out.
""" """
url = reverse('change_email_settings') 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}) response = self.client.post(url, {'course_id': self.course.id})
self.assertEquals(json.loads(response.content), {'success': True}) self.assertEquals(json.loads(response.content), {'success': True})
......
...@@ -64,6 +64,12 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): ...@@ -64,6 +64,12 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase):
selected_email_link = '<a href="#" onclick="goto(\'Email\')" class="selectedmode">Email</a>' selected_email_link = '<a href="#" onclick="goto(\'Email\')" class="selectedmode">Email</a>'
self.assertTrue(selected_email_link in response.content) self.assertTrue(selected_email_link in response.content)
def tearDown(self):
"""
Undo all patches.
"""
patch.stopall()
def test_send_to_self(self): def test_send_to_self(self):
""" """
Make sure email send to myself goes to myself. Make sure email send to myself goes to myself.
...@@ -185,7 +191,7 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): ...@@ -185,7 +191,7 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase):
self.assertIn( self.assertIn(
u'ẗëṡẗ ṁëṡṡäġë ḟöṛ äḷḷ イ乇丂イ ᄊ乇丂丂ムg乇 キo尺 ムレレ тэѕт мэѕѕаБэ fоѓ аll', u'ẗëṡẗ ṁëṡṡäġë ḟöṛ äḷḷ イ乇丂イ ᄊ乇丂丂ムg乇 キo尺 ムレレ тэѕт мэѕѕаБэ fоѓ аll',
mail.outbox[0].body.decode('utf-8') mail.outbox[0].body
) )
def test_unicode_students_send_to_all(self): def test_unicode_students_send_to_all(self):
......
...@@ -40,8 +40,10 @@ class TestEmailErrors(ModuleStoreTestCase): ...@@ -40,8 +40,10 @@ class TestEmailErrors(ModuleStoreTestCase):
self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id}) self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id})
def tearDown(self): def tearDown(self):
self.smtp_server_thread.stop() self.smtp_server_thread.stop()
patch.stopall()
@patch('bulk_email.tasks.course_email.retry') @patch('bulk_email.tasks.course_email.retry')
def test_data_err_retry(self, retry): def test_data_err_retry(self, retry):
......
...@@ -32,6 +32,12 @@ class TestInstructorDashboardEmailView(ModuleStoreTestCase): ...@@ -32,6 +32,12 @@ class TestInstructorDashboardEmailView(ModuleStoreTestCase):
# URL for email view # URL for email view
self.email_link = '<a href="#" onclick="goto(\'Email\')" class="None">Email</a>' self.email_link = '<a href="#" onclick="goto(\'Email\')" class="None">Email</a>'
def tearDown(self):
"""
Undo all patches.
"""
patch.stopall()
@patch.dict(settings.MITX_FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True}) @patch.dict(settings.MITX_FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True})
def test_email_flag_true(self): def test_email_flag_true(self):
# Assert that the URL for the email view is in the response # Assert that the URL for the email view is in the response
......
...@@ -55,6 +55,7 @@ from mitxmako.shortcuts import render_to_string ...@@ -55,6 +55,7 @@ from mitxmako.shortcuts import render_to_string
from bulk_email.models import CourseEmail from bulk_email.models import CourseEmail
from html_to_text import html_to_text
import datetime import datetime
from hashlib import md5 from hashlib import md5
from bulk_email import tasks from bulk_email import tasks
...@@ -706,12 +707,14 @@ def instructor_dashboard(request, course_id): ...@@ -706,12 +707,14 @@ def instructor_dashboard(request, course_id):
to_option = request.POST.get("to_option") to_option = request.POST.get("to_option")
subject = request.POST.get("subject") subject = request.POST.get("subject")
html_message = request.POST.get("message") html_message = request.POST.get("message")
text_message = html_to_text(html_message)
email = CourseEmail(course_id=course_id, email = CourseEmail(course_id=course_id,
sender=request.user, sender=request.user,
to=to_option, to=to_option,
subject=subject, subject=subject,
html_message=html_message, html_message=html_message,
text_message=text_message,
hash=md5((html_message + subject + datetime.datetime.isoformat(datetime.datetime.now())).encode('utf-8')).hexdigest()) hash=md5((html_message + subject + datetime.datetime.isoformat(datetime.datetime.now())).encode('utf-8')).hexdigest())
email.save() email.save()
......
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