[MERGE] mail/chatter complete review/refactoring
[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 from tools.translate import _
30
31 _logger = logging.getLogger(__name__)
32
33 class mail_mail(osv.Model):
34     """ Model holding RFC2822 email messages to send. This model also provides
35         facilities to queue and send new email messages.  """
36     _name = 'mail.mail'
37     _description = 'Outgoing Mails'
38     _inherits = {'mail.message': 'mail_message_id'}
39     _order = 'id desc'
40
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', help='Message sender, taken from user preferences.'),
55         'email_to': fields.text('To', help='Message recipients'),
56         'email_cc': fields.char('Cc', help='Carbon copy message recipients'),
57         'reply_to': fields.char('Reply-To', help='Preferred response address for the message'),
58         'body_html': fields.text('Rich-text Contents', help="Rich-text/HTML message"),
59
60         # Auto-detected based on create() - if 'mail_message_id' was passed then this mail is a notification
61         # and during unlink() we will cascade delete the parent and its attachments 
62         'notification': fields.boolean('Is Notification') 
63     }
64
65     def _get_default_from(self, cr, uid, context=None):
66         cur = self.pool.get('res.users').browse(cr, uid, uid, context=context)
67         if not cur.alias_domain:
68             raise osv.except_osv(_('Invalid Action!'), _('Unable to send email, set an alias domain in your server settings.'))
69         return cur.alias_name + '@' + cur.alias_domain
70
71     _defaults = {
72         'state': 'outgoing',
73         'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx),
74     }
75
76     def create(self, cr, uid, values, context=None):
77         if 'notification' not in values and values.get('mail_message_id'):
78             values['notification'] = True
79         return super(mail_mail,self).create(cr, uid, values, context=context)
80
81     def unlink(self, cr, uid, ids, context=None):
82         # cascade-delete the parent message for all mails that are not created for a notification
83         ids_to_cascade = self.search(cr, uid, [('notification','=',False),('id','in',ids)])
84         parent_msg_ids = [m.mail_message_id.id for m in self.browse(cr, uid, ids_to_cascade, context=context)]
85         res = super(mail_mail,self).unlink(cr, uid, ids, context=context)
86         self.pool.get('mail.message').unlink(cr, uid, parent_msg_ids, context=context)
87         return res
88
89     def mark_outgoing(self, cr, uid, ids, context=None):
90         return self.write(cr, uid, ids, {'state':'outgoing'}, context=context)
91
92     def cancel(self, cr, uid, ids, context=None):
93         return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
94
95     def process_email_queue(self, cr, uid, ids=None, context=None):
96         """Send immediately queued messages, committing after each
97            message is sent - this is not transactional and should
98            not be called during another transaction!
99
100            :param list ids: optional list of emails ids to send. If passed
101                             no search is performed, and these ids are used
102                             instead.
103            :param dict context: if a 'filters' key is present in context,
104                                 this value will be used as an additional
105                                 filter to further restrict the outgoing
106                                 messages to send (by default all 'outgoing'
107                                 messages are sent).
108         """
109         if context is None:
110             context = {}
111         if not ids:
112             filters = ['&', ('state', '=', 'outgoing'), ('type', '=', 'email')]
113             if 'filters' in context:
114                 filters.extend(context['filters'])
115             ids = self.search(cr, uid, filters, context=context)
116         res = None
117         try:
118             # Force auto-commit - this is meant to be called by
119             # the scheduler, and we can't allow rolling back the status
120             # of previously sent emails!
121             res = self.send(cr, uid, ids, auto_commit=True, context=context)
122         except Exception:
123             _logger.exception("Failed processing mail queue")
124         return res
125
126     def _postprocess_sent_message(self, cr, uid, mail, context=None):
127         """Perform any post-processing necessary after sending ``mail``
128         successfully, including deleting it completely along with its
129         attachment if the ``auto_delete`` flag of the mail was set.
130         Overridden by subclasses for extra post-processing behaviors. 
131
132         :param browse_record mail: the mail that was just sent
133         :return: True
134         """
135         if mail.auto_delete:
136             mail.unlink()
137         return True
138
139     def _send_get_mail_subject(self, cr, uid, mail, force=False, context=None):
140         """ if void and related document: '<Author> posted on <Resource>'
141             :param mail: mail.mail browse_record """
142         if force or (not mail.subject and mail.model and mail.res_id):
143             return '%s posted on %s' % (mail.author_id.name, mail.record_name)
144         return mail.subject
145
146     def send(self, cr, uid, ids, auto_commit=False, context=None):
147         """ Sends the selected emails immediately, ignoring their current
148             state (mails that have already been sent should not be passed
149             unless they should actually be re-sent).
150             Emails successfully delivered are marked as 'sent', and those
151             that fail to be deliver are marked as 'exception', and the
152             corresponding error mail is output in the server logs.
153
154             :param bool auto_commit: whether to force a commit of the mail status
155                 after sending each mail (meant only for scheduler processing);
156                 should never be True during normal transactions (default: False)
157             :return: True
158         """
159         ir_mail_server = self.pool.get('ir.mail_server')
160         for mail in self.browse(cr, uid, ids, context=context):
161             try:
162                 body = mail.body_html
163                 subject = self._send_get_mail_subject(cr, uid, mail, context=context)
164
165                 # handle attachments
166                 attachments = []
167                 for attach in mail.attachment_ids:
168                     attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
169
170                 # use only sanitized html and set its plaintexted version as alternative
171                 body_alternative = tools.html2plaintext(body)
172                 content_subtype_alternative = 'plain'
173
174                 # build an RFC2822 email.message.Message object and send it without queuing
175                 msg = ir_mail_server.build_email(
176                     email_from = mail.email_from,
177                     email_to = tools.email_split(mail.email_to),
178                     subject = subject,
179                     body = body,
180                     body_alternative = body_alternative,
181                     email_cc = tools.email_split(mail.email_cc),
182                     reply_to = mail.reply_to,
183                     attachments = attachments,
184                     message_id = mail.message_id,
185                     references = mail.references,
186                     object_id = mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
187                     subtype = 'html',
188                     subtype_alternative = content_subtype_alternative)
189                 res = ir_mail_server.send_email(cr, uid, msg,
190                     mail_server_id=mail.mail_server_id.id, context=context)
191                 if res:
192                     mail.write({'state':'sent', 'message_id': res})
193                 else:
194                     mail.write({'state':'exception'})
195                 mail.refresh()
196                 if mail.state == 'sent':
197                     self._postprocess_sent_message(cr, uid, mail, context=context)
198             except Exception:
199                 _logger.exception('failed sending mail.mail %s', mail.id)
200                 mail.write({'state':'exception'})
201
202             if auto_commit == True:
203                 cr.commit()
204         return True