2011-07-22 16:34:57 +00:00
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
2012-03-13 13:59:49 +00:00
# Copyright (C) 2010-today OpenERP SA (<http://www.openerp.com>)
2011-07-22 16:34:57 +00:00
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
2012-01-18 11:18:55 +00:00
import ast
2011-07-22 16:34:57 +00:00
import base64
2011-08-09 23:44:28 +00:00
import dateutil . parser
2011-07-22 16:34:57 +00:00
import email
import logging
import re
import time
2012-02-06 17:19:11 +00:00
import datetime
2011-07-22 16:34:57 +00:00
from email . header import decode_header
2011-09-07 15:13:48 +00:00
from email . message import Message
2011-07-22 16:34:57 +00:00
import tools
from osv import osv
from osv import fields
from tools . translate import _
2012-03-19 14:53:31 +00:00
from openerp import SUPERUSER_ID
2011-07-22 16:34:57 +00:00
_logger = logging . getLogger ( ' mail ' )
def format_date_tz ( date , tz = None ) :
if not date :
return ' n/a '
format = tools . DEFAULT_SERVER_DATETIME_FORMAT
return tools . server_to_local_timestamp ( date , format , format , tz )
def truncate_text ( text ) :
lines = text and text . split ( ' \n ' ) or [ ]
if len ( lines ) > 3 :
res = ' \n \t ' . join ( lines [ : 3 ] ) + ' ... '
else :
res = ' \n \t ' . join ( lines )
return res
def decode ( text ) :
""" Returns unicode() string conversion of the the given encoded smtp header text """
if text :
text = decode_header ( text . replace ( ' \r ' , ' ' ) )
return ' ' . join ( [ tools . ustr ( x [ 0 ] , x [ 1 ] ) for x in text ] )
def to_email ( text ) :
""" Return a list of the email addresses found in ``text`` """
if not text : return [ ]
return re . findall ( r ' ([^ ,<@]+@[^> ,]+) ' , text )
class mail_message_common ( osv . osv_memory ) :
""" Common abstract class for holding the main attributes of a
message object . It could be reused as parent model for any
database model or wizard screen that needs to hold a kind of
message """
2012-03-27 08:10:11 +00:00
def get_body ( self , cr , uid , ids , name , arg , context = None ) :
if context is None :
context = { }
result = dict . fromkeys ( ids , ' ' )
for message in self . browse ( cr , uid , ids , context = context ) :
if message . subtype == ' html ' :
result [ message . id ] = message . body_html
else :
result [ message . id ] = message . body_text
return result
def search_body ( self , cr , uid , obj , name , args , context = None ) :
2012-04-02 11:50:02 +00:00
""" will receive:
2012-03-27 08:10:11 +00:00
- obj : mail . message object
- name : ' body '
- args : [ ( ' body ' , ' ilike ' , ' blah ' ) ] """
return [ ' | ' , ' & ' , ( ' subtype ' , ' = ' , ' html ' ) , ( ' body_html ' , args [ 0 ] [ 1 ] , args [ 0 ] [ 2 ] ) , ( ' body_text ' , args [ 0 ] [ 1 ] , args [ 0 ] [ 2 ] ) ]
2012-03-27 07:30:53 +00:00
def get_record_name ( self , cr , uid , ids , name , arg , context = None ) :
if context is None :
context = { }
result = dict . fromkeys ( ids , ' ' )
for message in self . browse ( cr , uid , ids , context = context ) :
if not message . model or not message . res_id :
continue
2012-04-02 11:50:02 +00:00
result [ message . id ] = self . pool . get ( message . model ) . name_get ( cr , uid , [ message . res_id ] , context = context ) [ 0 ] [ 1 ]
2012-03-27 07:30:53 +00:00
return result
2011-07-22 16:34:57 +00:00
_name = ' mail.message.common '
_rec_name = ' subject '
_columns = {
' subject ' : fields . char ( ' Subject ' , size = 512 , required = True ) ,
2012-04-23 12:18:28 +00:00
' model ' : fields . char ( ' Related Document Model ' , size = 128 , select = 1 ) ,
2012-03-13 13:59:49 +00:00
' res_id ' : fields . integer ( ' Related Document ID ' , select = 1 ) ,
2012-04-23 12:18:28 +00:00
' record_name ' : fields . function ( get_record_name , type = ' string ' , string = ' Message Record Name ' ,
2012-04-02 11:50:02 +00:00
help = " Name of the record, matching the result of the name_get. " ) ,
2011-07-22 16:34:57 +00:00
' date ' : fields . datetime ( ' Date ' ) ,
2012-03-13 13:59:49 +00:00
' email_from ' : fields . char ( ' From ' , size = 128 , help = ' Message sender, taken from user preferences. ' ) ,
2011-07-22 16:34:57 +00:00
' email_to ' : fields . char ( ' To ' , size = 256 , help = ' Message recipients ' ) ,
' email_cc ' : fields . char ( ' Cc ' , size = 256 , help = ' Carbon copy message recipients ' ) ,
' email_bcc ' : fields . char ( ' Bcc ' , size = 256 , help = ' Blind carbon copy message recipients ' ) ,
2011-08-22 17:16:59 +00:00
' reply_to ' : fields . char ( ' Reply-To ' , size = 256 , help = ' Preferred response address for the message ' ) ,
2012-04-23 12:18:28 +00:00
' headers ' : fields . text ( ' Message Headers ' , readonly = 1 ,
2012-03-27 08:10:11 +00:00
help = " Full message headers, e.g. SMTP session headers (usually available on inbound messages only) " ) ,
2011-07-22 16:34:57 +00:00
' message_id ' : fields . char ( ' Message-Id ' , size = 256 , help = ' Message unique identifier ' , select = 1 , readonly = 1 ) ,
' references ' : fields . text ( ' References ' , help = ' Message references, such as identifiers of previous messages ' , readonly = 1 ) ,
2012-04-23 12:18:28 +00:00
' subtype ' : fields . char ( ' Message Type ' , size = 32 , help = " Type of message, usually ' html ' or ' plain ' , used to "
2011-07-22 16:34:57 +00:00
" select plaintext or rich text contents accordingly " , readonly = 1 ) ,
2012-04-23 12:18:28 +00:00
' body_text ' : fields . text ( ' Text Contents ' , help = " Plain-text version of the message " ) ,
' body_html ' : fields . text ( ' Rich-text Contents ' , help = " Rich-text/HTML version of the message " ) ,
' body ' : fields . function ( get_body , fnct_search = search_body , string = ' Message Content ' , type = ' text ' ,
2012-03-27 08:10:11 +00:00
help = " Content of the message. This content equals the body_text field for plain-test messages, and body_html for rich-text/HTML messages. This allows having one field if we want to access the content matching the message subtype. " ) ,
2012-05-25 13:35:36 +00:00
' parent_id ' : fields . many2one ( ' mail.message ' , ' Parent Message ' , help = " Parent message, used for displaying as threads with hierarchy " ,
select = True , ondelete = ' set null ' , ) ,
2012-04-23 12:18:28 +00:00
' child_ids ' : fields . one2many ( ' mail.message ' , ' parent_id ' , ' Child Messages ' ) ,
2011-07-22 16:34:57 +00:00
}
_defaults = {
2012-02-06 17:19:11 +00:00
' subtype ' : ' plain ' ,
2012-03-13 13:59:49 +00:00
' date ' : ( lambda * a : fields . datetime . now ( ) ) ,
2011-07-22 16:34:57 +00:00
}
class mail_message ( osv . osv ) :
2012-03-13 13:59:49 +00:00
''' Model holding messages: system notification (replacing res.log
notifications ) , comments ( for OpenSocial feature ) and
RFC2822 email messages . This model also provides facilities to
2012-03-22 12:08:36 +00:00
parse , queue and send new email messages . Type of messages
are differentiated using the ' type ' column .
2011-07-22 16:34:57 +00:00
The ` ` display_text ` ` field will have a slightly different
presentation for real emails and for log messages .
'''
_name = ' mail.message '
_inherit = ' mail.message.common '
2012-04-02 11:50:02 +00:00
_description = ' Mail Message (email, comment, notification) '
2011-07-22 16:34:57 +00:00
_order = ' date desc '
# XXX to review - how to determine action to use?
def open_document ( self , cr , uid , ids , context = None ) :
action_data = False
if ids :
msg = self . browse ( cr , uid , ids [ 0 ] , context = context )
model = msg . model
res_id = msg . res_id
ir_act_window = self . pool . get ( ' ir.actions.act_window ' )
action_ids = ir_act_window . search ( cr , uid , [ ( ' res_model ' , ' = ' , model ) ] )
if action_ids :
action_data = ir_act_window . read ( cr , uid , action_ids [ 0 ] , context = context )
action_data . update ( {
' domain ' : " [( ' id ' , ' = ' , %d )] " % ( res_id ) ,
' nodestroy ' : True ,
' context ' : { }
} )
return action_data
# XXX to review - how to determine action to use?
def open_attachment ( self , cr , uid , ids , context = None ) :
action_data = False
action_pool = self . pool . get ( ' ir.actions.act_window ' )
message = self . browse ( cr , uid , ids , context = context ) [ 0 ]
att_ids = [ x . id for x in message . attachment_ids ]
action_ids = action_pool . search ( cr , uid , [ ( ' res_model ' , ' = ' , ' ir.attachment ' ) ] )
if action_ids :
action_data = action_pool . read ( cr , uid , action_ids [ 0 ] , context = context )
action_data . update ( {
' domain ' : [ ( ' id ' , ' in ' , att_ids ) ] ,
' nodestroy ' : True
} )
return action_data
def _get_display_text ( self , cr , uid , ids , name , arg , context = None ) :
if context is None :
context = { }
tz = context . get ( ' tz ' )
result = { }
2012-03-19 14:53:31 +00:00
# Read message as UID 1 to allow viewing author even if from different company
for message in self . browse ( cr , SUPERUSER_ID , ids ) :
2011-07-22 16:34:57 +00:00
msg_txt = ' '
if message . email_from :
msg_txt + = _ ( ' %s wrote on %s : \n Subject: %s \n \t ' ) % ( message . email_from or ' / ' , format_date_tz ( message . date , tz ) , message . subject )
2011-08-23 17:58:09 +00:00
if message . body_text :
msg_txt + = truncate_text ( message . body_text )
2011-07-22 16:34:57 +00:00
else :
msg_txt = ( message . user_id . name or ' / ' ) + _ ( ' on ' ) + format_date_tz ( message . date , tz ) + ' : \n \t '
2012-02-22 09:53:42 +00:00
msg_txt + = ( message . subject or ' ' )
2011-07-22 16:34:57 +00:00
result [ message . id ] = msg_txt
return result
2012-02-07 17:07:46 +00:00
2011-07-22 16:34:57 +00:00
_columns = {
2012-04-02 16:24:25 +00:00
' type ' : fields . selection ( [
( ' email ' , ' e-mail ' ) ,
( ' comment ' , ' Comment ' ) ,
( ' notification ' , ' System notification ' ) ,
] , ' Type ' , help = " Message type: e-mail for e-mail message, notification for system message, comment for other messages such as user replies " ) ,
2011-07-22 16:34:57 +00:00
' partner_id ' : fields . many2one ( ' res.partner ' , ' Related partner ' ) ,
2012-04-24 06:05:32 +00:00
' user_id ' : fields . many2one ( ' res.users ' , ' Related User ' , readonly = 1 ) ,
2011-07-22 16:34:57 +00:00
' attachment_ids ' : fields . many2many ( ' ir.attachment ' , ' message_attachment_rel ' , ' message_id ' , ' attachment_id ' , ' Attachments ' ) ,
' display_text ' : fields . function ( _get_display_text , method = True , type = ' text ' , size = " 512 " , string = ' Display Text ' ) ,
' mail_server_id ' : fields . many2one ( ' ir.mail_server ' , ' Outgoing mail server ' , readonly = 1 ) ,
' state ' : fields . selection ( [
( ' outgoing ' , ' Outgoing ' ) ,
( ' sent ' , ' Sent ' ) ,
( ' received ' , ' Received ' ) ,
2011-09-06 16:52:04 +00:00
( ' exception ' , ' Delivery Failed ' ) ,
2011-07-22 16:34:57 +00:00
( ' cancel ' , ' Cancelled ' ) ,
2012-05-04 11:57:48 +00:00
] , ' Status ' , readonly = True ) ,
2011-09-12 12:27:58 +00:00
' auto_delete ' : fields . boolean ( ' Auto Delete ' , help = " Permanently delete this email after sending it, to save space " ) ,
2011-09-07 15:13:48 +00:00
' original ' : fields . binary ( ' Original ' , help = " Original version of the message, as it was sent on the network " , readonly = 1 ) ,
2011-07-22 16:34:57 +00:00
}
2012-02-07 17:07:46 +00:00
2011-08-23 17:58:09 +00:00
_defaults = {
2012-04-03 17:34:49 +00:00
' type ' : ' email ' ,
2011-09-07 15:15:22 +00:00
' state ' : ' received ' ,
2011-08-23 17:58:09 +00:00
}
2012-02-02 11:26:57 +00:00
2012-02-02 09:48:45 +00:00
#------------------------------------------------------
# E-Mail api
#------------------------------------------------------
2012-01-31 15:54:12 +00:00
2011-07-22 16:34:57 +00:00
def init ( self , cr ) :
cr . execute ( """ SELECT indexname FROM pg_indexes WHERE indexname = ' mail_message_model_res_id_idx ' """ )
if not cr . fetchone ( ) :
cr . execute ( """ CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id) """ )
2011-08-23 17:58:09 +00:00
def copy ( self , cr , uid , id , default = None , context = None ) :
""" Overridden to avoid duplicating fields that are unique to each email """
if default is None :
default = { }
default . update ( message_id = False , original = False , headers = False )
return super ( mail_message , self ) . copy ( cr , uid , id , default = default , context = context )
2011-07-22 16:34:57 +00:00
def schedule_with_attach ( self , cr , uid , email_from , email_to , subject , body , model = False , email_cc = None ,
email_bcc = None , reply_to = False , attachments = None , message_id = False , references = False ,
res_id = False , subtype = ' plain ' , headers = None , mail_server_id = False , auto_delete = False ,
context = None ) :
""" Schedule sending a new email message, to be sent the next time the mail scheduler runs, or
the next time : meth : ` process_email_queue ` is called explicitly .
: param string email_from : sender email address
: param list email_to : list of recipient addresses ( to be joined with commas )
: param string subject : email subject ( no pre - encoding / quoting necessary )
: param string body : email body , according to the ` ` subtype ` ` ( by default , plaintext ) .
If html subtype is used , the message will be automatically converted
to plaintext and wrapped in multipart / alternative .
: param list email_cc : optional list of string values for CC header ( to be joined with commas )
: param list email_bcc : optional list of string values for BCC header ( to be joined with commas )
: param string model : optional model name of the document this mail is related to ( this will also
be used to generate a tracking id , used to match any response related to the
same document )
: param int res_id : optional resource identifier this mail is related to ( this will also
be used to generate a tracking id , used to match any response related to the
same document )
: param string reply_to : optional value of Reply - To header
: param string subtype : optional mime subtype for the text body ( usually ' plain ' or ' html ' ) ,
must match the format of the ` ` body ` ` parameter . Default is ' plain ' ,
making the content part of the mail " text/plain " .
2011-08-22 17:16:59 +00:00
: param dict attachments : map of filename to filecontents , where filecontents is a string
2011-07-22 16:34:57 +00:00
containing the bytes of the attachment
: param dict headers : optional map of headers to set on the outgoing mail ( may override the
other headers , including Subject , Reply - To , Message - Id , etc . )
: param int mail_server_id : optional id of the preferred outgoing mail server for this mail
: param bool auto_delete : optional flag to turn on auto - deletion of the message after it has been
successfully sent ( default to False )
"""
if context is None :
context = { }
if attachments is None :
attachments = { }
attachment_obj = self . pool . get ( ' ir.attachment ' )
for param in ( email_to , email_cc , email_bcc ) :
if param and not isinstance ( param , list ) :
param = [ param ]
msg_vals = {
' subject ' : subject ,
' date ' : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
' user_id ' : uid ,
' model ' : model ,
' res_id ' : res_id ,
2012-03-28 09:02:53 +00:00
' type ' : ' email ' ,
2011-09-06 08:00:14 +00:00
' body_text ' : body if subtype != ' html ' else False ,
2011-07-22 16:34:57 +00:00
' body_html ' : body if subtype == ' html ' else False ,
' email_from ' : email_from ,
' email_to ' : email_to and ' , ' . join ( email_to ) or ' ' ,
' email_cc ' : email_cc and ' , ' . join ( email_cc ) or ' ' ,
' email_bcc ' : email_bcc and ' , ' . join ( email_bcc ) or ' ' ,
' reply_to ' : reply_to ,
' message_id ' : message_id ,
' references ' : references ,
' subtype ' : subtype ,
' headers ' : headers , # serialize the dict on the fly
' mail_server_id ' : mail_server_id ,
' state ' : ' outgoing ' ,
' auto_delete ' : auto_delete
}
email_msg_id = self . create ( cr , uid , msg_vals , context )
attachment_ids = [ ]
2011-08-22 17:16:59 +00:00
for fname , fcontent in attachments . iteritems ( ) :
2011-07-22 16:34:57 +00:00
attachment_data = {
' name ' : fname ,
' datas_fname ' : fname ,
2012-03-08 15:56:50 +00:00
' datas ' : fcontent and fcontent . encode ( ' base64 ' ) ,
2011-07-22 16:34:57 +00:00
' res_model ' : self . _name ,
' res_id ' : email_msg_id ,
}
if context . has_key ( ' default_type ' ) :
del context [ ' default_type ' ]
attachment_ids . append ( attachment_obj . create ( cr , uid , attachment_data , context ) )
if attachment_ids :
self . write ( cr , uid , email_msg_id , { ' attachment_ids ' : [ ( 6 , 0 , attachment_ids ) ] } , context = context )
return email_msg_id
def mark_outgoing ( self , cr , uid , ids , context = None ) :
return self . write ( cr , uid , ids , { ' state ' : ' outgoing ' } , context )
def process_email_queue ( self , cr , uid , ids = None , context = None ) :
""" Send immediately queued messages, committing after each
message is sent - this is not transactional and should
not be called during another transaction !
: param list ids : optional list of emails ids to send . If passed
no search is performed , and these ids are used
instead .
: param dict context : if a ' filters ' key is present in context ,
this value will be used as an additional
filter to further restrict the outgoing
messages to send ( by default all ' outgoing '
messages are sent ) .
"""
if context is None :
context = { }
if not ids :
filters = [ ( ' state ' , ' = ' , ' outgoing ' ) ]
if ' filters ' in context :
filters . extend ( context [ ' filters ' ] )
ids = self . search ( cr , uid , filters , context = context )
res = None
try :
# Force auto-commit - this is meant to be called by
# the scheduler, and we can't allow rolling back the status
# of previously sent emails!
res = self . send ( cr , uid , ids , auto_commit = True , context = context )
except Exception :
_logger . exception ( " Failed processing mail queue " )
return res
2011-09-07 15:13:48 +00:00
def parse_message ( self , message , save_original = False ) :
2011-07-22 16:34:57 +00:00
""" Parses a string or email.message.Message representing an
RFC - 2822 email , and returns a generic dict holding the
message details .
: param message : the message to parse
: type message : email . message . Message | string | unicode
2011-09-07 15:13:48 +00:00
: param bool save_original : whether the returned dict
should include an ` ` original ` ` entry with the base64
encoded source of the message .
2011-07-22 16:34:57 +00:00
: rtype : dict
: return : A dict with the following structure , where each
field may not be present if missing in original
message : :
{ ' message-id ' : msg_id ,
' subject ' : subject ,
' from ' : from ,
' to ' : to ,
' cc ' : cc ,
' headers ' : { ' X-Mailer ' : mailer ,
#.. all X- headers...
} ,
' subtype ' : msg_mime_subtype ,
2011-07-22 18:23:07 +00:00
' body_text ' : plaintext_body
2011-07-22 16:34:57 +00:00
' body_html ' : html_body ,
2011-10-18 03:39:13 +00:00
' attachments ' : [ ( ' file1 ' , ' bytes ' ) ,
( ' file2 ' , ' bytes ' ) }
2011-07-22 16:34:57 +00:00
# ...
' original ' : source_of_email ,
}
"""
msg_txt = message
if isinstance ( message , str ) :
msg_txt = email . message_from_string ( message )
# Warning: message_from_string doesn't always work correctly on unicode,
# we must use utf-8 strings here :-(
if isinstance ( message , unicode ) :
message = message . encode ( ' utf-8 ' )
msg_txt = email . message_from_string ( message )
message_id = msg_txt . get ( ' message-id ' , False )
msg = { }
2011-09-07 15:13:48 +00:00
if save_original :
# save original, we need to be able to read the original email sometimes
msg [ ' original ' ] = message . as_string ( ) if isinstance ( message , Message ) \
else message
msg [ ' original ' ] = base64 . b64encode ( msg [ ' original ' ] ) # binary fields are b64
2011-07-22 16:34:57 +00:00
if not message_id :
# Very unusual situation, be we should be fault-tolerant here
message_id = time . time ( )
msg_txt [ ' message-id ' ] = message_id
_logger . info ( ' Parsing Message without message-id, generating a random one: %s ' , message_id )
fields = msg_txt . keys ( )
msg [ ' id ' ] = message_id
msg [ ' message-id ' ] = message_id
if ' Subject ' in fields :
msg [ ' subject ' ] = decode ( msg_txt . get ( ' Subject ' ) )
if ' Content-Type ' in fields :
msg [ ' content-type ' ] = msg_txt . get ( ' Content-Type ' )
if ' From ' in fields :
msg [ ' from ' ] = decode ( msg_txt . get ( ' From ' ) or msg_txt . get_unixfrom ( ) )
2011-09-07 16:26:17 +00:00
if ' To ' in fields :
msg [ ' to ' ] = decode ( msg_txt . get ( ' To ' ) )
2011-12-09 14:28:39 +00:00
2011-07-22 16:34:57 +00:00
if ' Delivered-To ' in fields :
msg [ ' to ' ] = decode ( msg_txt . get ( ' Delivered-To ' ) )
if ' CC ' in fields :
msg [ ' cc ' ] = decode ( msg_txt . get ( ' CC ' ) )
2011-12-09 14:28:39 +00:00
if ' Cc ' in fields :
msg [ ' cc ' ] = decode ( msg_txt . get ( ' Cc ' ) )
2011-07-22 16:34:57 +00:00
if ' Reply-To ' in fields :
msg [ ' reply ' ] = decode ( msg_txt . get ( ' Reply-To ' ) )
if ' Date ' in fields :
2011-08-09 23:44:28 +00:00
date_hdr = decode ( msg_txt . get ( ' Date ' ) )
msg [ ' date ' ] = dateutil . parser . parse ( date_hdr ) . strftime ( " % Y- % m- %d % H: % M: % S " )
2011-07-22 16:34:57 +00:00
if ' Content-Transfer-Encoding ' in fields :
msg [ ' encoding ' ] = msg_txt . get ( ' Content-Transfer-Encoding ' )
if ' References ' in fields :
msg [ ' references ' ] = msg_txt . get ( ' References ' )
if ' In-Reply-To ' in fields :
msg [ ' in-reply-to ' ] = msg_txt . get ( ' In-Reply-To ' )
msg [ ' headers ' ] = { }
2011-08-22 17:16:59 +00:00
msg [ ' subtype ' ] = ' plain '
2011-07-22 16:34:57 +00:00
for item in msg_txt . items ( ) :
if item [ 0 ] . startswith ( ' X- ' ) :
msg [ ' headers ' ] . update ( { item [ 0 ] : item [ 1 ] } )
if not msg_txt . is_multipart ( ) or ' text/plain ' in msg . get ( ' content-type ' , ' ' ) :
encoding = msg_txt . get_content_charset ( )
body = msg_txt . get_payload ( decode = True )
if ' text/html ' in msg . get ( ' content-type ' , ' ' ) :
msg [ ' body_html ' ] = body
msg [ ' subtype ' ] = ' html '
2012-02-22 15:32:20 +00:00
if body :
2012-02-17 13:58:24 +00:00
body = tools . html2plaintext ( body )
2011-07-22 18:23:07 +00:00
msg [ ' body_text ' ] = tools . ustr ( body , encoding )
2011-07-22 16:34:57 +00:00
2011-10-18 03:39:13 +00:00
attachments = [ ]
2011-07-22 16:34:57 +00:00
if msg_txt . is_multipart ( ) or ' multipart/alternative ' in msg . get ( ' content-type ' , ' ' ) :
body = " "
if ' multipart/alternative ' in msg . get ( ' content-type ' , ' ' ) :
msg [ ' subtype ' ] = ' alternative '
else :
msg [ ' subtype ' ] = ' mixed '
for part in msg_txt . walk ( ) :
if part . get_content_maintype ( ) == ' multipart ' :
continue
encoding = part . get_content_charset ( )
filename = part . get_filename ( )
if part . get_content_maintype ( ) == ' text ' :
content = part . get_payload ( decode = True )
if filename :
2011-10-18 03:39:13 +00:00
attachments . append ( ( filename , content ) )
2011-07-22 16:34:57 +00:00
content = tools . ustr ( content , encoding )
if part . get_content_subtype ( ) == ' html ' :
msg [ ' body_html ' ] = content
2011-08-22 17:16:59 +00:00
msg [ ' subtype ' ] = ' html ' # html version prevails
2011-07-22 16:34:57 +00:00
body = tools . ustr ( tools . html2plaintext ( content ) )
2012-02-23 05:40:22 +00:00
body = body . replace ( ' ' , ' ' )
2011-07-22 16:34:57 +00:00
elif part . get_content_subtype ( ) == ' plain ' :
body = content
elif part . get_content_maintype ( ) in ( ' application ' , ' image ' ) :
if filename :
2011-10-18 03:39:13 +00:00
attachments . append ( ( filename , part . get_payload ( decode = True ) ) )
2011-07-22 16:34:57 +00:00
else :
res = part . get_payload ( decode = True )
body + = tools . ustr ( res , encoding )
2011-07-22 18:23:07 +00:00
msg [ ' body_text ' ] = body
2011-08-22 17:16:59 +00:00
msg [ ' attachments ' ] = attachments
# for backwards compatibility:
msg [ ' body ' ] = msg [ ' body_text ' ]
msg [ ' sub_type ' ] = msg [ ' subtype ' ] or ' plain '
2011-07-22 16:34:57 +00:00
return msg
2012-03-27 11:58:00 +00:00
def _postprocess_sent_message ( self , cr , uid , message , context = None ) :
2012-03-29 10:41:49 +00:00
""" Perform any post-processing necessary after sending ``message``
successfully , including deleting it completely along with its
attachment if the ` ` auto_delete ` ` flag of the message was set .
Overridden by subclasses for extra post - processing behaviors .
2012-03-27 11:58:00 +00:00
2012-03-29 10:41:49 +00:00
: param browse_record message : the message that was just sent
: return : True
2012-03-27 11:58:00 +00:00
"""
if message . auto_delete :
self . pool . get ( ' ir.attachment ' ) . unlink ( cr , uid ,
[ x . id for x in message . attachment_ids \
if x . res_model == self . _name and \
x . res_id == message . id ] ,
context = context )
message . unlink ( )
return True
2011-12-09 14:28:39 +00:00
2011-07-22 16:34:57 +00:00
def send ( self , cr , uid , ids , auto_commit = False , context = None ) :
""" Sends the selected emails immediately, ignoring their current
state ( mails that have already been sent should not be passed
unless they should actually be re - sent ) .
Emails successfully delivered are marked as ' sent ' , and those
that fail to be deliver are marked as ' exception ' , and the
corresponding error message is output in the server logs .
: param bool auto_commit : whether to force a commit of the message
status after sending each message ( meant
only for processing by the scheduler ) ,
should never be True during normal
transactions ( default : False )
: return : True
"""
if context is None :
context = { }
ir_mail_server = self . pool . get ( ' ir.mail_server ' )
self . write ( cr , uid , ids , { ' state ' : ' outgoing ' } , context = context )
for message in self . browse ( cr , uid , ids , context = context ) :
try :
attachments = [ ]
for attach in message . attachment_ids :
attachments . append ( ( attach . datas_fname , base64 . b64decode ( attach . datas ) ) )
2011-09-30 21:09:26 +00:00
body = message . body_html if message . subtype == ' html ' else message . body_text
body_alternative = None
subtype_alternative = None
if message . subtype == ' html ' and message . body_text :
# we have a plain text alternative prepared, pass it to
# build_message instead of letting it build one
body_alternative = message . body_text
subtype_alternative = ' plain '
2011-07-22 16:34:57 +00:00
msg = ir_mail_server . build_email (
email_from = message . email_from ,
email_to = to_email ( message . email_to ) ,
subject = message . subject ,
2011-09-30 21:09:26 +00:00
body = body ,
body_alternative = body_alternative ,
2011-07-22 16:34:57 +00:00
email_cc = to_email ( message . email_cc ) ,
email_bcc = to_email ( message . email_bcc ) ,
reply_to = message . reply_to ,
attachments = attachments , message_id = message . message_id ,
references = message . references ,
object_id = message . res_id and ( ' %s - %s ' % ( message . res_id , message . model ) ) ,
subtype = message . subtype ,
2011-09-30 21:09:26 +00:00
subtype_alternative = subtype_alternative ,
2012-01-18 11:18:55 +00:00
headers = message . headers and ast . literal_eval ( message . headers ) )
2011-07-22 16:34:57 +00:00
res = ir_mail_server . send_email ( cr , uid , msg ,
mail_server_id = message . mail_server_id . id ,
context = context )
if res :
2011-09-07 15:13:48 +00:00
message . write ( { ' state ' : ' sent ' , ' message_id ' : res } )
2011-07-22 16:34:57 +00:00
else :
message . write ( { ' state ' : ' exception ' } )
message . refresh ( )
2012-03-27 11:58:00 +00:00
if message . state == ' sent ' :
self . _postprocess_sent_message ( cr , uid , message , context = context )
2011-07-22 16:34:57 +00:00
except Exception :
_logger . exception ( ' failed sending mail.message %s ' , message . id )
message . write ( { ' state ' : ' exception ' } )
if auto_commit == True :
cr . commit ( )
return True
def cancel ( self , cr , uid , ids , context = None ) :
self . write ( cr , uid , ids , { ' state ' : ' cancel ' } , context = context )
return True
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: