2012-05-03 03:51:54 -05:00
|
|
|
'''This package contains functions for sending email notifications.'''
|
2012-05-08 07:49:45 -05:00
|
|
|
import smtplib, socket
|
2012-05-03 03:51:54 -05:00
|
|
|
from email.MIMEMultipart import MIMEMultipart
|
|
|
|
from email.MIMEBase import MIMEBase
|
|
|
|
from email.MIMEText import MIMEText
|
|
|
|
from email import Encoders
|
|
|
|
from email.Header import Header
|
|
|
|
from appy.shared.utils import sequenceTypes
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
def sendMail(tool, to, subject, body, attachments=None):
|
|
|
|
'''Sends a mail, via p_tool.mailHost, to p_to (a single email address or a
|
|
|
|
list of email addresses).'''
|
|
|
|
# Just log things if mail is disabled
|
|
|
|
fromAddress = tool.mailFrom
|
2012-05-05 10:04:19 -05:00
|
|
|
if not tool.mailEnabled or not tool.mailHost:
|
|
|
|
if not tool.mailHost:
|
|
|
|
msg = ' (no mailhost defined)'
|
|
|
|
else:
|
|
|
|
msg = ''
|
|
|
|
tool.log('Mail disabled%s: should send mail from %s to %s.' % \
|
|
|
|
(msg, fromAddress, str(to)))
|
2012-05-03 03:51:54 -05:00
|
|
|
tool.log('Subject: %s' % subject)
|
|
|
|
tool.log('Body: %s' % body)
|
|
|
|
if attachments:
|
|
|
|
tool.log('%d attachment(s).' % len(attachments))
|
|
|
|
return
|
|
|
|
tool.log('Sending mail from %s to %s (subject: %s).' % \
|
|
|
|
(fromAddress, str(to), subject))
|
|
|
|
# Create the base MIME message
|
|
|
|
body = MIMEText(body, 'plain', 'utf-8')
|
|
|
|
if attachments:
|
|
|
|
msg = MIMEMultipart()
|
|
|
|
msg.attach( body )
|
|
|
|
else:
|
|
|
|
msg = body
|
|
|
|
# Add the header values
|
|
|
|
msg['Subject'] = Header(subject, 'utf-8')
|
|
|
|
msg['From'] = fromAddress
|
|
|
|
if isinstance(to, basestring):
|
|
|
|
msg['To'] = to
|
|
|
|
else:
|
|
|
|
if len(to) == 1:
|
|
|
|
msg['To'] = to[0]
|
|
|
|
else:
|
|
|
|
msg['To'] = fromAddress
|
|
|
|
msg['Bcc'] = ', '.join(to)
|
|
|
|
to = fromAddress
|
|
|
|
# Add attachments
|
|
|
|
if attachments:
|
|
|
|
for fileName, fileContent in attachments:
|
|
|
|
part = MIMEBase('application', 'octet-stream')
|
2012-05-08 07:49:45 -05:00
|
|
|
if fileContent.__class__.__name__ == 'FileWrapper':
|
|
|
|
fileContent = fileContent._zopeFile
|
2012-05-03 03:51:54 -05:00
|
|
|
if hasattr(fileContent, 'data'):
|
|
|
|
# It is a File instance coming from the database
|
|
|
|
data = fileContent.data
|
|
|
|
if isinstance(data, basestring):
|
|
|
|
payLoad = data
|
|
|
|
else:
|
|
|
|
payLoad = ''
|
|
|
|
while data is not None:
|
|
|
|
payLoad += data.data
|
|
|
|
data = data.next
|
|
|
|
else:
|
|
|
|
payLoad = fileContent
|
|
|
|
part.set_payload(payLoad)
|
|
|
|
Encoders.encode_base64(part)
|
|
|
|
part.add_header('Content-Disposition',
|
|
|
|
'attachment; filename="%s"' % fileName)
|
|
|
|
msg.attach(part)
|
|
|
|
# Send the email
|
|
|
|
try:
|
2012-05-05 10:04:19 -05:00
|
|
|
smtpInfo = tool.mailHost.split(':', 3)
|
|
|
|
login = password = None
|
|
|
|
if len(smtpInfo) == 2:
|
|
|
|
# We simply have server and port
|
|
|
|
server, port = smtpInfo
|
|
|
|
else:
|
|
|
|
# We also have login and password
|
|
|
|
server, port, login, password = smtpInfo
|
|
|
|
smtpServer = smtplib.SMTP(server, port=int(port))
|
|
|
|
if login:
|
|
|
|
smtpServer.login(login, password)
|
|
|
|
res = smtpServer.sendmail(fromAddress, [to], msg.as_string())
|
|
|
|
smtpServer.quit()
|
|
|
|
if res:
|
|
|
|
tool.log('Could not send mail to some recipients. %s' % str(res),
|
|
|
|
type='warning')
|
2012-05-03 03:51:54 -05:00
|
|
|
except smtplib.SMTPException, e:
|
2012-05-05 10:04:19 -05:00
|
|
|
tool.log('Mail sending failed: %s' % str(e), type='error')
|
2012-05-08 07:49:45 -05:00
|
|
|
except socket.error, se:
|
|
|
|
tool.log('Mail sending failed: %s' % str(se), type='error')
|
2012-05-03 03:51:54 -05:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
def sendNotification(obj, transition, transitionName, workflow):
|
|
|
|
'''Sends mail about p_transition named p_transitionName, that has been
|
|
|
|
triggered on p_obj that is controlled by p_workflow.'''
|
|
|
|
from appy.gen.descriptors import WorkflowDescriptor
|
|
|
|
wfName = WorkflowDescriptor.getWorkflowName(workflow.__class__)
|
|
|
|
zopeObj = obj.o
|
|
|
|
tool = zopeObj.getTool()
|
|
|
|
mailInfo = transition.notify(workflow, obj)
|
|
|
|
if not mailInfo[0]: return # Send a mail to nobody.
|
|
|
|
# mailInfo may be one of the following:
|
|
|
|
# (to,)
|
|
|
|
# (to, cc)
|
|
|
|
# (to, mailSubject, mailBody)
|
|
|
|
# (to, cc, mailSubject, mailBody)
|
|
|
|
# "to" and "cc" maybe simple strings (one simple string = one email
|
|
|
|
# address or one role) or sequences of strings.
|
|
|
|
# Determine mail subject and body.
|
|
|
|
if len(mailInfo) <= 2:
|
|
|
|
# The user didn't mention mail body and subject. We will use those
|
|
|
|
# defined from i18n labels.
|
|
|
|
wfHistory = zopeObj.getHistory()
|
|
|
|
labelPrefix = '%s_%s' % (wfName, transitionName)
|
|
|
|
tName = obj.translate(labelPrefix)
|
|
|
|
keys = {'siteUrl': tool.getPath('/').absolute_url(),
|
|
|
|
'siteTitle': tool.getAppName(),
|
|
|
|
'objectUrl': zopeObj.absolute_url(),
|
|
|
|
'objectTitle': zopeObj.Title(),
|
|
|
|
'transitionName': tName,
|
|
|
|
'transitionComment': wfHistory[0]['comments']}
|
|
|
|
mailSubject = obj.translate(labelPrefix + '_mail_subject', keys)
|
|
|
|
mailBody = obj.translate(labelPrefix + '_mail_body', keys)
|
|
|
|
else:
|
|
|
|
mailSubject = mailInfo[-1]
|
|
|
|
mailBody = mailInfo[-2]
|
|
|
|
# Determine "to" and "cc".
|
|
|
|
to = mailInfo[0]
|
|
|
|
cc = []
|
|
|
|
if (len(mailInfo) in (2,4)) and mailInfo[1]: cc = mailInfo[1]
|
|
|
|
if type(to) not in sequenceTypes: to = [to]
|
|
|
|
if type(cc) not in sequenceTypes: cc = [cc]
|
|
|
|
# Send the mail
|
|
|
|
sendMail(tool.appy(), to, mailSubject, mailBody)
|
|
|
|
# ------------------------------------------------------------------------------
|