Commit 1f49870c authored by Yusei Tahara's avatar Yusei Tahara

refactoring. now portal_notifications is a central point to send messages.


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@20865 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent bb96987c
...@@ -48,18 +48,11 @@ except ImportError: ...@@ -48,18 +48,11 @@ except ImportError:
A dummy exception class which is used when MimetypesRegistry product is A dummy exception class which is used when MimetypesRegistry product is
not installed yet. not installed yet.
""" """
from email import message_from_string from email import message_from_string
from email.Header import decode_header from email.Header import decode_header
from email.Utils import parsedate from email.Utils import parsedate
from email import Encoders
from email.Message import Message
from email.MIMEAudio import MIMEAudio
from email.MIMEBase import MIMEBase
from email.MIMEImage import MIMEImage
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
DEFAULT_TEXT_FORMAT = 'text/html' DEFAULT_TEXT_FORMAT = 'text/html'
COMMASPACE = ', ' COMMASPACE = ', '
...@@ -400,9 +393,6 @@ class EmailDocument(File, TextDocument): ...@@ -400,9 +393,6 @@ class EmailDocument(File, TextDocument):
download - if set to True returns, the message online download - if set to True returns, the message online
rather than sending it. rather than sending it.
This method is based on the examples provided by
http://docs.python.org/lib/node162.html
TODO: support conversion to base format and use TODO: support conversion to base format and use
base format rather than original format base format rather than original format
...@@ -412,11 +402,17 @@ class EmailDocument(File, TextDocument): ...@@ -412,11 +402,17 @@ class EmailDocument(File, TextDocument):
if not _checkPermission(Permissions.View, self): if not _checkPermission(Permissions.View, self):
raise Unauthorized raise Unauthorized
#
# Prepare header data # Prepare header data
#
if body is None: if body is None:
body = self.asText() body = self.asText()
# Subject
if subject is None: if subject is None:
subject = self.getTitle() subject = self.getTitle()
# From
if from_url is None: if from_url is None:
sender = self.getSourceValue() sender = self.getSourceValue()
if sender.getTitle(): if sender.getTitle():
...@@ -424,101 +420,100 @@ class EmailDocument(File, TextDocument): ...@@ -424,101 +420,100 @@ class EmailDocument(File, TextDocument):
sender.getDefaultEmailText()) sender.getDefaultEmailText())
else: else:
from_url = sender.getDefaultEmailText() from_url = sender.getDefaultEmailText()
# Return-Path
if reply_url is None: if reply_url is None:
reply_url = self.portal_preferences.getPreferredEventSenderEmail() reply_url = self.portal_preferences.getPreferredEventSenderEmail()
additional_headers = None
if reply_url:
additional_headers = {'Return-Path':reply_url}
# To (multiple)
to_url_list = []
if to_url is None: if to_url is None:
to_url = []
for recipient in self.getDestinationValueList(): for recipient in self.getDestinationValueList():
email = recipient.getDefaultEmailText() email = recipient.getDefaultEmailText()
if email: if email:
if recipient.getTitle(): if recipient.getTitle():
to_url.append('"%s" <%s>' % (recipient.getTitle(), email)) to_url_list.append('"%s" <%s>' % (recipient.getTitle(), email))
else: else:
to_url.append(email) to_url_list.append(email)
else: else:
raise ValueError, 'Recipient %s has no defined email' % recipient raise ValueError, 'Recipient %s has no defined email' % recipient
elif type(to_url) in types.StringTypes: elif type(to_url) in types.StringTypes:
to_url = [to_url] to_url_list.append(to_url)
# Not efficient but clean # Attachments
for recipient in to_url: attachment_list = []
# Create the container (outer) email message. document_type_list = self.getPortalDocumentTypeList()
message = MIMEMultipart() for attachment in self.getAggregateValueList():
message['Subject'] = subject mime_type = None
message['From'] = from_url content = None
message['To'] = recipient name = None
message['Return-Path'] = reply_url if not attachment.getPortalType() in document_type_list:
message.preamble = 'You will not see this in a MIME-aware mail reader.\n' mime_type = 'application/pdf'
content = attachment.asPDF() # XXX - Not implemented yet
# Add the body of the message else:
attached_message = MIMEText(str(body), _charset='UTF-8') #
message.attach(attached_message) # Document type attachment
#
# Attach files
document_type_list = self.getPortalDocumentTypeList() # WARNING - this could fail since getContentType
for attachment in self.getAggregateValueList(): # is not (yet) part of Document API
mime_type = None if getattr(attachment, 'getContentType', None) is not None:
attached_data = None mime_type = attachment.getContentType()
if attachment.getPortalType() in document_type_list: elif getattr(attachment, 'getTextFormat', None) is not None:
# If this is a document, use mime_type = attachment.getTextFormat()
# WARNING - this could fail since getContentType
# is not (yet) part of Document API
if getattr(attachment, 'getContentType', None) is not None:
mime_type = attachment.getContentType()
elif getattr(attachment, 'getTextFormat', None) is not None:
mime_type = attachment.getTextFormat()
else:
raise ValueError, "Cannot find mimetype of the document."
if mime_type is not None:
try:
mime_type, attached_data = attachment.convert(mime_type)
except ConversionError:
mime_type = attachment.getBaseContentType()
attached_data = attachment.getBaseData()
except (NotImplementedError, MimeTypeException):
pass
if attached_data is None:
if getattr(attachment, 'getTextContent', None) is not None:
attached_data = attachment.getTextContent()
elif getattr(attachment, 'getData', None) is not None:
attached_data = attachment.getData()
elif getattr(attachment, 'getBaseData', None) is not None:
attached_data = attachment.getBaseData()
else:
mime_type = 'application/pdf'
attached_data = attachment.asPDF() # XXX - Not implemented yet
# should provide a default printout
if not isinstance(attached_data, str):
attached_data = str(attached_data)
if not mime_type:
mime_type = 'application/octet-stream'
# Use appropriate class based on mime_type
maintype, subtype = mime_type.split('/', 1)
if maintype == 'text':
attached_message = MIMEText(attached_data, _subtype=subtype)
elif maintype == 'image':
attached_message = MIMEImage(attached_data, _subtype=subtype)
elif maintype == 'audio':
attached_message = MIMEAudio(attached_data, _subtype=subtype)
else: else:
attached_message = MIMEBase(maintype, subtype) raise ValueError, "Cannot find mimetype of the document."
attached_message.set_payload(attached_data)
Encoders.encode_base64(attached_message) if mime_type is not None:
attached_message.add_header('Content-Disposition', 'attachment', filename=attachment.getReference()) try:
message.attach(attached_message) mime_type, content = attachment.convert(mime_type)
except ConversionError:
mime_type = attachment.getBaseContentType()
content = attachment.getBaseData()
except (NotImplementedError, MimeTypeException):
pass
if content is None:
if getattr(attachment, 'getTextContent', None) is not None:
content = attachment.getTextContent()
elif getattr(attachment, 'getData', None) is not None:
content = attachment.getData()
elif getattr(attachment, 'getBaseData', None) is not None:
content = attachment.getBaseData()
if not isinstance(content, str):
content = str(content)
attachment_list.append({'mime_type':mime_type,
'content':content,
'name':attachment.getReference()}
)
portal_notifications = getToolByName(self, 'portal_notifications')
kw = {}
# Only for debugging purpose
if download:
kw = {'debug':True}
else:
portal_notifications = portal_notifications.activate(activity="SQLQueue")
# Send the message for to_url in to_url_list:
if download: result = portal_notifications.sendMessageLowLevel(
return message.as_string() # Only for debugging purpose from_url=from_url, to_url=to_url, body=body, subject=subject,
attachment_list=attachment_list,
additional_headers=additional_headers,
**kw
)
# Use activities # Send the message
self.activate(activity="SQLQueue").sendMailHostMessage(message.as_string()) if download:
return result # Only for debugging purpose
# XXX Obsolete method, Use portal_notifications instead.
security.declareProtected(Permissions.UseMailhostServices, 'sendMailHostMessage') security.declareProtected(Permissions.UseMailhostServices, 'sendMailHostMessage')
def sendMailHostMessage(self, message): def sendMailHostMessage(self, message):
""" """
......
...@@ -146,10 +146,6 @@ class Url(Coordinate, Base, UrlMixIn): ...@@ -146,10 +146,6 @@ class Url(Coordinate, Base, UrlMixIn):
* extra_headers is a dictionnary of custom headers to add to the email. * extra_headers is a dictionnary of custom headers to add to the email.
"X-" prefix is automatically added to those headers. "X-" prefix is automatically added to those headers.
""" """
# get the mailhost object
mailhost = getattr(self.getPortalObject(), 'MailHost', None)
if mailhost is None:
raise AttributeError, "Cannot find a MailHost object"
if from_url is None: if from_url is None:
from_url = self.getUrlString(None) from_url = self.getUrlString(None)
if to_url is None: if to_url is None:
...@@ -157,9 +153,9 @@ class Url(Coordinate, Base, UrlMixIn): ...@@ -157,9 +153,9 @@ class Url(Coordinate, Base, UrlMixIn):
if from_url is None or to_url is None: if from_url is None or to_url is None:
raise AttributeError, "No mail defined" raise AttributeError, "No mail defined"
message = buildEmailMessage(from_url, to_url, msg=msg, portal_notifications = getToolByName(self, 'portal_notifications')
subject=subject, attachment_list=attachment_list,
extra_headers=extra_headers)
# send mail to user portal_notifications.sendMessageLowLevel(from_url=from_url, to_url=to_url,
mailhost.send(message.as_string(), to_url, from_url) body=msg, subject=subject,
\ No newline at end of file attachment_list=attachment_list,
extra_headers=extra_headers)
...@@ -36,13 +36,16 @@ from mimetypes import guess_type ...@@ -36,13 +36,16 @@ from mimetypes import guess_type
from email.MIMEMultipart import MIMEMultipart from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText from email.MIMEText import MIMEText
from email.MIMEBase import MIMEBase from email.MIMEBase import MIMEBase
from email.MIMEAudio import MIMEAudio
from email.MIMEImage import MIMEImage
from email.Header import make_header from email.Header import make_header
from email import Encoders from email import Encoders
def buildEmailMessage(from_url, to_url, msg=None, def buildEmailMessage(from_url, to_url, msg=None,
subject=None, attachment_list=None, subject=None, attachment_list=None,
extra_headers=None): extra_headers=None,
additional_headers=None):
""" """
Builds a mail message which is ready to be Builds a mail message which is ready to be
sent by Zope MailHost. sent by Zope MailHost.
...@@ -53,6 +56,7 @@ def buildEmailMessage(from_url, to_url, msg=None, ...@@ -53,6 +56,7 @@ def buildEmailMessage(from_url, to_url, msg=None,
- mime_type: mime-type corresponding to the attachment - mime_type: mime-type corresponding to the attachment
* extra_headers is a dictionnary of custom headers to add to the email. * extra_headers is a dictionnary of custom headers to add to the email.
"X-" prefix is automatically added to those headers. "X-" prefix is automatically added to those headers.
* additional_headers is similar to extra_headers, but no prefix is added.
""" """
if attachment_list == None: if attachment_list == None:
...@@ -67,8 +71,12 @@ def buildEmailMessage(from_url, to_url, msg=None, ...@@ -67,8 +71,12 @@ def buildEmailMessage(from_url, to_url, msg=None,
message.attach(MIMEText(msg, _charset='utf-8')) message.attach(MIMEText(msg, _charset='utf-8'))
if extra_headers: if extra_headers:
for k, v in extra_headers.items(): for key, value in extra_headers.items():
message.add_header('X-%s' % k, v) message.add_header('X-%s' % key, value)
if additional_headers:
for key, value in additional_headers.items():
message.add_header(key, value)
message.add_header('Subject', message.add_header('Subject',
make_header([(subject, 'utf-8')]).encode()) make_header([(subject, 'utf-8')]).encode())
...@@ -92,10 +100,18 @@ def buildEmailMessage(from_url, to_url, msg=None, ...@@ -92,10 +100,18 @@ def buildEmailMessage(from_url, to_url, msg=None,
if attachment['mime_type'] == 'text/plain': if attachment['mime_type'] == 'text/plain':
part = MIMEText(attachment['content'], _charset='utf-8') part = MIMEText(attachment['content'], _charset='utf-8')
else: else:
# encode non-plaintext attachment in base64 major, minor = attachment['mime_type'].split('/', 1)
part = MIMEBase(*attachment['mime_type'].split('/', 1)) if major == 'text':
part.set_payload(attachment['content']) part = MIMEText(attachment['content'], _subtype=minor)
Encoders.encode_base64(part) elif major == 'image':
part = MIMEImage(attachment['content'], _subtype=minor)
elif major == 'audio':
part = MIMEAudio(attachment['content'], _subtype=minor)
else:
# encode non-plaintext attachment in base64
part = MIMEBase(major, minor)
part.set_payload(attachment['content'])
Encoders.encode_base64(part)
part.add_header('Content-Disposition', part.add_header('Content-Disposition',
'attachment; filename=%s' % attachment_name) 'attachment; filename=%s' % attachment_name)
...@@ -133,6 +149,24 @@ class NotificationTool(BaseTool): ...@@ -133,6 +149,24 @@ class NotificationTool(BaseTool):
security.declareProtected( Permissions.ManagePortal, 'manage_overview' ) security.declareProtected( Permissions.ManagePortal, 'manage_overview' )
manage_overview = DTMLFile( 'explainNotificationTool', _dtmldir ) manage_overview = DTMLFile( 'explainNotificationTool', _dtmldir )
# XXX Bad Name...Any Idea?
security.declareProtected(Permissions.UseMailhostServices, 'sendMessageLowLevel')
def sendMessageLowLevel(self, from_url, to_url, body=None, subject=None,
attachment_list=None, extra_headers=None, additional_headers=None,
debug=False):
portal = self.getPortalObject()
mailhost = getattr(portal, 'MailHost', None)
if mailhost is None:
raise ValueError, "Can't find MailHost."
message = buildEmailMessage(from_url, to_url, msg=body, subject=subject,
attachment_list=attachment_list, extra_headers=extra_headers,
additional_headers=additional_headers)
if debug:
return message.as_string()
mailhost.send(messageText=message.as_string(), mto=to_url, mfrom=from_url)
security.declareProtected(Permissions.UseMailhostServices, 'sendMessage') security.declareProtected(Permissions.UseMailhostServices, 'sendMessage')
def sendMessage(self, sender=None, recipient=None, subject=None, def sendMessage(self, sender=None, recipient=None, subject=None,
message=None, attachment_list=None, message=None, attachment_list=None,
...@@ -171,9 +205,6 @@ class NotificationTool(BaseTool): ...@@ -171,9 +205,6 @@ class NotificationTool(BaseTool):
""" """
portal = self.getPortalObject() portal = self.getPortalObject()
catalog_tool = getToolByName(self, 'portal_catalog') catalog_tool = getToolByName(self, 'portal_catalog')
mailhost = getattr(portal, 'MailHost', None)
if mailhost is None:
raise ValueError, "Can't find MailHost."
# Find Default Values # Find Default Values
default_from_email = portal.email_from_address default_from_email = portal.email_from_address
...@@ -214,14 +245,12 @@ class NotificationTool(BaseTool): ...@@ -214,14 +245,12 @@ class NotificationTool(BaseTool):
# Build and Send Messages # Build and Send Messages
for to_address in to_address_list: for to_address in to_address_list:
mail_message = buildEmailMessage(from_url=from_address, self.sendMessageLowLevel(from_url=from_address,
to_url=to_address, to_url=to_address,
msg=message, body=message,
subject=subject, subject=subject,
attachment_list=attachment_list attachment_list=attachment_list
) )
mailhost.send(mail_message.as_string(), to_address, from_address)
return return
# Future implemetation could consist in implementing # Future implemetation could consist in implementing
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
import unittest import unittest
import os import os
import email import email
import email.Header
from Products.ERP5Type.tests.utils import DummyMailHost from Products.ERP5Type.tests.utils import DummyMailHost
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
...@@ -515,7 +516,8 @@ class TestCRMMailSend(ERP5TypeTestCase): ...@@ -515,7 +516,8 @@ class TestCRMMailSend(ERP5TypeTestCase):
message = email.message_from_string(messageText) message = email.message_from_string(messageText)
self.assertEquals('A Mail', message['Subject']) self.assertEquals('A Mail',
email.Header.decode_header(message['Subject'])[0][0])
part = None part = None
for i in message.get_payload(): for i in message.get_payload():
if i.get_content_type()=='text/plain': if i.get_content_type()=='text/plain':
...@@ -598,7 +600,8 @@ class TestCRMMailSend(ERP5TypeTestCase): ...@@ -598,7 +600,8 @@ class TestCRMMailSend(ERP5TypeTestCase):
message = email.message_from_string(messageText) message = email.message_from_string(messageText)
self.assertEquals('Héhé', message['Subject']) self.assertEquals('Héhé',
email.Header.decode_header(message['Subject'])[0][0])
part = None part = None
for i in message.get_payload(): for i in message.get_payload():
if i.get_content_type()=='text/plain': if i.get_content_type()=='text/plain':
......
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