[Erp5-report] r15843 - /erp5/trunk/products/ERP5/Document/EmailDocument.py

nobody at svn.erp5.org nobody at svn.erp5.org
Mon Aug 27 15:32:42 CEST 2007


Author: jp
Date: Mon Aug 27 15:32:41 2007
New Revision: 15843

URL: http://svn.erp5.org?rev=15843&view=rev
Log:
A document which supports the conversion of email files into an ERP5 representation. Work in progress. Initial upload.

Added:
    erp5/trunk/products/ERP5/Document/EmailDocument.py

Added: erp5/trunk/products/ERP5/Document/EmailDocument.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/EmailDocument.py?rev=15843&view=auto
==============================================================================
--- erp5/trunk/products/ERP5/Document/EmailDocument.py (added)
+++ erp5/trunk/products/ERP5/Document/EmailDocument.py Mon Aug 27 15:32:41 2007
@@ -1,0 +1,416 @@
+##############################################################################
+#
+# Copyright (c) 2007 Nexedi SA and Contributors. All Rights Reserved.
+#                    Jean-Paul Smets-Solanes <jp at nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+##############################################################################
+
+import re, types
+from DateTime import DateTime
+from time import mktime
+from Globals import get_request
+
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type.Base import WorkflowMethod
+from Products.CMFCore.utils import getToolByName
+from Products.CMFCore.utils import _setCacheHeaders
+from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
+from Products.ERP5.Document.TextDocument import TextDocument
+from Products.ERP5.Document.File import File
+from Products.CMFDefault.utils import isHTMLSafe
+
+from email import message_from_string
+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'
+COMMASPACE = ', '
+_MARKER = []
+
+class EmailDocument(File, TextDocument):
+  """
+    EmailDocument is a File which stores its metadata in a form which
+    is similar to a TextDocument.
+    A Text Document which stores raw HTML and can 
+    convert it to various formats.
+  """
+
+  meta_type = 'ERP5 Email Document'
+  portal_type = 'Email Document'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+  isDocument = 1
+  isDelivery = 1 # XXX must be removed later - only event is a delivery
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.Version
+                    , PropertySheet.Document
+                    , PropertySheet.Snapshot
+                    , PropertySheet.ExternalDocument
+                    , PropertySheet.Url
+                    , PropertySheet.TextDocument
+                    , PropertySheet.Arrow
+                    , PropertySheet.Task
+                    , PropertySheet.ItemAggregation
+                    )
+
+  # Declarative interfaces
+  __implements__ = ()
+
+  # Mail processing API
+  def _getMessage(self):
+    result = getattr(self, '_v_message', None)
+    if result is None:
+      result = message_from_string(str(self.getData()))
+      self._v_message = result
+    return result
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getContentInformation')
+  def getContentInformation(self):
+    """
+    Returns the content information from the header information.
+    This is used by the metadata discovery system.
+    """
+    result = {}
+    for (name, value) in self._getMessage().items():
+      result[name] = value
+    return result
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getAttachmentInformationList')
+  def getAttachmentInformationList(self, **kw):
+    """
+    Returns a list of dictionnaries for every attachment. Each dictionnary
+    represents the metadata of the attachment.
+    
+    **kw - support for listbox (TODO: improve it)
+    """
+    result = []
+    i = 0
+    for part in self._getMessage().walk():
+      if not part.is_multipart():
+        kw = dict(part.items())
+        kw['uid'] = 'part_%s' % i
+        kw['index'] = i
+        if kw.has_key('Content-Disposition'):
+          content_disposition = kw['Content-Disposition']
+          if content_disposition.split(';')[0] == 'attachment':
+            kw['file_name'] = content_disposition.split(';')[1].split('=')[1] # Quick hack - make this better with re
+          elif content_disposition.split(';')[0] == 'inline':
+            kw['file_name'] = 'inline_%s' % i
+          else:
+            kw['file_name'] = 'part_%s' % i
+        result.append(kw)
+      i += 1
+    return result
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getAttachmentData')
+  def getAttachmentData(self, index):
+    """
+    Returns the decoded data of an attachment.
+    
+    TODO: add support for format in RESPONSE if defined
+    """
+    i = 0
+    for part in self._getMessage().walk():
+      if index == i:
+        return part.get_payload(decode=1)
+      i += 1
+    return KeyError, "No attachment with index %s" % index
+
+  # Overriden methods
+  security.declareProtected(Permissions.AccessContentsInformation, 'getTitle')
+  def getTitle(self, default=_MARKER):
+    """
+    Returns the title
+    """
+    if not self.hasFile():
+      # Return the standard text content if no file was provided
+      if default is _MARKER:
+        return self._baseGetTitle()
+      else:
+        return self._baseGetTitle(default)
+    return self.getContentInformation().get('Subject', '')
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getStartDate')
+  def getStartDate(self, default=_MARKER):
+    """
+    Returns the title
+    """
+    if not self.hasFile():
+      # Return the standard start date if no file was provided
+      if default is _MARKER:
+        return self._baseGetStartDate()
+      else:
+        return self._baseGetStartDate(default)
+    date_string = self.getContentInformation().get('Date', None)
+    if date_string:
+      time = mktime(parsedate(date_string))
+      if time:
+        return DateTime(time)
+    return self.getCreationDate()
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getTextContent')
+  def getTextContent(self, default=_MARKER):
+    """
+    Returns the content of the email as text. This is useful
+    to display the content of an email.
+    
+    TODO: add support for legacy objects
+    """
+    if not self.hasFile():
+      # Return the standard text content if no file was provided
+      if default is _MARKER:
+        return self._baseGetTextContent()
+      else:
+        return self._baseGetTextContent(default)
+    text_result = None
+    html_result = None
+    for part in self._getMessage().walk():
+      if part.get_content_type() == 'text/plain' and not text_result and not part.is_multipart():
+        text_result = part.get_payload(decode=1)
+      elif part.get_content_type() == 'text/html' and not html_result and not part.is_multipart():
+        return part.get_payload(decode=1)
+    return text_result
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getTextFormat')
+  def getTextFormat(self, default=_MARKER):
+    """
+    Returns the format of the email (text or html).
+    
+    TODO: add support for legacy objects
+    """
+    if not self.hasFile():
+      # Return the standard text format if no file was provided
+      if default is _MARKER:
+        return self._baseGetTextFormat()
+      else:
+        return self._baseGetTextFormat(default)
+    for part in self._getMessage().walk():
+      if part.get_content_type() == 'text/html' and not part.is_multipart():
+        return 'text/html'
+    return 'text/plain'
+
+  # Conversion API
+  def _convertToBaseFormat(self):
+    """
+      Build a structure which can be later used
+      to extract content information from this mail
+      message.
+    """
+    pass
+
+  index_html = TextDocument.index_html
+
+  security.declareProtected(Permissions.View, 'convert')
+  def convert(self, format, **kw):
+    """
+      Convert text using portal_transforms
+    """
+    # Accelerate rendering in Web mode
+    _setCacheHeaders(self, {'format' : format})
+    # Return the raw content
+    if format == 'raw':
+      return 'text/plain', self.getTextContent()
+    mime_type = getToolByName(self, 'mimetypes_registry').lookupExtension('name.%s' % format)
+    src_mimetype = self.getTextFormat(DEFAULT_TEXT_FORMAT)
+    if not src_mimetype.startswith('text/'):
+      src_mimetype = 'text/%s' % src_mimetype
+    # check if document has set text_content and convert if necessary
+    text_content = self.getTextContent()
+    if text_content is not None:
+      portal_transforms = getToolByName(self, 'portal_transforms')
+      return mime_type, portal_transforms.convertTo(mime_type,
+                                                    text_content, 
+                                                    object = self, 
+                                                    mimetype = src_mimetype)
+    else:
+      # text_content is not set, return empty string instead of None
+      return mime_type, ''
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'hasBaseData')
+  def hasBaseData(self):
+    """ 
+      Since there is no need to convert to a base format, we consider that 
+      we always have the base format if we have text of file.
+    """
+    return self.hasFile() or self.hasTextContent()
+
+  # Methods which can be useful to prepare a reply by email to an event
+  security.declareProtected(Permissions.AccessContentsInformation, 'getReplyBody')
+  def getReplyBody(self):
+    """
+      This is used in order to respond to a mail,
+      this put a '> ' before each line of the body
+    """
+    body = self.asText()
+    if body:
+      return '> ' + str(body).replace('\n', '\n> ')
+    return ''
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getReplySubject')
+  def getReplySubject(self):
+    """
+      This is used in order to respond to a mail,
+      this put a 'Re: ' before the orignal subject
+    """
+    reply_subject = self.getTitle()
+    if reply_subject.find('Re: ') != 0:
+      reply_subject = 'Re: ' + reply_subject
+    return reply_subject
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getReplyTo')
+  def getReplyTo(self):
+    """
+      Returns the send of this message based on getContentInformation
+    """
+    content_information = self.getContentInformation()
+    return content_information.get('Return-Path', content_information.get('From'))
+
+  security.declareProtected(Permissions.UseMailhostServices, 'send')
+  def send(self, from_url=None, to_url=None, reply_url=None, subject=None,
+                 body=None,
+                 attachment_format=None, download=False):
+    """
+      Sends the current event content by email. If documents are
+      attached through the aggregate category, enclose them.
+
+      from_url - the sender of this email. If not provided
+                 we will use source to find a valid
+                 email address
+
+      to_url   - the recipients of this email. If not provided
+                 we will use destination category to 
+                 find a list of valid email addresses
+
+      reply_url - the email address to reply to. If nothing
+                 is provided, use the email defined in 
+                 preferences.
+
+      subject  - a custom title. If not provided, we will use
+                 getTitle
+
+      body     - a body message If not provided, we will
+                 use the text representation of the event
+                 as body
+
+      attachment_format - defines an option format
+                 to convet attachments to (ex. application/pdf)
+
+      download - if set to True returns, the message online
+                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
+      base format rather than original format
+
+      TODO2: consider turning this method into a general method for
+      any ERP5 document.
+    """
+    # Prepare header data
+    if body is None:
+      body = self.asText()
+    if subject is None:
+      subject = self.getTitle()
+    if from_url is None:
+      from_url = self.getSourceValue().getDefaultEmailText()
+    if reply_url is None:
+      reply_url = self.portal_preferences.getPreferredEventSenderEmail()
+    if to_url is None:
+      for recipient in self.getDestinationValueList():
+        to_url = []
+        email = recipient.getDefaultEmailText()
+        if email:
+          to_url.append(email)
+        else:
+          raise ValueError, 'Recipient %s has no defined email' % recipient
+    elif type(to_url) in types.StringTypes:
+      to_url = [to_url]
+
+    # Create the container (outer) email message.
+    message = MIMEMultipart()
+    message['Subject'] = subject
+    message['From'] = from_url
+    message['To'] = COMMASPACE.join(to_url)
+    message['Return-Path'] = reply_url
+    message.preamble = 'You will not see this in a MIME-aware mail reader.\n'
+
+    # Add the body of the message
+    attached_message = MIMEText(str(body))
+    message.attach(attached_message)
+
+    # Attach files
+    document_type_list = self.getPortalDocumentTypeList()
+    for attachment in self.getAggregateValueList():
+      if attachment.getPortalType() in document_type_list:
+        # If this is a document, use 
+        mime_type = attachment.getContentType() # WARNING - this could fail since getContentType
+                                                # is not (yet) part of Document API
+        mime_type, attached_data = attachment.convert(mime_type)
+      else:
+        mime_type = 'application/pdf'
+        attached_data = attachment.asPDF() # XXX - Not implemented yet
+                                               # should provide a default printout
+      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:
+        attached_message = MIMEBase(maintype, subtype)
+        attached_message.set_payload(attached_data)
+        Encoders.encode_base64(attached_message)
+      attached_message.add_header('Content-Disposition', 'attachment', filename=attachment.getReference())
+      message.attach(attached_message)
+
+    # Send the message
+    if download:
+      return message.as_string()
+
+    self.MailHost.send(message.as_string())
+
+## Compatibility layer
+#from Products.ERP5Type import Document
+#Document.MailMessage = EmailDocument




More information about the Erp5-report mailing list