2012-08-22 07:45:45 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
|
|
#
|
|
|
|
# OpenERP, Open Source Management Solution
|
|
|
|
# Copyright (C) 2010-today OpenERP SA (<http://www.openerp.com>)
|
|
|
|
#
|
|
|
|
# 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-08-24 16:09:36 +00:00
|
|
|
# import ast
|
2012-08-15 18:44:03 +00:00
|
|
|
import base64
|
|
|
|
import logging
|
2012-08-22 07:45:45 +00:00
|
|
|
import tools
|
2012-08-15 18:44:03 +00:00
|
|
|
|
|
|
|
from osv import osv
|
|
|
|
from osv import fields
|
2012-08-29 15:00:02 +00:00
|
|
|
from tools.translate import _
|
2012-08-15 18:44:03 +00:00
|
|
|
|
2012-08-22 01:09:47 +00:00
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
2012-08-15 17:08:22 +00:00
|
|
|
class mail_mail(osv.Model):
|
2012-08-31 09:31:39 +00:00
|
|
|
""" Model holding RFC2822 email messages to send. This model also provides
|
|
|
|
facilities to queue and send new email messages. """
|
2012-08-15 17:08:22 +00:00
|
|
|
_name = 'mail.mail'
|
|
|
|
_description = 'Outgoing Mails'
|
2012-08-16 09:26:16 +00:00
|
|
|
_inherits = {'mail.message': 'mail_message_id'}
|
2012-08-30 08:51:16 +00:00
|
|
|
_order = 'id desc'
|
|
|
|
|
2012-08-15 17:08:22 +00:00
|
|
|
_columns = {
|
2012-08-16 09:26:16 +00:00
|
|
|
'mail_message_id': fields.many2one('mail.message', 'Message', required=True, ondelete='cascade'),
|
2012-08-15 17:08:22 +00:00
|
|
|
'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1),
|
|
|
|
'state': fields.selection([
|
2012-08-15 19:34:06 +00:00
|
|
|
('outgoing', 'Outgoing'),
|
|
|
|
('sent', 'Sent'),
|
|
|
|
('received', 'Received'),
|
|
|
|
('exception', 'Delivery Failed'),
|
|
|
|
('cancel', 'Cancelled'),
|
|
|
|
], 'Status', readonly=True),
|
2012-08-15 17:08:22 +00:00
|
|
|
'auto_delete': fields.boolean('Auto Delete',
|
|
|
|
help="Permanently delete this email after sending it, to save space"),
|
2012-08-16 09:26:16 +00:00
|
|
|
'references': fields.text('References', help='Message references, such as identifiers of previous messages', readonly=1),
|
2012-08-30 17:44:52 +00:00
|
|
|
'email_from': fields.char('From', help='Message sender, taken from user preferences.'),
|
2012-08-15 17:08:22 +00:00
|
|
|
'email_to': fields.text('To', help='Message recipients'),
|
2012-08-30 17:44:52 +00:00
|
|
|
'email_cc': fields.char('Cc', help='Carbon copy message recipients'),
|
2012-09-05 15:19:50 +00:00
|
|
|
'reply_to': fields.char('Reply-To', help='Preferred response address for the message'),
|
2012-08-30 08:51:16 +00:00
|
|
|
'body_html': fields.text('Rich-text Contents', help="Rich-text/HTML message"),
|
2012-09-05 15:19:50 +00:00
|
|
|
|
|
|
|
# Auto-detected based on create() - if 'mail_message_id' was passed then this mail is a notification
|
|
|
|
# and during unlink() we will cascade delete the parent and its attachments
|
|
|
|
'notification': fields.boolean('Is Notification')
|
2012-08-15 17:08:22 +00:00
|
|
|
}
|
|
|
|
|
2012-08-29 15:00:02 +00:00
|
|
|
def _get_default_from(self, cr, uid, context=None):
|
2012-08-20 16:51:10 +00:00
|
|
|
cur = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
|
|
|
if not cur.alias_domain:
|
2012-08-31 09:31:39 +00:00
|
|
|
raise osv.except_osv(_('Invalid Action!'), _('Unable to send email, set an alias domain in your server settings.'))
|
2012-08-20 16:51:10 +00:00
|
|
|
return cur.alias_name + '@' + cur.alias_domain
|
|
|
|
|
2012-08-15 17:08:22 +00:00
|
|
|
_defaults = {
|
2012-08-15 18:44:03 +00:00
|
|
|
'state': 'outgoing',
|
2012-08-29 15:00:02 +00:00
|
|
|
'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx),
|
2012-08-15 17:08:22 +00:00
|
|
|
}
|
|
|
|
|
2012-09-05 15:19:50 +00:00
|
|
|
def create(self, cr, uid, values, context=None):
|
|
|
|
if 'notification' not in values and values.get('mail_message_id'):
|
|
|
|
values['notification'] = True
|
|
|
|
return super(mail_mail,self).create(cr, uid, values, context=context)
|
|
|
|
|
|
|
|
def unlink(self, cr, uid, ids, context=None):
|
|
|
|
# cascade-delete the parent message for all mails that are not created for a notification
|
|
|
|
ids_to_cascade = self.search(cr, uid, [('notification','=',False),('id','in',ids)])
|
|
|
|
parent_msg_ids = [m.mail_message_id.id for m in self.browse(cr, uid, ids_to_cascade, context=context)]
|
|
|
|
res = super(mail_mail,self).unlink(cr, uid, ids, context=context)
|
|
|
|
self.pool.get('mail.message').unlink(cr, uid, parent_msg_ids, context=context)
|
|
|
|
return res
|
|
|
|
|
2012-08-15 17:08:22 +00:00
|
|
|
def mark_outgoing(self, cr, uid, ids, context=None):
|
|
|
|
return self.write(cr, uid, ids, {'state':'outgoing'}, context=context)
|
|
|
|
|
|
|
|
def cancel(self, cr, uid, ids, context=None):
|
|
|
|
return self.write(cr, uid, ids, {'state':'cancel'}, context=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'), ('type', '=', 'email')]
|
|
|
|
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
|
|
|
|
|
2012-09-05 15:19:50 +00:00
|
|
|
def _postprocess_sent_message(self, cr, uid, mail, context=None):
|
|
|
|
"""Perform any post-processing necessary after sending ``mail``
|
2012-08-15 17:08:22 +00:00
|
|
|
successfully, including deleting it completely along with its
|
2012-09-05 15:19:50 +00:00
|
|
|
attachment if the ``auto_delete`` flag of the mail was set.
|
2012-08-15 17:08:22 +00:00
|
|
|
Overridden by subclasses for extra post-processing behaviors.
|
|
|
|
|
2012-09-05 15:19:50 +00:00
|
|
|
:param browse_record mail: the mail that was just sent
|
2012-08-15 17:08:22 +00:00
|
|
|
:return: True
|
|
|
|
"""
|
2012-09-05 15:19:50 +00:00
|
|
|
if mail.auto_delete:
|
|
|
|
mail.unlink()
|
2012-08-15 17:08:22 +00:00
|
|
|
return True
|
|
|
|
|
2012-08-31 09:31:39 +00:00
|
|
|
def _send_get_mail_subject(self, cr, uid, mail, force=False, context=None):
|
|
|
|
""" if void and related document: '<Author> posted on <Resource>'
|
|
|
|
:param mail: mail.mail browse_record """
|
|
|
|
if force or (not mail.subject and mail.model and mail.res_id):
|
|
|
|
return '%s posted on %s' % (mail.author_id.name, mail.record_name)
|
|
|
|
return mail.subject
|
|
|
|
|
2012-08-15 17:08:22 +00:00
|
|
|
def send(self, cr, uid, ids, auto_commit=False, context=None):
|
2012-08-31 09:31:39 +00:00
|
|
|
""" 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 mail is output in the server logs.
|
|
|
|
|
|
|
|
:param bool auto_commit: whether to force a commit of the mail status
|
|
|
|
after sending each mail (meant only for scheduler processing);
|
|
|
|
should never be True during normal transactions (default: False)
|
|
|
|
:return: True
|
2012-08-15 17:08:22 +00:00
|
|
|
"""
|
|
|
|
ir_mail_server = self.pool.get('ir.mail_server')
|
2012-08-31 09:31:39 +00:00
|
|
|
for mail in self.browse(cr, uid, ids, context=context):
|
2012-08-15 17:08:22 +00:00
|
|
|
try:
|
2012-08-31 09:31:39 +00:00
|
|
|
body = mail.body_html
|
|
|
|
subject = self._send_get_mail_subject(cr, uid, mail, context=context)
|
2012-08-29 15:00:02 +00:00
|
|
|
|
|
|
|
# handle attachments
|
2012-08-15 17:08:22 +00:00
|
|
|
attachments = []
|
2012-08-31 09:31:39 +00:00
|
|
|
for attach in mail.attachment_ids:
|
2012-08-15 17:08:22 +00:00
|
|
|
attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
|
|
|
|
|
2012-08-29 15:00:02 +00:00
|
|
|
# use only sanitized html and set its plaintexted version as alternative
|
|
|
|
body_alternative = tools.html2plaintext(body)
|
|
|
|
content_subtype_alternative = 'plain'
|
2012-08-15 17:08:22 +00:00
|
|
|
|
2012-08-31 09:31:39 +00:00
|
|
|
# build an RFC2822 email.message.Message object and send it without queuing
|
2012-08-15 17:08:22 +00:00
|
|
|
msg = ir_mail_server.build_email(
|
2012-08-31 09:31:39 +00:00
|
|
|
email_from = mail.email_from,
|
|
|
|
email_to = tools.email_split(mail.email_to),
|
2012-08-29 15:00:02 +00:00
|
|
|
subject = subject,
|
|
|
|
body = body,
|
|
|
|
body_alternative = body_alternative,
|
2012-08-31 09:31:39 +00:00
|
|
|
email_cc = tools.email_split(mail.email_cc),
|
|
|
|
reply_to = mail.reply_to,
|
2012-08-29 15:00:02 +00:00
|
|
|
attachments = attachments,
|
2012-08-31 09:31:39 +00:00
|
|
|
message_id = mail.message_id,
|
|
|
|
references = mail.references,
|
|
|
|
object_id = mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
|
2012-08-29 15:00:02 +00:00
|
|
|
subtype = 'html',
|
|
|
|
subtype_alternative = content_subtype_alternative)
|
2012-08-15 17:08:22 +00:00
|
|
|
res = ir_mail_server.send_email(cr, uid, msg,
|
2012-08-31 09:31:39 +00:00
|
|
|
mail_server_id=mail.mail_server_id.id, context=context)
|
2012-08-15 17:08:22 +00:00
|
|
|
if res:
|
2012-08-31 09:31:39 +00:00
|
|
|
mail.write({'state':'sent', 'message_id': res})
|
2012-08-15 17:08:22 +00:00
|
|
|
else:
|
2012-08-31 09:31:39 +00:00
|
|
|
mail.write({'state':'exception'})
|
|
|
|
mail.refresh()
|
|
|
|
if mail.state == 'sent':
|
|
|
|
self._postprocess_sent_message(cr, uid, mail, context=context)
|
2012-08-15 17:08:22 +00:00
|
|
|
except Exception:
|
2012-08-31 09:31:39 +00:00
|
|
|
_logger.exception('failed sending mail.mail %s', mail.id)
|
|
|
|
mail.write({'state':'exception'})
|
2012-08-15 17:08:22 +00:00
|
|
|
|
|
|
|
if auto_commit == True:
|
|
|
|
cr.commit()
|
|
|
|
return True
|