2011-07-22 16:34:57 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
|
|
#
|
|
|
|
# OpenERP, Open Source Management Solution
|
2012-03-13 15:06:35 +00:00
|
|
|
# Copyright (C) 2009-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/>
|
|
|
|
#
|
|
|
|
##############################################################################
|
|
|
|
|
2011-08-23 17:58:09 +00:00
|
|
|
import base64
|
2012-08-22 08:38:13 +00:00
|
|
|
import dateutil
|
2011-07-22 16:34:57 +00:00
|
|
|
import email
|
|
|
|
import logging
|
2012-08-23 18:54:43 +00:00
|
|
|
import pytz
|
|
|
|
import time
|
2012-08-28 12:55:22 +00:00
|
|
|
import tools
|
2011-07-22 16:34:57 +00:00
|
|
|
import xmlrpclib
|
|
|
|
|
2012-08-09 17:16:55 +00:00
|
|
|
from email.message import Message
|
2012-08-23 18:54:43 +00:00
|
|
|
from mail_message import decode
|
2012-09-14 11:58:53 +00:00
|
|
|
from openerp import SUPERUSER_ID
|
2012-08-23 18:54:43 +00:00
|
|
|
from osv import osv, fields
|
2012-08-09 17:16:55 +00:00
|
|
|
from tools.safe_eval import safe_eval as eval
|
2012-08-23 18:54:43 +00:00
|
|
|
|
2012-02-01 16:21:36 +00:00
|
|
|
_logger = logging.getLogger(__name__)
|
2011-07-22 16:34:57 +00:00
|
|
|
|
2012-10-15 13:34:38 +00:00
|
|
|
|
2012-08-10 13:19:19 +00:00
|
|
|
def decode_header(message, header, separator=' '):
|
2012-09-05 15:51:21 +00:00
|
|
|
return separator.join(map(decode, message.get_all(header, [])))
|
2012-08-10 13:19:19 +00:00
|
|
|
|
2012-09-20 10:17:04 +00:00
|
|
|
|
2012-09-04 12:18:38 +00:00
|
|
|
class mail_thread(osv.AbstractModel):
|
2012-08-31 08:01:03 +00:00
|
|
|
''' mail_thread model is meant to be inherited by any model that needs to
|
|
|
|
act as a discussion topic on which messages can be attached. Public
|
|
|
|
methods are prefixed with ``message_`` in order to avoid name
|
|
|
|
collisions with methods of the models that will inherit from this class.
|
|
|
|
|
|
|
|
``mail.thread`` defines fields used to handle and display the
|
|
|
|
communication history. ``mail.thread`` also manages followers of
|
|
|
|
inheriting classes. All features and expected behavior are managed
|
|
|
|
by mail.thread. Widgets has been designed for the 7.0 and following
|
|
|
|
versions of OpenERP.
|
|
|
|
|
|
|
|
Inheriting classes are not required to implement any method, as the
|
|
|
|
default implementation will work for any model. However it is common
|
|
|
|
to override at least the ``message_new`` and ``message_update``
|
|
|
|
methods (calling ``super``) to add model-specific behavior at
|
|
|
|
creation and update of a thread when processing incoming emails.
|
2012-10-15 13:23:13 +00:00
|
|
|
|
|
|
|
Options:
|
|
|
|
- _mail_flat_thread: if set to True, all messages without parent_id
|
|
|
|
are automatically attached to the first message posted on the
|
|
|
|
ressource. If set to False, the display of Chatter is done using
|
|
|
|
threads, and no parent_id is automatically set.
|
2011-07-22 16:34:57 +00:00
|
|
|
'''
|
|
|
|
_name = 'mail.thread'
|
|
|
|
_description = 'Email Thread'
|
2012-10-15 13:23:13 +00:00
|
|
|
_mail_flat_thread = True
|
2011-07-22 16:34:57 +00:00
|
|
|
|
2012-08-15 13:36:43 +00:00
|
|
|
def _get_message_data(self, cr, uid, ids, name, args, context=None):
|
2012-09-20 10:17:04 +00:00
|
|
|
""" Computes:
|
|
|
|
- message_unread: has uid unread message for the document
|
|
|
|
- message_summary: html snippet summarizing the Chatter for kanban views """
|
2012-09-05 15:51:21 +00:00
|
|
|
res = dict((id, dict(message_unread=False, message_summary='')) for id in ids)
|
2012-08-22 11:03:13 +00:00
|
|
|
|
2012-09-14 13:52:45 +00:00
|
|
|
# search for unread messages, by reading directly mail.notification, as SUPERUSER
|
2012-08-22 11:03:13 +00:00
|
|
|
notif_obj = self.pool.get('mail.notification')
|
2012-09-14 11:58:53 +00:00
|
|
|
notif_ids = notif_obj.search(cr, SUPERUSER_ID, [
|
2012-08-22 11:03:13 +00:00
|
|
|
('partner_id.user_ids', 'in', [uid]),
|
|
|
|
('message_id.res_id', 'in', ids),
|
|
|
|
('message_id.model', '=', self._name),
|
|
|
|
('read', '=', False)
|
2012-08-15 13:36:43 +00:00
|
|
|
], context=context)
|
2012-09-14 11:58:53 +00:00
|
|
|
for notif in notif_obj.browse(cr, SUPERUSER_ID, notif_ids, context=context):
|
2012-08-20 12:55:25 +00:00
|
|
|
res[notif.message_id.res_id]['message_unread'] = True
|
2012-08-15 13:36:43 +00:00
|
|
|
|
2012-08-13 19:13:46 +00:00
|
|
|
for thread in self.browse(cr, uid, ids, context=context):
|
2012-08-23 16:04:16 +00:00
|
|
|
cls = res[thread.id]['message_unread'] and ' class="oe_kanban_mail_new"' or ''
|
|
|
|
res[thread.id]['message_summary'] = "<span%s><span class='oe_e'>9</span> %d</span> <span><span class='oe_e'>+</span> %d</span>" % (cls, len(thread.message_comment_ids), len(thread.message_follower_ids))
|
2012-09-20 10:17:04 +00:00
|
|
|
|
2012-09-21 09:50:37 +00:00
|
|
|
return res
|
2012-10-15 13:23:13 +00:00
|
|
|
|
2012-09-20 10:17:04 +00:00
|
|
|
def _get_subscription_data(self, cr, uid, ids, name, args, context=None):
|
|
|
|
""" Computes:
|
|
|
|
- message_subtype_data: data about document subtypes: which are
|
|
|
|
available, which are followed if any """
|
2012-10-16 11:17:53 +00:00
|
|
|
res = dict((id, dict(message_subtype_data='')) for id in ids)
|
2012-09-20 10:17:04 +00:00
|
|
|
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
|
|
|
|
|
|
|
# find current model subtypes, add them to a dictionary
|
|
|
|
subtype_obj = self.pool.get('mail.message.subtype')
|
|
|
|
subtype_ids = subtype_obj.search(cr, uid, ['|', ('res_model', '=', self._name), ('res_model', '=', False)], context=context)
|
2012-09-20 11:49:47 +00:00
|
|
|
subtype_dict = dict((subtype.name, dict(default=subtype.default, followed=False, id=subtype.id)) for subtype in subtype_obj.browse(cr, uid, subtype_ids, context=context))
|
|
|
|
for id in ids:
|
|
|
|
res[id]['message_subtype_data'] = subtype_dict.copy()
|
2012-09-20 10:17:04 +00:00
|
|
|
|
|
|
|
# find the document followers, update the data
|
|
|
|
fol_obj = self.pool.get('mail.followers')
|
2012-10-19 09:59:19 +00:00
|
|
|
fol_ids = fol_obj.search(cr, uid, [
|
2012-09-20 10:17:04 +00:00
|
|
|
('partner_id', '=', user_pid),
|
|
|
|
('res_id', 'in', ids),
|
|
|
|
('res_model', '=', self._name),
|
|
|
|
], context=context)
|
2012-10-19 09:59:19 +00:00
|
|
|
for fol in fol_obj.browse(cr, uid, fol_ids, context=context):
|
2012-09-20 11:49:47 +00:00
|
|
|
thread_subtype_dict = res[fol.res_id]['message_subtype_data']
|
2012-09-20 10:17:04 +00:00
|
|
|
for subtype in fol.subtype_ids:
|
|
|
|
thread_subtype_dict[subtype.name]['followed'] = True
|
2012-09-20 11:49:47 +00:00
|
|
|
res[fol.res_id]['message_subtype_data'] = thread_subtype_dict
|
2012-10-15 13:23:13 +00:00
|
|
|
|
2012-02-03 11:21:16 +00:00
|
|
|
return res
|
2012-04-25 05:41:43 +00:00
|
|
|
|
2012-08-22 15:59:54 +00:00
|
|
|
def _search_unread(self, cr, uid, obj=None, name=None, domain=None, context=None):
|
2012-09-18 10:14:23 +00:00
|
|
|
partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
2012-08-22 15:59:54 +00:00
|
|
|
res = {}
|
|
|
|
notif_obj = self.pool.get('mail.notification')
|
|
|
|
notif_ids = notif_obj.search(cr, uid, [
|
|
|
|
('partner_id', '=', partner_id),
|
|
|
|
('message_id.model', '=', self._name),
|
|
|
|
('read', '=', False)
|
|
|
|
], context=context)
|
|
|
|
for notif in notif_obj.browse(cr, uid, notif_ids, context=context):
|
|
|
|
res[notif.message_id.res_id] = True
|
2012-09-05 15:51:21 +00:00
|
|
|
return [('id', 'in', res.keys())]
|
2012-06-14 10:09:22 +00:00
|
|
|
|
2012-10-08 15:12:34 +00:00
|
|
|
def _get_followers(self, cr, uid, ids, name, arg, context=None):
|
|
|
|
fol_obj = self.pool.get('mail.followers')
|
|
|
|
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', 'in', ids)])
|
2012-10-16 11:17:53 +00:00
|
|
|
res = dict((id, dict(message_follower_ids=[], message_is_follower=False)) for id in ids)
|
|
|
|
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
2012-10-08 15:12:34 +00:00
|
|
|
for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids):
|
2012-10-16 11:17:53 +00:00
|
|
|
res[fol.res_id]['message_follower_ids'].append(fol.partner_id.id)
|
|
|
|
if fol.partner_id.id == user_pid:
|
|
|
|
res[fol.res_id]['message_is_follower'] = True
|
2012-10-08 15:12:34 +00:00
|
|
|
return res
|
|
|
|
|
|
|
|
def _set_followers(self, cr, uid, id, name, value, arg, context=None):
|
2012-10-10 07:25:10 +00:00
|
|
|
if not value:
|
|
|
|
return
|
2012-10-08 15:12:34 +00:00
|
|
|
partner_obj = self.pool.get('res.partner')
|
|
|
|
fol_obj = self.pool.get('mail.followers')
|
|
|
|
|
|
|
|
# read the old set of followers, and determine the new set of followers
|
|
|
|
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', '=', id)])
|
|
|
|
old = set(fol.partner_id.id for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids))
|
|
|
|
new = set(old)
|
|
|
|
|
2012-11-02 08:27:07 +00:00
|
|
|
for command in value or []:
|
2012-10-08 15:12:34 +00:00
|
|
|
if isinstance(command, (int, long)):
|
|
|
|
new.add(command)
|
|
|
|
elif command[0] == 0:
|
|
|
|
new.add(partner_obj.create(cr, uid, command[2], context=context))
|
|
|
|
elif command[0] == 1:
|
|
|
|
partner_obj.write(cr, uid, [command[1]], command[2], context=context)
|
|
|
|
new.add(command[1])
|
|
|
|
elif command[0] == 2:
|
|
|
|
partner_obj.unlink(cr, uid, [command[1]], context=context)
|
|
|
|
new.discard(command[1])
|
|
|
|
elif command[0] == 3:
|
|
|
|
new.discard(command[1])
|
|
|
|
elif command[0] == 4:
|
|
|
|
new.add(command[1])
|
|
|
|
elif command[0] == 5:
|
|
|
|
new.clear()
|
|
|
|
elif command[0] == 6:
|
|
|
|
new = set(command[2])
|
|
|
|
|
|
|
|
# remove partners that are no longer followers
|
|
|
|
fol_ids = fol_obj.search(cr, SUPERUSER_ID,
|
|
|
|
[('res_model', '=', self._name), ('res_id', '=', id), ('partner_id', 'not in', list(new))])
|
|
|
|
fol_obj.unlink(cr, SUPERUSER_ID, fol_ids)
|
|
|
|
|
|
|
|
# add new followers
|
|
|
|
for partner_id in new - old:
|
|
|
|
fol_obj.create(cr, SUPERUSER_ID, {'res_model': self._name, 'res_id': id, 'partner_id': partner_id})
|
|
|
|
|
|
|
|
def _search_followers(self, cr, uid, obj, name, args, context):
|
|
|
|
fol_obj = self.pool.get('mail.followers')
|
|
|
|
res = []
|
|
|
|
for field, operator, value in args:
|
|
|
|
assert field == name
|
|
|
|
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('partner_id', operator, value)])
|
|
|
|
res_ids = [fol.res_id for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids)]
|
|
|
|
res.append(('id', 'in', res_ids))
|
|
|
|
return res
|
|
|
|
|
2011-07-22 16:34:57 +00:00
|
|
|
_columns = {
|
2012-10-16 11:17:53 +00:00
|
|
|
'message_is_follower': fields.function(_get_followers,
|
|
|
|
type='boolean', string='Is a Follower', multi='_get_followers,'),
|
2012-10-08 15:12:34 +00:00
|
|
|
'message_follower_ids': fields.function(_get_followers, fnct_inv=_set_followers,
|
2012-10-16 11:17:53 +00:00
|
|
|
fnct_search=_search_followers, type='many2many',
|
|
|
|
obj='res.partner', string='Followers', multi='_get_followers'),
|
2012-08-23 15:57:47 +00:00
|
|
|
'message_comment_ids': fields.one2many('mail.message', 'res_id',
|
2012-09-04 13:36:48 +00:00
|
|
|
domain=lambda self: [('model', '=', self._name), ('type', 'in', ('comment', 'email'))],
|
2012-09-05 15:51:21 +00:00
|
|
|
string='Comments and emails',
|
2012-09-04 13:36:48 +00:00
|
|
|
help="Comments and emails"),
|
2012-08-15 13:36:43 +00:00
|
|
|
'message_ids': fields.one2many('mail.message', 'res_id',
|
2012-09-05 15:51:21 +00:00
|
|
|
domain=lambda self: [('model', '=', self._name)],
|
|
|
|
string='Messages',
|
2012-09-04 13:36:48 +00:00
|
|
|
help="Messages and communication history"),
|
2012-09-05 15:51:21 +00:00
|
|
|
'message_unread': fields.function(_get_message_data, fnct_search=_search_unread,
|
2012-09-04 13:36:48 +00:00
|
|
|
type='boolean', string='Unread Messages', multi="_get_message_data",
|
|
|
|
help="If checked new messages require your attention."),
|
2012-08-15 13:36:43 +00:00
|
|
|
'message_summary': fields.function(_get_message_data, method=True,
|
|
|
|
type='text', string='Summary', multi="_get_message_data",
|
2012-06-21 15:23:11 +00:00
|
|
|
help="Holds the Chatter summary (number of messages, ...). "\
|
|
|
|
"This summary is directly in html format in order to "\
|
|
|
|
"be inserted in kanban views."),
|
2011-07-22 16:34:57 +00:00
|
|
|
}
|
|
|
|
|
2012-02-28 14:06:32 +00:00
|
|
|
#------------------------------------------------------
|
2012-08-22 11:03:13 +00:00
|
|
|
# Automatic subscription when creating
|
2012-02-28 14:06:32 +00:00
|
|
|
#------------------------------------------------------
|
2012-04-25 05:41:43 +00:00
|
|
|
|
2012-02-28 14:06:32 +00:00
|
|
|
def create(self, cr, uid, vals, context=None):
|
2012-09-04 13:36:48 +00:00
|
|
|
""" Override to subscribe the current user. """
|
2012-10-19 09:59:19 +00:00
|
|
|
thread_id = super(mail_thread, self).create(cr, uid, vals, context=context)
|
|
|
|
self.message_subscribe_users(cr, uid, [thread_id], [uid], context=context)
|
2012-06-04 09:33:24 +00:00
|
|
|
return thread_id
|
2012-04-25 05:41:43 +00:00
|
|
|
|
2012-03-13 15:06:35 +00:00
|
|
|
def unlink(self, cr, uid, ids, context=None):
|
2012-08-22 11:03:13 +00:00
|
|
|
""" Override unlink to delete messages and followers. This cannot be
|
|
|
|
cascaded, because link is done through (res_model, res_id). """
|
2012-03-13 15:06:35 +00:00
|
|
|
msg_obj = self.pool.get('mail.message')
|
2012-08-22 11:03:13 +00:00
|
|
|
fol_obj = self.pool.get('mail.followers')
|
2012-04-20 12:42:00 +00:00
|
|
|
# delete messages and notifications
|
2012-08-22 11:03:13 +00:00
|
|
|
msg_ids = msg_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)], context=context)
|
|
|
|
msg_obj.unlink(cr, uid, msg_ids, context=context)
|
|
|
|
# delete followers
|
|
|
|
fol_ids = fol_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', 'in', ids)], context=context)
|
|
|
|
fol_obj.unlink(cr, uid, fol_ids, context=context)
|
2012-03-22 12:08:36 +00:00
|
|
|
return super(mail_thread, self).unlink(cr, uid, ids, context=context)
|
2012-09-14 13:52:45 +00:00
|
|
|
|
|
|
|
def copy(self, cr, uid, id, default=None, context=None):
|
|
|
|
default = default or {}
|
|
|
|
default['message_ids'] = []
|
|
|
|
default['message_comment_ids'] = []
|
|
|
|
default['message_follower_ids'] = []
|
|
|
|
return super(mail_thread, self).copy(cr, uid, id, default=default, context=context)
|
2012-04-25 05:41:43 +00:00
|
|
|
|
2012-02-01 16:21:36 +00:00
|
|
|
#------------------------------------------------------
|
2012-06-21 09:37:55 +00:00
|
|
|
# mail.message wrappers and tools
|
2012-02-01 16:21:36 +00:00
|
|
|
#------------------------------------------------------
|
2012-04-25 05:41:43 +00:00
|
|
|
|
2012-08-31 17:15:07 +00:00
|
|
|
def _needaction_domain_get(self, cr, uid, context=None):
|
2012-08-15 13:36:43 +00:00
|
|
|
if self._needaction:
|
2012-08-28 09:53:23 +00:00
|
|
|
return [('message_unread', '=', True)]
|
2012-08-15 13:36:43 +00:00
|
|
|
return []
|
2012-08-31 17:15:07 +00:00
|
|
|
|
2012-08-20 07:42:42 +00:00
|
|
|
#------------------------------------------------------
|
|
|
|
# Mail gateway
|
|
|
|
#------------------------------------------------------
|
|
|
|
|
2011-12-09 14:28:39 +00:00
|
|
|
def message_capable_models(self, cr, uid, context=None):
|
2012-09-04 14:50:11 +00:00
|
|
|
""" Used by the plugin addon, based for plugin_outlook and others. """
|
2011-12-09 14:28:39 +00:00
|
|
|
ret_dict = {}
|
|
|
|
for model_name in self.pool.obj_list():
|
|
|
|
model = self.pool.get(model_name)
|
|
|
|
if 'mail.thread' in getattr(model, '_inherit', []):
|
2012-09-04 14:50:11 +00:00
|
|
|
ret_dict[model_name] = model._description
|
2011-12-09 14:28:39 +00:00
|
|
|
return ret_dict
|
|
|
|
|
2012-08-22 08:38:13 +00:00
|
|
|
def _message_find_partners(self, cr, uid, message, header_fields=['From'], context=None):
|
|
|
|
""" Find partners related to some header fields of the message. """
|
|
|
|
s = ', '.join([decode(message.get(h)) for h in header_fields if message.get(h)])
|
2012-09-04 13:36:48 +00:00
|
|
|
return [partner_id for email in tools.email_split(s)
|
|
|
|
for partner_id in self.pool.get('res.partner').search(cr, uid, [('email', 'ilike', email)], context=context)]
|
2012-08-16 15:48:23 +00:00
|
|
|
|
2012-08-07 18:04:12 +00:00
|
|
|
def _message_find_user_id(self, cr, uid, message, context=None):
|
2012-08-16 16:43:11 +00:00
|
|
|
from_local_part = tools.email_split(decode(message.get('From')))[0]
|
2012-08-20 07:42:42 +00:00
|
|
|
# FP Note: canonification required, the minimu: .lower()
|
2012-09-05 15:51:21 +00:00
|
|
|
user_ids = self.pool.get('res.users').search(cr, uid, ['|',
|
2012-08-20 07:42:42 +00:00
|
|
|
('login', '=', from_local_part),
|
|
|
|
('email', '=', from_local_part)], context=context)
|
2012-08-07 18:04:12 +00:00
|
|
|
return user_ids[0] if user_ids else uid
|
2012-06-14 14:17:32 +00:00
|
|
|
|
2012-08-07 18:04:12 +00:00
|
|
|
def message_route(self, cr, uid, message, model=None, thread_id=None,
|
|
|
|
custom_values=None, context=None):
|
|
|
|
"""Attempt to figure out the correct target model, thread_id,
|
|
|
|
custom_values and user_id to use for an incoming message.
|
2012-08-10 13:19:19 +00:00
|
|
|
Multiple values may be returned, if a message had multiple
|
|
|
|
recipients matching existing mail.aliases, for example.
|
2012-08-07 18:04:12 +00:00
|
|
|
|
2012-08-15 13:36:43 +00:00
|
|
|
The following heuristics are used, in this order:
|
2012-08-07 18:04:12 +00:00
|
|
|
1. If the message replies to an existing thread_id, and
|
|
|
|
properly contains the thread model in the 'In-Reply-To'
|
|
|
|
header, use this model/thread_id pair, and ignore
|
2012-08-15 13:36:43 +00:00
|
|
|
custom_value (not needed as no creation will take place)
|
2012-08-07 18:04:12 +00:00
|
|
|
2. Look for a mail.alias entry matching the message
|
|
|
|
recipient, and use the corresponding model, thread_id,
|
|
|
|
custom_values and user_id.
|
|
|
|
3. Fallback to the ``model``, ``thread_id`` and ``custom_values``
|
|
|
|
provided.
|
|
|
|
4. If all the above fails, raise an exception.
|
|
|
|
|
|
|
|
:param string message: an email.message instance
|
|
|
|
:param string model: the fallback model to use if the message
|
|
|
|
does not match any of the currently configured mail aliases
|
|
|
|
(may be None if a matching alias is supposed to be present)
|
|
|
|
:type dict custom_values: optional dictionary of default field values
|
|
|
|
to pass to ``message_new`` if a new record needs to be created.
|
|
|
|
Ignored if the thread record already exists, and also if a
|
|
|
|
matching mail.alias was found (aliases define their own defaults)
|
|
|
|
:param int thread_id: optional ID of the record/thread from ``model``
|
|
|
|
to which this mail should be attached. Only used if the message
|
|
|
|
does not reply to an existing thread and does not match any mail alias.
|
2012-08-10 13:19:19 +00:00
|
|
|
:return: list of [model, thread_id, custom_values, user_id]
|
2011-07-22 16:34:57 +00:00
|
|
|
"""
|
2012-08-09 17:16:55 +00:00
|
|
|
assert isinstance(message, Message), 'message must be an email.message.Message at this point'
|
|
|
|
message_id = message.get('Message-Id')
|
|
|
|
|
2012-08-07 18:04:12 +00:00
|
|
|
# 1. Verify if this is a reply to an existing thread
|
2012-08-10 13:19:19 +00:00
|
|
|
references = decode_header(message, 'References') or decode_header(message, 'In-Reply-To')
|
2012-08-09 17:16:55 +00:00
|
|
|
ref_match = references and tools.reference_re.search(references)
|
2012-08-07 18:04:12 +00:00
|
|
|
if ref_match:
|
|
|
|
thread_id = int(ref_match.group(1))
|
|
|
|
model = ref_match.group(2) or model
|
|
|
|
model_pool = self.pool.get(model)
|
|
|
|
if thread_id and model and model_pool and model_pool.exists(cr, uid, thread_id) \
|
|
|
|
and hasattr(model_pool, 'message_update'):
|
2012-08-14 08:04:21 +00:00
|
|
|
_logger.debug('Routing mail with Message-Id %s: direct reply to model: %s, thread_id: %s, custom_values: %s, uid: %s',
|
2012-08-09 17:16:55 +00:00
|
|
|
message_id, model, thread_id, custom_values, uid)
|
2012-08-10 13:19:19 +00:00
|
|
|
return [(model, thread_id, custom_values, uid)]
|
2012-08-15 13:36:43 +00:00
|
|
|
|
2012-08-07 18:04:12 +00:00
|
|
|
# 2. Look for a matching mail.alias entry
|
|
|
|
# Delivered-To is a safe bet in most modern MTAs, but we have to fallback on To + Cc values
|
|
|
|
# for all the odd MTAs out there, as there is no standard header for the envelope's `rcpt_to` value.
|
2012-08-10 13:19:19 +00:00
|
|
|
rcpt_tos = decode_header(message, 'Delivered-To') or \
|
2012-08-14 08:04:21 +00:00
|
|
|
','.join([decode_header(message, 'To'),
|
|
|
|
decode_header(message, 'Cc'),
|
|
|
|
decode_header(message, 'Resent-To'),
|
|
|
|
decode_header(message, 'Resent-Cc')])
|
2012-08-16 16:43:11 +00:00
|
|
|
local_parts = [e.split('@')[0] for e in tools.email_split(rcpt_tos)]
|
2012-08-07 18:04:12 +00:00
|
|
|
if local_parts:
|
|
|
|
mail_alias = self.pool.get('mail.alias')
|
|
|
|
alias_ids = mail_alias.search(cr, uid, [('alias_name', 'in', local_parts)])
|
2012-08-09 17:16:55 +00:00
|
|
|
if alias_ids:
|
2012-08-10 13:19:19 +00:00
|
|
|
routes = []
|
|
|
|
for alias in mail_alias.browse(cr, uid, alias_ids, context=context):
|
|
|
|
user_id = alias.alias_user_id.id
|
|
|
|
if not user_id:
|
|
|
|
user_id = self._message_find_user_id(cr, uid, message, context=context)
|
|
|
|
routes.append((alias.alias_model_id.model, alias.alias_force_thread_id, \
|
|
|
|
eval(alias.alias_defaults), user_id))
|
|
|
|
_logger.debug('Routing mail with Message-Id %s: direct alias match: %r', message_id, routes)
|
|
|
|
return routes
|
2012-08-15 13:36:43 +00:00
|
|
|
|
2012-08-07 18:04:12 +00:00
|
|
|
# 3. Fallback to the provided parameters, if they work
|
|
|
|
model_pool = self.pool.get(model)
|
2012-08-10 13:19:19 +00:00
|
|
|
if not thread_id:
|
|
|
|
# Legacy: fallback to matching [ID] in the Subject
|
|
|
|
match = tools.res_re.search(decode_header(message, 'Subject'))
|
|
|
|
thread_id = match and match.group(1)
|
2012-08-07 18:04:12 +00:00
|
|
|
assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
|
|
|
|
"No possible route found for incoming message with Message-Id %s. " \
|
2012-08-14 08:04:21 +00:00
|
|
|
"Create an appropriate mail.alias or force the destination model." % message_id
|
2012-08-10 13:19:19 +00:00
|
|
|
if thread_id and not model_pool.exists(cr, uid, thread_id):
|
2012-08-09 17:16:55 +00:00
|
|
|
_logger.warning('Received mail reply to missing document %s! Ignoring and creating new document instead for Message-Id %s',
|
|
|
|
thread_id, message_id)
|
|
|
|
thread_id = None
|
|
|
|
_logger.debug('Routing mail with Message-Id %s: fallback to model:%s, thread_id:%s, custom_values:%s, uid:%s',
|
|
|
|
message_id, model, thread_id, custom_values, uid)
|
2012-08-10 13:19:19 +00:00
|
|
|
return [(model, thread_id, custom_values, uid)]
|
2011-07-22 16:34:57 +00:00
|
|
|
|
2011-09-07 15:13:48 +00:00
|
|
|
def message_process(self, cr, uid, model, message, custom_values=None,
|
2011-09-08 00:16:51 +00:00
|
|
|
save_original=False, strip_attachments=False,
|
2012-06-14 14:17:32 +00:00
|
|
|
thread_id=None, context=None):
|
2012-08-07 18:04:12 +00:00
|
|
|
"""Process an incoming RFC2822 email message, relying on
|
|
|
|
``mail.message.parse()`` for the parsing operation,
|
2012-08-15 13:36:43 +00:00
|
|
|
and ``message_route()`` to figure out the target model.
|
|
|
|
|
2012-08-07 18:04:12 +00:00
|
|
|
Once the target model is known, its ``message_new`` method
|
|
|
|
is called with the new message (if the thread record did not exist)
|
2012-08-20 07:26:03 +00:00
|
|
|
or its ``message_update`` method (if it did).
|
2012-08-07 18:04:12 +00:00
|
|
|
|
|
|
|
:param string model: the fallback model to use if the message
|
|
|
|
does not match any of the currently configured mail aliases
|
|
|
|
(may be None if a matching alias is supposed to be present)
|
|
|
|
:param message: source of the RFC2822 message
|
2011-07-22 16:34:57 +00:00
|
|
|
:type message: string or xmlrpclib.Binary
|
2011-09-13 13:23:40 +00:00
|
|
|
:type dict custom_values: optional dictionary of field values
|
2012-08-07 18:04:12 +00:00
|
|
|
to pass to ``message_new`` if a new record needs to be created.
|
|
|
|
Ignored if the thread record already exists, and also if a
|
|
|
|
matching mail.alias was found (aliases define their own defaults)
|
2011-09-07 15:13:48 +00:00
|
|
|
:param bool save_original: whether to keep a copy of the original
|
2012-07-03 12:20:20 +00:00
|
|
|
email source attached to the message after it is imported.
|
2011-09-08 00:16:51 +00:00
|
|
|
:param bool strip_attachments: whether to strip all attachments
|
2012-07-03 12:20:20 +00:00
|
|
|
before processing the message, in order to save some space.
|
2012-06-14 14:17:32 +00:00
|
|
|
:param int thread_id: optional ID of the record/thread from ``model``
|
|
|
|
to which this mail should be attached. When provided, this
|
|
|
|
overrides the automatic detection based on the message
|
|
|
|
headers.
|
2011-07-22 16:34:57 +00:00
|
|
|
"""
|
2012-10-15 13:34:38 +00:00
|
|
|
if context is None:
|
|
|
|
context = {}
|
2012-08-07 18:04:12 +00:00
|
|
|
|
2011-07-22 16:34:57 +00:00
|
|
|
# extract message bytes - we are forced to pass the message as binary because
|
|
|
|
# we don't know its encoding until we parse its headers and hence can't
|
|
|
|
# convert it to utf-8 for transport between the mailgate script and here.
|
|
|
|
if isinstance(message, xmlrpclib.Binary):
|
|
|
|
message = str(message.data)
|
|
|
|
# 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)
|
2012-08-10 13:19:19 +00:00
|
|
|
routes = self.message_route(cr, uid, msg_txt, model,
|
|
|
|
thread_id, custom_values,
|
|
|
|
context=context)
|
2012-08-23 18:54:43 +00:00
|
|
|
msg = self.message_parse(cr, uid, msg_txt, save_original=save_original, context=context)
|
2012-10-15 13:34:38 +00:00
|
|
|
if strip_attachments:
|
|
|
|
msg.pop('attachments', None)
|
2012-09-16 15:10:38 +00:00
|
|
|
thread_id = False
|
2012-08-15 13:36:43 +00:00
|
|
|
for model, thread_id, custom_values, user_id in routes:
|
2012-08-10 13:19:19 +00:00
|
|
|
if self._name != model:
|
|
|
|
context.update({'thread_model': model})
|
|
|
|
model_pool = self.pool.get(model)
|
|
|
|
assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
|
|
|
|
"Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \
|
2012-10-09 13:40:20 +00:00
|
|
|
(msg['message_id'], model)
|
2012-08-10 13:19:19 +00:00
|
|
|
if thread_id and hasattr(model_pool, 'message_update'):
|
|
|
|
model_pool.message_update(cr, user_id, [thread_id], msg, context=context)
|
|
|
|
else:
|
|
|
|
thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=context)
|
2012-10-09 13:31:32 +00:00
|
|
|
model_pool.message_post(cr, uid, [thread_id], context=context, **msg)
|
2012-09-13 07:17:24 +00:00
|
|
|
return thread_id
|
2011-07-22 16:34:57 +00:00
|
|
|
|
|
|
|
def message_new(self, cr, uid, msg_dict, custom_values=None, context=None):
|
2011-08-23 17:58:09 +00:00
|
|
|
"""Called by ``message_process`` when a new message is received
|
2012-04-25 05:41:43 +00:00
|
|
|
for a given thread model, if the message did not belong to
|
2011-08-25 12:27:57 +00:00
|
|
|
an existing thread.
|
|
|
|
The default behavior is to create a new record of the corresponding
|
2012-09-04 14:50:11 +00:00
|
|
|
model (based on some very basic info extracted from the message).
|
2011-07-22 16:34:57 +00:00
|
|
|
Additional behavior may be implemented by overriding this method.
|
|
|
|
|
|
|
|
:param dict msg_dict: a map containing the email details and
|
2011-08-23 17:58:09 +00:00
|
|
|
attachments. See ``message_process`` and
|
2011-08-25 12:27:57 +00:00
|
|
|
``mail.message.parse`` for details.
|
2011-07-22 16:34:57 +00:00
|
|
|
:param dict custom_values: optional dictionary of additional
|
|
|
|
field values to pass to create()
|
|
|
|
when creating the new thread record.
|
|
|
|
Be careful, these values may override
|
|
|
|
any other values coming from the message.
|
|
|
|
:param dict context: if a ``thread_model`` value is present
|
|
|
|
in the context, its value will be used
|
|
|
|
to determine the model of the record
|
|
|
|
to create (instead of the current model).
|
|
|
|
:rtype: int
|
|
|
|
:return: the id of the newly created thread object
|
|
|
|
"""
|
|
|
|
if context is None:
|
|
|
|
context = {}
|
|
|
|
model = context.get('thread_model') or self._name
|
|
|
|
model_pool = self.pool.get(model)
|
|
|
|
fields = model_pool.fields_get(cr, uid, context=context)
|
|
|
|
data = model_pool.default_get(cr, uid, fields, context=context)
|
|
|
|
if 'name' in fields and not data.get('name'):
|
2012-08-14 08:04:21 +00:00
|
|
|
data['name'] = msg_dict.get('subject', '')
|
2011-07-22 16:34:57 +00:00
|
|
|
if custom_values and isinstance(custom_values, dict):
|
|
|
|
data.update(custom_values)
|
|
|
|
res_id = model_pool.create(cr, uid, data, context=context)
|
|
|
|
return res_id
|
|
|
|
|
2012-06-04 14:12:54 +00:00
|
|
|
def message_update(self, cr, uid, ids, msg_dict, update_vals=None, context=None):
|
2011-08-23 17:58:09 +00:00
|
|
|
"""Called by ``message_process`` when a new message is received
|
2012-09-04 14:50:11 +00:00
|
|
|
for an existing thread. The default behavior is to update the record
|
|
|
|
with update_vals taken from the incoming email.
|
2011-07-22 16:34:57 +00:00
|
|
|
Additional behavior may be implemented by overriding this
|
|
|
|
method.
|
|
|
|
:param dict msg_dict: a map containing the email details and
|
2012-07-05 10:22:19 +00:00
|
|
|
attachments. See ``message_process`` and
|
|
|
|
``mail.message.parse()`` for details.
|
|
|
|
:param dict update_vals: a dict containing values to update records
|
2012-06-04 14:12:54 +00:00
|
|
|
given their ids; if the dict is None or is
|
|
|
|
void, no write operation is performed.
|
2011-07-22 16:34:57 +00:00
|
|
|
"""
|
2012-06-04 14:12:54 +00:00
|
|
|
if update_vals:
|
|
|
|
self.write(cr, uid, ids, update_vals, context=context)
|
2011-07-22 16:34:57 +00:00
|
|
|
return True
|
|
|
|
|
2012-08-23 18:54:43 +00:00
|
|
|
def _message_extract_payload(self, message, save_original=False):
|
|
|
|
"""Extract body as HTML and attachments from the mail message"""
|
|
|
|
attachments = []
|
|
|
|
body = u''
|
|
|
|
if save_original:
|
|
|
|
attachments.append(('original_email.eml', message.as_string()))
|
|
|
|
if not message.is_multipart() or 'text/' in message.get('content-type', ''):
|
|
|
|
encoding = message.get_content_charset()
|
|
|
|
body = message.get_payload(decode=True)
|
|
|
|
body = tools.ustr(body, encoding, errors='replace')
|
2012-09-05 16:01:45 +00:00
|
|
|
if message.get_content_type() == 'text/plain':
|
|
|
|
# text/plain -> <pre/>
|
|
|
|
body = tools.append_content_to_html(u'', body)
|
2012-08-23 18:54:43 +00:00
|
|
|
else:
|
|
|
|
alternative = (message.get_content_type() == 'multipart/alternative')
|
|
|
|
for part in message.walk():
|
|
|
|
if part.get_content_maintype() == 'multipart':
|
|
|
|
continue # skip container
|
|
|
|
filename = part.get_filename() # None if normal part
|
|
|
|
encoding = part.get_content_charset() # None if attachment
|
|
|
|
# 1) Explicit Attachments -> attachments
|
2012-09-05 15:51:21 +00:00
|
|
|
if filename or part.get('content-disposition', '').strip().startswith('attachment'):
|
2012-08-23 18:54:43 +00:00
|
|
|
attachments.append((filename or 'attachment', part.get_payload(decode=True)))
|
|
|
|
continue
|
|
|
|
# 2) text/plain -> <pre/>
|
|
|
|
if part.get_content_type() == 'text/plain' and (not alternative or not body):
|
2012-08-31 15:51:03 +00:00
|
|
|
body = tools.append_content_to_html(body, tools.ustr(part.get_payload(decode=True),
|
|
|
|
encoding, errors='replace'))
|
2012-08-23 18:54:43 +00:00
|
|
|
# 3) text/html -> raw
|
|
|
|
elif part.get_content_type() == 'text/html':
|
|
|
|
html = tools.ustr(part.get_payload(decode=True), encoding, errors='replace')
|
|
|
|
if alternative:
|
|
|
|
body = html
|
2011-09-07 15:13:48 +00:00
|
|
|
else:
|
2012-08-31 15:51:03 +00:00
|
|
|
body = tools.append_content_to_html(body, html, plaintext=False)
|
2012-08-23 18:54:43 +00:00
|
|
|
# 4) Anything else -> attachment
|
|
|
|
else:
|
|
|
|
attachments.append((filename or 'attachment', part.get_payload(decode=True)))
|
|
|
|
return body, attachments
|
|
|
|
|
|
|
|
def message_parse(self, cr, uid, message, save_original=False, context=None):
|
2012-08-16 15:48:23 +00:00
|
|
|
"""Parses a string or email.message.Message representing an
|
|
|
|
RFC-2822 email, and returns a generic dict holding the
|
|
|
|
message details.
|
2011-07-22 16:34:57 +00:00
|
|
|
|
2012-08-16 15:48:23 +00:00
|
|
|
:param message: the message to parse
|
|
|
|
:type message: email.message.Message | string | unicode
|
|
|
|
:param bool save_original: whether the returned dict
|
2012-08-23 18:54:43 +00:00
|
|
|
should include an ``original`` attachment containing
|
|
|
|
the source of the message
|
2011-07-22 16:34:57 +00:00
|
|
|
:rtype: dict
|
2012-08-16 15:48:23 +00:00
|
|
|
:return: A dict with the following structure, where each
|
|
|
|
field may not be present if missing in original
|
|
|
|
message::
|
|
|
|
|
2012-10-09 13:40:20 +00:00
|
|
|
{ 'message_id': msg_id,
|
2012-08-16 15:48:23 +00:00
|
|
|
'subject': subject,
|
2012-08-23 18:54:43 +00:00
|
|
|
'from': from,
|
|
|
|
'to': to,
|
|
|
|
'cc': cc,
|
2012-08-31 08:01:03 +00:00
|
|
|
'body': unified_body,
|
2012-08-16 15:48:23 +00:00
|
|
|
'attachments': [('file1', 'bytes'),
|
2012-08-23 18:54:43 +00:00
|
|
|
('file2', 'bytes')}
|
2012-08-16 15:48:23 +00:00
|
|
|
}
|
2011-07-22 16:34:57 +00:00
|
|
|
"""
|
2012-10-25 11:30:48 +00:00
|
|
|
msg_dict = {
|
|
|
|
'type': 'email',
|
|
|
|
'subtype': 'mail.mt_comment',
|
2012-10-25 13:50:20 +00:00
|
|
|
'author_id': False,
|
2012-10-25 11:30:48 +00:00
|
|
|
}
|
2012-08-23 18:54:43 +00:00
|
|
|
if not isinstance(message, Message):
|
|
|
|
if isinstance(message, unicode):
|
|
|
|
# Warning: message_from_string doesn't always work correctly on unicode,
|
|
|
|
# we must use utf-8 strings here :-(
|
|
|
|
message = message.encode('utf-8')
|
|
|
|
message = email.message_from_string(message)
|
|
|
|
|
|
|
|
message_id = message['message-id']
|
2012-08-16 15:48:23 +00:00
|
|
|
if not message_id:
|
|
|
|
# Very unusual situation, be we should be fault-tolerant here
|
2012-08-23 18:54:43 +00:00
|
|
|
message_id = "<%s@localhost>" % time.time()
|
|
|
|
_logger.debug('Parsing Message without message-id, generating a random one: %s', message_id)
|
|
|
|
msg_dict['message_id'] = message_id
|
2012-08-16 15:48:23 +00:00
|
|
|
|
2012-08-23 18:54:43 +00:00
|
|
|
if 'Subject' in message:
|
|
|
|
msg_dict['subject'] = decode(message.get('Subject'))
|
2012-08-16 15:48:23 +00:00
|
|
|
|
2012-10-25 13:50:20 +00:00
|
|
|
# Envelope fields not stored in mail.message but made available for message_new()
|
2012-08-23 18:54:43 +00:00
|
|
|
msg_dict['from'] = decode(message.get('from'))
|
|
|
|
msg_dict['to'] = decode(message.get('to'))
|
|
|
|
msg_dict['cc'] = decode(message.get('cc'))
|
2012-08-16 15:48:23 +00:00
|
|
|
|
2012-08-23 18:54:43 +00:00
|
|
|
if 'From' in message:
|
|
|
|
author_ids = self._message_find_partners(cr, uid, message, ['From'], context=context)
|
2012-08-16 15:48:23 +00:00
|
|
|
if author_ids:
|
2012-08-23 18:54:43 +00:00
|
|
|
msg_dict['author_id'] = author_ids[0]
|
2012-10-25 11:30:48 +00:00
|
|
|
else:
|
2012-10-25 13:50:20 +00:00
|
|
|
msg_dict['email_from'] = message.get('from')
|
2012-09-05 15:51:21 +00:00
|
|
|
partner_ids = self._message_find_partners(cr, uid, message, ['From', 'To', 'Cc'], context=context)
|
2012-11-07 10:51:48 +00:00
|
|
|
msg_dict['partner_ids'] = [(4, partner_id) for partner_id in partner_ids]
|
2012-08-16 15:48:23 +00:00
|
|
|
|
2012-08-23 18:54:43 +00:00
|
|
|
if 'Date' in message:
|
|
|
|
date_hdr = decode(message.get('Date'))
|
2012-08-22 11:34:39 +00:00
|
|
|
# convert from email timezone to server timezone
|
|
|
|
date_server_datetime = dateutil.parser.parse(date_hdr).astimezone(pytz.timezone(tools.get_server_timezone()))
|
|
|
|
date_server_datetime_str = date_server_datetime.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
2012-08-23 18:54:43 +00:00
|
|
|
msg_dict['date'] = date_server_datetime_str
|
2012-08-16 15:48:23 +00:00
|
|
|
|
2012-08-23 18:54:43 +00:00
|
|
|
if 'In-Reply-To' in message:
|
2012-09-05 15:51:21 +00:00
|
|
|
parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', decode(message['In-Reply-To']))])
|
2012-08-28 17:39:01 +00:00
|
|
|
if parent_ids:
|
|
|
|
msg_dict['parent_id'] = parent_ids[0]
|
|
|
|
|
|
|
|
if 'References' in message and 'parent_id' not in msg_dict:
|
2012-09-05 15:51:21 +00:00
|
|
|
parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'in',
|
2012-08-28 17:39:01 +00:00
|
|
|
[x.strip() for x in decode(message['References']).split()])])
|
|
|
|
if parent_ids:
|
|
|
|
msg_dict['parent_id'] = parent_ids[0]
|
2012-09-05 15:51:21 +00:00
|
|
|
|
2012-08-23 18:54:43 +00:00
|
|
|
msg_dict['body'], msg_dict['attachments'] = self._message_extract_payload(message)
|
|
|
|
return msg_dict
|
2012-02-01 16:21:36 +00:00
|
|
|
|
|
|
|
#------------------------------------------------------
|
|
|
|
# Note specific
|
|
|
|
#------------------------------------------------------
|
2012-04-25 05:41:43 +00:00
|
|
|
|
2012-04-02 11:50:02 +00:00
|
|
|
def log(self, cr, uid, id, message, secondary=False, context=None):
|
2012-09-04 13:36:48 +00:00
|
|
|
_logger.warning("log() is deprecated. As this module inherit from "\
|
|
|
|
"mail.thread, the message will be managed by this "\
|
|
|
|
"module instead of by the res.log mechanism. Please "\
|
|
|
|
"use mail_thread.message_post() instead of the "\
|
|
|
|
"now deprecated res.log.")
|
2012-08-22 11:34:39 +00:00
|
|
|
self.message_post(cr, uid, [id], message, context=context)
|
|
|
|
|
2012-10-01 13:05:30 +00:00
|
|
|
def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
|
2012-09-20 10:17:04 +00:00
|
|
|
subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
|
2012-09-04 13:36:48 +00:00
|
|
|
""" Post a new message in an existing thread, returning the new
|
2012-08-31 08:01:03 +00:00
|
|
|
mail.message ID. Extra keyword arguments will be used as default
|
|
|
|
column values for the new mail.message record.
|
2012-09-28 13:27:23 +00:00
|
|
|
Auto link messages for same id and object
|
2012-11-07 15:39:10 +00:00
|
|
|
:param int thread_id: thread ID to post into, or list with one ID;
|
|
|
|
if False/0, mail.message model will also be set as False
|
2012-08-31 08:01:03 +00:00
|
|
|
:param str body: body of the message, usually raw HTML that will
|
|
|
|
be sanitized
|
|
|
|
:param str subject: optional subject
|
2012-09-04 13:36:48 +00:00
|
|
|
:param str type: mail_message.type
|
2012-08-31 08:01:03 +00:00
|
|
|
:param int parent_id: optional ID of parent message in this thread
|
2012-10-08 14:26:54 +00:00
|
|
|
:param tuple(str,str) attachments or list id: list of attachment tuples in the form
|
2012-08-31 08:01:03 +00:00
|
|
|
``(name,content)``, where content is NOT base64 encoded
|
2012-09-05 15:51:21 +00:00
|
|
|
:return: ID of newly created mail.message
|
2012-08-22 11:34:39 +00:00
|
|
|
"""
|
2012-08-17 10:03:02 +00:00
|
|
|
context = context or {}
|
2012-08-31 08:01:03 +00:00
|
|
|
attachments = attachments or []
|
2012-09-20 10:17:04 +00:00
|
|
|
assert (not thread_id) or isinstance(thread_id, (int, long)) or \
|
2012-10-02 10:29:15 +00:00
|
|
|
(isinstance(thread_id, (list, tuple)) and len(thread_id) == 1), "Invalid thread_id"
|
2012-08-22 11:34:39 +00:00
|
|
|
if isinstance(thread_id, (list, tuple)):
|
|
|
|
thread_id = thread_id and thread_id[0]
|
2012-10-15 13:23:13 +00:00
|
|
|
mail_message = self.pool.get('mail.message')
|
|
|
|
model = context.get('thread_model', self._name) if thread_id else False
|
2012-08-17 10:03:02 +00:00
|
|
|
|
2012-10-15 13:23:13 +00:00
|
|
|
attachment_ids = []
|
2012-08-22 11:34:39 +00:00
|
|
|
for name, content in attachments:
|
|
|
|
if isinstance(content, unicode):
|
|
|
|
content = content.encode('utf-8')
|
2012-08-17 10:03:02 +00:00
|
|
|
data_attach = {
|
2012-08-22 11:34:39 +00:00
|
|
|
'name': name,
|
2012-08-23 18:54:43 +00:00
|
|
|
'datas': base64.b64encode(str(content)),
|
2012-08-22 11:34:39 +00:00
|
|
|
'datas_fname': name,
|
|
|
|
'description': name,
|
2012-08-23 18:54:43 +00:00
|
|
|
'res_model': context.get('thread_model') or self._name,
|
|
|
|
'res_id': thread_id,
|
2012-08-17 10:03:02 +00:00
|
|
|
}
|
2012-09-05 15:51:21 +00:00
|
|
|
attachment_ids.append((0, 0, data_attach))
|
2012-08-17 10:03:02 +00:00
|
|
|
|
2012-10-15 13:23:13 +00:00
|
|
|
# fetch subtype
|
|
|
|
if subtype:
|
|
|
|
s_data = subtype.split('.')
|
|
|
|
if len(s_data) == 1:
|
|
|
|
s_data = ('mail', s_data[0])
|
|
|
|
ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, s_data[0], s_data[1])
|
|
|
|
subtype_id = ref and ref[1] or False
|
|
|
|
else:
|
|
|
|
subtype_id = False
|
2012-09-25 08:58:09 +00:00
|
|
|
|
2012-10-15 13:23:13 +00:00
|
|
|
# _mail_flat_thread: automatically set free messages to the first posted message
|
|
|
|
if self._mail_flat_thread and not parent_id and thread_id:
|
|
|
|
message_ids = mail_message.search(cr, uid, ['&', ('res_id', '=', thread_id), ('model', '=', model)], context=context, order="id ASC", limit=1)
|
|
|
|
parent_id = message_ids and message_ids[0] or False
|
2012-10-25 15:11:23 +00:00
|
|
|
# we want to set a parent: force to set the parent_id to the oldest ancestor, to avoid having more than 1 level of thread
|
|
|
|
elif parent_id:
|
|
|
|
message_ids = mail_message.search(cr, SUPERUSER_ID, [('id', '=', parent_id), ('parent_id', '!=', False)], context=context)
|
2012-10-26 12:36:04 +00:00
|
|
|
# avoid loops when finding ancestors
|
|
|
|
processed_list = []
|
2012-10-25 15:42:15 +00:00
|
|
|
if message_ids:
|
|
|
|
message = mail_message.browse(cr, SUPERUSER_ID, message_ids[0], context=context)
|
2012-10-26 12:36:04 +00:00
|
|
|
while (message.parent_id and message.parent_id.id not in processed_list):
|
|
|
|
processed_list.append(message.parent_id.id)
|
2012-10-25 15:42:15 +00:00
|
|
|
message = message.parent_id
|
|
|
|
parent_id = message.id
|
2012-09-25 08:58:09 +00:00
|
|
|
|
2012-08-22 11:34:39 +00:00
|
|
|
values = kwargs
|
2012-09-05 15:51:21 +00:00
|
|
|
values.update({
|
2012-09-25 08:58:09 +00:00
|
|
|
'model': model,
|
2012-08-22 12:49:43 +00:00
|
|
|
'res_id': thread_id or False,
|
2012-08-17 10:03:02 +00:00
|
|
|
'body': body,
|
2012-10-01 13:05:30 +00:00
|
|
|
'subject': subject or False,
|
2012-09-04 13:36:48 +00:00
|
|
|
'type': type,
|
2012-08-21 10:43:45 +00:00
|
|
|
'parent_id': parent_id,
|
2012-08-31 08:01:03 +00:00
|
|
|
'attachment_ids': attachment_ids,
|
2012-09-20 10:17:04 +00:00
|
|
|
'subtype_id': subtype_id,
|
2012-08-17 10:03:02 +00:00
|
|
|
})
|
2012-10-03 14:27:12 +00:00
|
|
|
|
2012-09-20 10:17:04 +00:00
|
|
|
# Avoid warnings about non-existing fields
|
|
|
|
for x in ('from', 'to', 'cc'):
|
|
|
|
values.pop(x, None)
|
2012-09-25 08:58:09 +00:00
|
|
|
|
2012-10-15 13:23:13 +00:00
|
|
|
return mail_message.create(cr, uid, values, context=context)
|
2012-04-25 05:41:43 +00:00
|
|
|
|
2012-10-29 10:12:30 +00:00
|
|
|
def message_post_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False, attachment_ids=None, context=None):
|
2012-10-25 08:20:01 +00:00
|
|
|
""" Wrapper on message_post, used only in Chatter (JS). The purpose is
|
|
|
|
to handle attachments.
|
|
|
|
# TDE FIXME: body is plaintext: convert it into html
|
|
|
|
"""
|
2012-10-29 10:12:30 +00:00
|
|
|
new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type='comment',
|
2012-10-25 11:30:48 +00:00
|
|
|
subtype='mail.mt_comment', parent_id=parent_id, context=context)
|
2012-10-08 14:26:54 +00:00
|
|
|
|
2012-10-25 07:50:36 +00:00
|
|
|
# HACK FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
|
2012-10-25 08:20:01 +00:00
|
|
|
if attachment_ids:
|
2012-10-08 14:26:54 +00:00
|
|
|
ir_attachment = self.pool.get('ir.attachment')
|
2012-10-15 13:23:13 +00:00
|
|
|
mail_message = self.pool.get('mail.message')
|
2012-10-25 08:20:01 +00:00
|
|
|
filtered_attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [
|
2012-10-29 10:00:01 +00:00
|
|
|
('res_model', '=', 'mail.compose.message'),
|
2012-10-25 08:20:01 +00:00
|
|
|
('res_id', '=', 0),
|
|
|
|
('create_uid', '=', uid),
|
|
|
|
('id', 'in', attachment_ids)], context=context)
|
|
|
|
if filtered_attachment_ids:
|
2012-10-19 09:59:19 +00:00
|
|
|
ir_attachment.write(cr, SUPERUSER_ID, attachment_ids, {'res_model': self._name, 'res_id': thread_id}, context=context)
|
|
|
|
mail_message.write(cr, SUPERUSER_ID, [new_message_id], {'attachment_ids': [(6, 0, [pid for pid in attachment_ids])]}, context=context)
|
|
|
|
|
2012-10-25 08:20:01 +00:00
|
|
|
return new_message_id
|
2012-09-27 13:48:23 +00:00
|
|
|
|
2012-10-15 13:23:13 +00:00
|
|
|
#------------------------------------------------------
|
|
|
|
# Followers API
|
|
|
|
#------------------------------------------------------
|
2012-09-27 13:48:23 +00:00
|
|
|
|
2012-10-15 13:34:38 +00:00
|
|
|
def message_get_subscription_data(self, cr, uid, ids, context=None):
|
2012-10-18 13:06:01 +00:00
|
|
|
""" Wrapper to get subtypes data. """
|
2012-09-25 08:58:09 +00:00
|
|
|
return self._get_subscription_data(cr, uid, ids, None, None, context=context)
|
|
|
|
|
2012-09-20 10:17:04 +00:00
|
|
|
def message_subscribe_users(self, cr, uid, ids, user_ids=None, subtype_ids=None, context=None):
|
2012-08-22 11:03:13 +00:00
|
|
|
""" Wrapper on message_subscribe, using users. If user_ids is not
|
|
|
|
provided, subscribe uid instead. """
|
2012-10-15 13:23:13 +00:00
|
|
|
if user_ids is None:
|
|
|
|
user_ids = [uid]
|
2012-08-22 11:03:13 +00:00
|
|
|
partner_ids = [user.partner_id.id for user in self.pool.get('res.users').browse(cr, uid, user_ids, context=context)]
|
2012-09-20 10:17:04 +00:00
|
|
|
return self.message_subscribe(cr, uid, ids, partner_ids, subtype_ids=subtype_ids, context=context)
|
2012-08-16 10:18:48 +00:00
|
|
|
|
2012-09-20 10:17:04 +00:00
|
|
|
def message_subscribe(self, cr, uid, ids, partner_ids, subtype_ids=None, context=None):
|
2012-09-12 13:37:11 +00:00
|
|
|
""" Add partners to the records followers. """
|
2012-09-20 10:17:04 +00:00
|
|
|
self.write(cr, uid, ids, {'message_follower_ids': [(4, pid) for pid in partner_ids]}, context=context)
|
2012-09-20 11:49:47 +00:00
|
|
|
# if subtypes are not specified (and not set to a void list), fetch default ones
|
|
|
|
if subtype_ids is None:
|
2012-08-29 06:55:14 +00:00
|
|
|
subtype_obj = self.pool.get('mail.message.subtype')
|
2012-09-20 10:17:04 +00:00
|
|
|
subtype_ids = subtype_obj.search(cr, uid, [('default', '=', True), '|', ('res_model', '=', self._name), ('res_model', '=', False)], context=context)
|
2012-09-20 11:49:47 +00:00
|
|
|
# update the subscriptions
|
2012-09-20 10:17:04 +00:00
|
|
|
fol_obj = self.pool.get('mail.followers')
|
2012-10-15 13:23:13 +00:00
|
|
|
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', 'in', ids), ('partner_id', 'in', partner_ids)], context=context)
|
|
|
|
fol_obj.write(cr, SUPERUSER_ID, fol_ids, {'subtype_ids': [(6, 0, subtype_ids)]}, context=context)
|
2012-09-20 10:17:04 +00:00
|
|
|
return True
|
2012-02-01 16:21:36 +00:00
|
|
|
|
2012-08-22 11:03:13 +00:00
|
|
|
def message_unsubscribe_users(self, cr, uid, ids, user_ids=None, context=None):
|
|
|
|
""" Wrapper on message_subscribe, using users. If user_ids is not
|
|
|
|
provided, unsubscribe uid instead. """
|
2012-10-15 13:23:13 +00:00
|
|
|
if user_ids is None:
|
2012-09-20 10:17:04 +00:00
|
|
|
user_ids = [uid]
|
2012-08-22 11:03:13 +00:00
|
|
|
partner_ids = [user.partner_id.id for user in self.pool.get('res.users').browse(cr, uid, user_ids, context=context)]
|
|
|
|
return self.message_unsubscribe(cr, uid, ids, partner_ids, context=context)
|
2012-08-15 13:36:43 +00:00
|
|
|
|
2012-08-22 11:03:13 +00:00
|
|
|
def message_unsubscribe(self, cr, uid, ids, partner_ids, context=None):
|
2012-09-12 13:37:11 +00:00
|
|
|
""" Remove partners from the records followers. """
|
|
|
|
return self.write(cr, uid, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context)
|
2012-02-01 16:21:36 +00:00
|
|
|
|
2012-03-21 17:20:18 +00:00
|
|
|
#------------------------------------------------------
|
2012-08-28 12:55:22 +00:00
|
|
|
# Thread state
|
2012-06-04 09:33:24 +00:00
|
|
|
#------------------------------------------------------
|
2012-06-07 15:17:53 +00:00
|
|
|
|
2012-08-17 13:34:49 +00:00
|
|
|
def message_mark_as_unread(self, cr, uid, ids, context=None):
|
2012-08-28 12:55:22 +00:00
|
|
|
""" Set as unread. """
|
2012-08-17 13:34:49 +00:00
|
|
|
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
|
|
|
|
cr.execute('''
|
2012-09-05 15:51:21 +00:00
|
|
|
UPDATE mail_notification SET
|
2012-08-17 13:34:49 +00:00
|
|
|
read=false
|
|
|
|
WHERE
|
|
|
|
message_id IN (SELECT id from mail_message where res_id=any(%s) and model=%s limit 1) and
|
|
|
|
partner_id = %s
|
|
|
|
''', (ids, self._name, partner_id))
|
|
|
|
return True
|
2011-07-22 16:34:57 +00:00
|
|
|
|
2012-06-25 16:13:12 +00:00
|
|
|
def message_mark_as_read(self, cr, uid, ids, context=None):
|
2012-07-02 15:46:30 +00:00
|
|
|
""" Set as read. """
|
2012-08-17 13:34:49 +00:00
|
|
|
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
|
2012-08-15 13:36:43 +00:00
|
|
|
cr.execute('''
|
2012-09-05 15:51:21 +00:00
|
|
|
UPDATE mail_notification SET
|
2012-08-15 13:36:43 +00:00
|
|
|
read=true
|
2012-08-17 13:34:49 +00:00
|
|
|
WHERE
|
|
|
|
message_id IN (SELECT id FROM mail_message WHERE res_id=ANY(%s) AND model=%s) AND
|
|
|
|
partner_id = %s
|
|
|
|
''', (ids, self._name, partner_id))
|
2012-08-15 13:36:43 +00:00
|
|
|
return True
|
2012-06-25 16:13:12 +00:00
|
|
|
|
2011-07-22 16:34:57 +00:00
|
|
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|