From: Andrew V. <av...@us...> - 2005-03-26 11:40:08
|
Update of /cvsroot/mailmanager/mailmanager In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv4400 Modified Files: Tag: db-backend MailManager.py Log Message: Mailing in messages now works. Caveat - all messages added to ticket number 1. Index: MailManager.py =================================================================== RCS file: /cvsroot/mailmanager/mailmanager/MailManager.py,v retrieving revision 1.147.2.15 retrieving revision 1.147.2.16 diff -u -d -r1.147.2.15 -r1.147.2.16 --- MailManager.py 25 Mar 2005 18:11:01 -0000 1.147.2.15 +++ MailManager.py 26 Mar 2005 11:40:00 -0000 1.147.2.16 @@ -37,6 +37,9 @@ import email.Utils from calendar import monthrange +# Psycopg +from psycopg import Binary + # Constants. mversion = 9 @@ -201,22 +204,28 @@ # Add the sql methods manage_addDirectoryView(self, os.path.join('MailManager', 'sql')) - # This SQL can't be added from a directory view because we want to - # use a Pluggable Brain. - manage_addZSQLMethod(self, id='ticket', title='', - connection_id='mailmanager_db', arguments='id', - template='SELECT * FROM mm_ticket WHERE ' - '<dtml-sqltest id type=int>') + # These ZSQL method can't be added from a directory view because we want + # to use a Pluggable Brain. + manage_addZSQLMethod(self, id='account', title='', + connection_id='mailmanager_db', arguments='email', + template='SELECT * FROM mm_account WHERE ' + '<dtml-sqltest email type=nb>') manage_addZSQLMethod(self, id='attachment', title='', connection_id='mailmanager_db', arguments='id', template='SELECT * FROM mm_attachment WHERE ' '<dtml-sqltest id type=int>') + manage_addZSQLMethod(self, id='ticket', title='', + connection_id='mailmanager_db', arguments='id', + template='SELECT * FROM mm_ticket WHERE ' + '<dtml-sqltest id type=int>') # Set the pluggable brain and allow direct traversal - self.ticket.manage_advanced(1, 1, 0, 'TicketPluggableBrain', - 'MailManager.TicketPluggableBrain', 1) self.attachment.manage_advanced(1, 1, 0, 'AttachPluggableBrain', 'MailManager.AttachPluggableBrain', 1) + self.account.manage_advanced(1, 1, 0, 'AccountPluggableBrain', + 'MailManager.AccountPluggableBrain', 1) + self.ticket.manage_advanced(1, 1, 0, 'TicketPluggableBrain', + 'MailManager.TicketPluggableBrain', 1) # Use cookie based authentication. manage_addCC(self, 'Cookie') @@ -323,15 +332,6 @@ # Convenience Functions for Page Templates - security.declareProtected('MailManager Settings', 'getAccount') - def getAccount(self, account_id): - """Gets an account object or returns nothing. - - Used in AccountSettings. Avoids raising an exception. - """ - if account_id in self.accounts.objectIds(): - return self.accounts[account_id] - security.declareProtected('View MailManager', 'formatTarget') def formatTarget(self, target): """Convert a response target in days to a human readable form. @@ -404,6 +404,139 @@ # Ticket Methods + security.declarePrivate('addMessageToTicket') + def addMessageToTicket(self, ticket_id, msg, from_name=None): + """Adds an email message to an existing ticket.""" + body = '' + body_charset = 'us-ascii' + html_body = '' + fname, femail = email.Utils.parseaddr(msg.get('from')) + id = self.sql.getNextMessageId()[0].id + + # Get the message body, html_body and attachments. + counter = 1 + for part, parents in walk_with_parents(msg): + if part.is_multipart(): + continue + ctype = part.get_content_type() + if ctype == 'application/applefile': + continue # skip AppleDouble resource files per RFC1740 + payload = part.get_payload(decode=1) + if payload is not None: + filename = self._isfile(part) + if filename is None: + msg_container = self._isforwarded(parents) + if filename is not None: + # An attached file + isfile = True + title = filename + elif msg_container is not None: + # Part of a forwarded message (excluding attached files). + # XXX Doesn't work for my message/delivery-status example. + isfile = False + title = 'Forwarded message%s' % self._getname(part, parents, + msg_container) + elif ctype == 'text/plain' and not body: + # The main message body in plain text. + body = payload + part.set_payload('body') # XXX what's going on here? + body_charset = part.get_content_charset('us-ascii') + continue + elif self._ishtmlmain(ctype, parents, html_body): + html_body = payload + part.set_payload('html_body') # XXX what's going on here? + continue + else: + # This shouldn't often happen. + isfile = False + title = 'Attachment%s' % self._getname(part, parents) + # If we get here it's an attachment. + self.sql.addAttachment(message_id=id, + title=title, + content_type=ctype, + is_file=str(isfile), + body=Binary(payload), + dangerous='False') + + self.sql.addMessage(id=id, + ticket_id=ticket_id, + message_id=msg.get('message-id'), + from_name=from_name or fname, + subject=msg.get('subject'), + body=body, + body_charset=body_charset, + html_body=html_body) + + security.declarePrivate('_isfile') + def _isfile(self, part): + """Test whether subpart is an attached file. + + If it is return filename. + """ + filename = self._getparam(part, 'name') + if filename is None: + filename = self._getparam(part, 'filename', 'content-disposition') + return filename + + security.declarePrivate('_getparam') + def _getparam(self, msg, param, header='content-type', failobj=None): + """Get a parameter from a header. + + Currently only used to determine the name of attachments. + """ + param = msg.get_param(param, failobj, header) + if isinstance(param, type(())): + # This forces a parameter to be UTF-8 encoded, but other 8 bit + # charsets may slip through by, illegally, not being RFC 2231 + # encoded in the first place. Or is it the case that 8 bit + # chars can NEVER be transmitted? Cf. MailMixin._decodeHeader(). + param = unicode(param[2], param[0] or 'us-ascii').encode('utf-8') + return param + + security.declarePrivate('_isforwarded') + def _isforwarded(self, parents): + """Test whether subpart is part of a forwarded message. + + If it is return container of forwarded message. + + Attached files in forwarded messages will already have been + handled as regular attached files. + """ + for parent in parents: + if parent.get_content_type().startswith('message'): + return parent + return None + + security.declarePrivate('_ishtmlmain') + def _ishtmlmain(self, ctype, parents, html_body): + """Test whether subpart is the main message in HTML. + + An HTML message's immediate container should be + 'multipart/alternative' but I have seen a whole message with + type 'text/html'. In such a case what it contains must count + as the main HTML message. + """ + if (ctype == 'text/html' and not html_body and + (not parents or + parents[0].get_content_type() == 'multipart/alternative')): + return True + return False + + security.declarePrivate('_getname') + def _getname(self, part, parents, msg_container=None): + """Get a reasonable title for a forwarded message.""" + name = self.getHeader(part, 'content-description') + if not name and msg_container is not None: # forwarded message + name = self.getHeader(msg_container, 'content-description') + if not name: + name = self.getHeader(part, 'subject') + if (not name and parents and # HTML alternative + parents[0].get_content_type() == 'multipart/alternative'): + name = self.getHeader(parents[0], 'subject') + if name: + name = ': ' + name + return name + security.declareProtected('MailManager Create Tickets', 'addAttachment') def addAttachment(self, REQUEST, standard_attach = None, file_attach=None): """Add an attachment to a reply. @@ -1524,3 +1657,11 @@ def __init__(self, group, name): self.group = group self.name = name + +def walk_with_parents(msg, parents=[]): + """Return a list of subparts in message paired with lists of parents.""" + parts = [(msg, parents)] + if msg.is_multipart(): + for part in msg.get_payload(): + parts.extend(walk_with_parents(part, [msg] + parents)) + return parts |