From a8a511fd3c863c82bfa6b7b942b4cdab1983d922 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 24 Apr 2012 15:09:49 -0400 Subject: [PATCH] Pull _construct_email out of construct_email and add construct_response. _construct_email() handles general tasks for setting up the main email header. construct_email() combines this with a simple text/plain body, while construct_response() uses a multipart/mixed format suitable for sending message receipts to users. --- pygrader/email.py | 134 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 103 insertions(+), 31 deletions(-) diff --git a/pygrader/email.py b/pygrader/email.py index 3839e51..4fab66b 100644 --- a/pygrader/email.py +++ b/pygrader/email.py @@ -19,6 +19,8 @@ from __future__ import absolute_import from email.header import Header as _Header from email.header import decode_header as _decode_header +from email.mime.message import MIMEMessage as _MIMEMessage +from email.mime.multipart import MIMEMultipart as _MIMEMultipart import email.utils as _email_utils import logging as _logging import smtplib as _smtplib @@ -150,6 +152,52 @@ def get_address(person, header=False): return _email_utils.formataddr((name, person.emails[0])) return _email_utils.formataddr((person.name, person.emails[0])) +def _construct_email(author, targets, subject, message, cc=None): + if author.pgp_key: + signers = [author.pgp_key] + else: + signers = [] + recipients = [p.pgp_key for p in targets if p.pgp_key] + encrypt = True + for person in targets: + if not person.pgp_key: + encrypt = False # cannot encrypt to every recipient + break + if cc: + recipients.extend([p.pgp_key for p in cc if p.pgp_key]) + for person in cc: + if not person.pgp_key: + encrypt = False + break + if not recipients: + encrypt = False # noone to encrypt to + if signers and encrypt: + if author.pgp_key not in recipients: + recipients.append(author.pgp_key) + message = _pgp_mime.sign_and_encrypt( + message=message, signers=signers, recipients=recipients, + always_trust=True) + elif signers: + message = _pgp_mime.sign(message=message, signers=signers) + elif encrypt: + message = _pgp_mime.encrypt(message=message, recipients=recipients) + + message['Date'] = _email_utils.formatdate() + message['From'] = get_address(author, header=True) + message['Reply-to'] = message['From'] + message['To'] = ', '.join( + get_address(target, header=True) for target in targets) + if cc: + message['Cc'] = ', '.join( + get_address(target, header=True) for target in cc) + subject_encoding = _pgp_mime.guess_encoding(subject) + if subject_encoding == 'us-ascii': + message['Subject'] = subject + else: + message['Subject'] = _Header(subject, subject_encoding) + + return message + def construct_email(author, targets, subject, text, cc=None): r"""Build a text/plain email using `Person` instances @@ -192,35 +240,59 @@ def construct_email(author, targets, subject, text, cc=None): RnVua3kg4pyJLg== """ - msg = _pgp_mime.encodedMIMEText(text) - if author.pgp_key: - signers = [author.pgp_key] - else: - signers = [] - recipients = [p.pgp_key for p in targets if p.pgp_key] - if signers and recipients: - if author.pgp_key not in recipients: - recipients.append(author.pgp_key) - msg = _pgp_mime.sign_and_encrypt( - message=msg, signers=signers, recipients=recipients, - always_trust=True) - elif signers: - msg = _pgp_mime.sign(message=msg, signers=signers) - elif recipients: - msg = _pgp_mime.encrypt(message=msg, recipients=recipients) - - msg['Date'] = _email_utils.formatdate() - msg['From'] = get_address(author, header=True) - msg['Reply-to'] = msg['From'] - msg['To'] = ', '.join( - get_address(target, header=True) for target in targets) - if cc: - msg['Cc'] = ', '.join( - get_address(target, header=True) for target in cc) - subject_encoding = _pgp_mime.guess_encoding(subject) - if subject_encoding == 'us-ascii': - msg['Subject'] = subject - else: - msg['Subject'] = _Header(subject, subject_encoding) + message = _pgp_mime.encodedMIMEText(text) + return _construct_email( + author=author, targets=targets, subject=subject, message=message, + cc=cc) + +def construct_response(author, targets, subject, text, original, cc=None): + r"""Build a multipart/mixed response email using `Person` instances - return msg + >>> from pygrader.model.person import Person as Person + >>> student = Person(name='Джон Доу', emails=['jdoe@a.gov.ru']) + >>> assistant = Person(name='Jill', emails=['c@d.net']) + >>> cc = [assistant] + >>> msg = construct_email(author=student, targets=[assistant], + ... subject='Assignment 1 submission', text='Bla bla bla...') + >>> rsp = construct_response(author=assistant, targets=[student], + ... subject='Received assignment 1 submission', text='3 hours late', + ... original=msg) + >>> print(rsp.as_string()) # doctest: +REPORT_UDIFF, +ELLIPSIS + Content-Type: multipart/mixed; boundary="===============...==" + MIME-Version: 1.0 + Date: ... + From: Jill + Reply-to: Jill + To: =?utf-8?b?0JTQttC+0L0g0JTQvtGD?= + Subject: Received assignment 1 submission + + --===============...== + Content-Type: text/plain; charset="us-ascii" + MIME-Version: 1.0 + Content-Transfer-Encoding: 7bit + Content-Disposition: inline + + 3 hours late + --===============...== + Content-Type: message/rfc822 + MIME-Version: 1.0 + + Content-Type: text/plain; charset="us-ascii" + MIME-Version: 1.0 + Content-Transfer-Encoding: 7bit + Content-Disposition: inline + Date: ... + From: =?utf-8?b?0JTQttC+0L0g0JTQvtGD?= + Reply-to: =?utf-8?b?0JTQttC+0L0g0JTQvtGD?= + To: Jill + Subject: Assignment 1 submission + + Bla bla bla... + --===============...==-- + """ + message = _MIMEMultipart('mixed') + message.attach(_pgp_mime.encodedMIMEText(text)) + message.attach(_MIMEMessage(original)) + return _construct_email( + author=author, targets=targets, subject=subject, message=message, + cc=cc) -- 2.26.2