ebfd19e87b30f9065443a61c0b9ea37c4acb10fb
[odoo/odoo.git] / addons / mail / mail_mail.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2010-today OpenERP SA (<http://www.openerp.com>)
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>
19 #
20 ##############################################################################
21
22 # import ast
23 import base64
24 import logging
25 import tools
26
27 from osv import osv
28 from osv import fields
29
30 _logger = logging.getLogger(__name__)
31
32 class mail_mail(osv.Model):
33     """
34     Model holding RFC2822 email messages to send. This model also provides
35     facilities to queue and send new email messages. 
36     """
37
38     _name = 'mail.mail'
39     _description = 'Outgoing Mails'
40     _inherits = {'mail.message': 'mail_message_id'}
41     _columns = {
42         'mail_message_id': fields.many2one('mail.message', 'Message', required=True, ondelete='cascade'),
43         'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1),
44         'state': fields.selection([
45             ('outgoing', 'Outgoing'),
46             ('sent', 'Sent'),
47             ('received', 'Received'),
48             ('exception', 'Delivery Failed'),
49             ('cancel', 'Cancelled'),
50         ], 'Status', readonly=True),
51         'auto_delete': fields.boolean('Auto Delete',
52             help="Permanently delete this email after sending it, to save space"),
53         'references': fields.text('References', help='Message references, such as identifiers of previous messages', readonly=1),
54         'email_from': fields.char('From', size=128, help='Message sender, taken from user preferences.'),
55         'email_to': fields.text('To', help='Message recipients'),
56         'email_cc': fields.char('Cc', size=256, help='Carbon copy message recipients'),
57         'reply_to':fields.char('Reply-To', size=256, help='Preferred response address for the message'),
58
59         'content_subtype': fields.char('Message content subtype', size=32,
60             oldname="subtype", readonly=1,
61             help="Type of message, usually 'html' or 'plain', used to select "\
62                   "plain-text or rich-text contents accordingly"),
63         'body_html': fields.text('Rich-text Contents', help="Rich-text/HTML version of the message"),
64     }
65
66     def _get_default_from(self, cr, uid, context={}):
67         cur = self.pool.get('res.users').browse(cr, uid, uid, context=context)
68         if not cur.alias_domain:
69             raise osv.except_osv(_('Invalid Action!'), _('Unable to send email, set an alias domain in your server settings.'))
70         return cur.alias_name + '@' + cur.alias_domain
71
72     _defaults = {
73         'state': 'outgoing',
74         'content_subtype': 'plain',
75         'email_from': _get_default_from
76     }
77
78     def mark_outgoing(self, cr, uid, ids, context=None):
79         return self.write(cr, uid, ids, {'state':'outgoing'}, context=context)
80
81     def cancel(self, cr, uid, ids, context=None):
82         return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
83
84     def process_email_queue(self, cr, uid, ids=None, context=None):
85         """Send immediately queued messages, committing after each
86            message is sent - this is not transactional and should
87            not be called during another transaction!
88
89            :param list ids: optional list of emails ids to send. If passed
90                             no search is performed, and these ids are used
91                             instead.
92            :param dict context: if a 'filters' key is present in context,
93                                 this value will be used as an additional
94                                 filter to further restrict the outgoing
95                                 messages to send (by default all 'outgoing'
96                                 messages are sent).
97         """
98         if context is None:
99             context = {}
100         if not ids:
101             filters = ['&', ('state', '=', 'outgoing'), ('type', '=', 'email')]
102             if 'filters' in context:
103                 filters.extend(context['filters'])
104             ids = self.search(cr, uid, filters, context=context)
105         res = None
106         try:
107             # Force auto-commit - this is meant to be called by
108             # the scheduler, and we can't allow rolling back the status
109             # of previously sent emails!
110             res = self.send(cr, uid, ids, auto_commit=True, context=context)
111         except Exception:
112             _logger.exception("Failed processing mail queue")
113         return res
114
115     def _postprocess_sent_message(self, cr, uid, message, context=None):
116         """Perform any post-processing necessary after sending ``message``
117         successfully, including deleting it completely along with its
118         attachment if the ``auto_delete`` flag of the message was set.
119         Overridden by subclasses for extra post-processing behaviors. 
120
121         :param browse_record message: the message that was just sent
122         :return: True
123         """
124         if message.auto_delete:
125             self.pool.get('ir.attachment').unlink(cr, uid,
126                 [x.id for x in message.attachment_ids],
127                 context=context)
128             message.unlink()
129         return True
130
131     def send(self, cr, uid, ids, auto_commit=False, context=None):
132         """Sends the selected emails immediately, ignoring their current
133            state (mails that have already been sent should not be passed
134            unless they should actually be re-sent).
135            Emails successfully delivered are marked as 'sent', and those
136            that fail to be deliver are marked as 'exception', and the
137            corresponding error message is output in the server logs.
138
139            :param bool auto_commit: whether to force a commit of the message
140                                     status after sending each message (meant
141                                     only for processing by the scheduler),
142                                     should never be True during normal
143                                     transactions (default: False)
144            :return: True
145         """
146         ir_mail_server = self.pool.get('ir.mail_server')
147         self.write(cr, uid, ids, {'state': 'outgoing'}, context=context)
148         for message in self.browse(cr, uid, ids, context=context):
149             try:
150                 attachments = []
151                 for attach in message.attachment_ids:
152                     attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
153
154                 body = message.body_html if message.content_subtype == 'html' else message.body
155                 body_alternative = None
156                 content_subtype_alternative = None
157                 if message.content_subtype == 'html':
158                     # we have a plain text alternative prepared, pass it to 
159                     # build_message instead of letting it build one
160                     body_alternative = tools.html2plaintext(message.body)
161                     content_subtype_alternative = 'plain'
162
163                 # handle destination_partners
164                 partner_ids_email_to = ''
165                 for partner in message.partner_ids:
166                     partner_ids_email_to += '%s ' % (partner.email or '')
167                 message_email_to = '%s %s' % (partner_ids_email_to, message.email_to or '')
168
169                 # build an RFC2822 email.message.Message object and send it
170                 # without queuing
171                 msg = ir_mail_server.build_email(
172                     email_from=message.email_from,
173                     email_to=tools.email_split(message_email_to),
174                     subject=message.subject,
175                     body=body,
176                     body_alternative=body_alternative,
177                     email_cc=tools.email_split(message.email_cc),
178                     reply_to=message.reply_to,
179                     attachments=attachments, message_id=message.message_id,
180                     references = message.references,
181                     object_id=message.res_id and ('%s-%s' % (message.res_id,message.model)),
182                     subtype=message.content_subtype,
183                     subtype_alternative=content_subtype_alternative)
184                 res = ir_mail_server.send_email(cr, uid, msg,
185                                                 mail_server_id=message.mail_server_id.id,
186                                                 context=context)
187                 if res:
188                     message.write({'state':'sent', 'message_id': res, 'email_to': message_email_to})
189                 else:
190                     message.write({'state':'exception', 'email_to': message_email_to})
191                 message.refresh()
192                 if message.state == 'sent':
193                     self._postprocess_sent_message(cr, uid, message, context=context)
194             except Exception:
195                 _logger.exception('failed sending mail.mail %s', message.id)
196                 message.write({'state':'exception'})
197
198             if auto_commit == True:
199                 cr.commit()
200         return True
201