summaryrefslogtreecommitdiff
path: root/addons/mail/mail_message.py
diff options
context:
space:
mode:
Diffstat (limited to 'addons/mail/mail_message.py')
-rw-r--r--addons/mail/mail_message.py541
1 files changed, 354 insertions, 187 deletions
diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py
index 8719d59bf04..bd0ade31b68 100644
--- a/addons/mail/mail_message.py
+++ b/addons/mail/mail_message.py
@@ -24,11 +24,17 @@ import tools
from email.header import decode_header
from openerp import SUPERUSER_ID
-from osv import osv, orm, fields
-from tools.translate import _
+from openerp.osv import osv, orm, fields
+from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
+try:
+ from mako.template import Template as MakoTemplate
+except ImportError:
+ _logger.warning("payment_acquirer: mako templates not available, payment acquirer will not work!")
+
+
""" Some tools for parsing / creating email fields """
def decode(text):
"""Returns unicode() string conversion of the the given encoded smtp header text"""
@@ -46,7 +52,7 @@ class mail_message(osv.Model):
_order = 'id desc'
_message_read_limit = 10
- _message_read_fields = ['id', 'parent_id', 'model', 'res_id', 'body', 'subject', 'date', 'to_read',
+ _message_read_fields = ['id', 'parent_id', 'model', 'res_id', 'body', 'subject', 'date', 'to_read', 'email_from',
'type', 'vote_user_ids', 'attachment_ids', 'author_id', 'partner_ids', 'record_name', 'favorite_user_ids']
_message_record_name_length = 18
_message_read_more_limit = 1024
@@ -57,17 +63,14 @@ class mail_message(osv.Model):
return name[:self._message_record_name_length] + '...'
def _get_record_name(self, cr, uid, ids, name, arg, context=None):
- """ Return the related document name, using name_get. It is included in
- a try/except statement, because if uid cannot read the related
- document, he should see a void string instead of crashing. """
+ """ Return the related document name, using name_get. It is done using
+ SUPERUSER_ID, to be sure to have the record name correctly stored. """
+ # TDE note: regroup by model/ids, to have less queries to perform
result = dict.fromkeys(ids, False)
for message in self.read(cr, uid, ids, ['model', 'res_id'], context=context):
- if not message['model'] or not message['res_id']:
+ if not message.get('model') or not message.get('res_id'):
continue
- try:
- result[message['id']] = self._shorten_name(self.pool.get(message['model']).name_get(cr, uid, [message['res_id']], context=context)[0][1])
- except (orm.except_orm, osv.except_osv):
- pass
+ result[message['id']] = self._shorten_name(self.pool.get(message['model']).name_get(cr, SUPERUSER_ID, [message['res_id']], context=context)[0][1])
return result
def _get_to_read(self, cr, uid, ids, name, arg, context=None):
@@ -78,10 +81,10 @@ class mail_message(osv.Model):
notif_ids = notif_obj.search(cr, uid, [
('partner_id', 'in', [partner_id]),
('message_id', 'in', ids),
- ('read', '=', False)
+ ('read', '=', False),
], context=context)
for notif in notif_obj.browse(cr, uid, notif_ids, context=context):
- res[notif.message_id.id] = not notif.read
+ res[notif.message_id.id] = True
return res
def _search_to_read(self, cr, uid, obj, name, domain, context=None):
@@ -115,16 +118,21 @@ class mail_message(osv.Model):
], 'Type',
help="Message type: email for email message, notification for system "\
"message, comment for other messages such as user replies"),
- 'author_id': fields.many2one('res.partner', 'Author', required=True),
- 'partner_ids': fields.many2many('res.partner', 'mail_notification', 'message_id', 'partner_id', 'Recipients'),
+ 'email_from': fields.char('From',
+ help="Email address of the sender. This field is set when no matching partner is found for incoming emails."),
+ 'author_id': fields.many2one('res.partner', 'Author',
+ help="Author of the message. If not set, email_from may hold an email address that did not match any partner."),
+ 'partner_ids': fields.many2many('res.partner', string='Recipients'),
+ 'notified_partner_ids': fields.many2many('res.partner', 'mail_notification',
+ 'message_id', 'partner_id', 'Recipients'),
'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel',
'message_id', 'attachment_id', 'Attachments'),
'parent_id': fields.many2one('mail.message', 'Parent Message', select=True, ondelete='set null', help="Initial thread message."),
'child_ids': fields.one2many('mail.message', 'parent_id', 'Child Messages'),
'model': fields.char('Related Document Model', size=128, select=1),
'res_id': fields.integer('Related Document ID', select=1),
- 'record_name': fields.function(_get_record_name, type='string',
- string='Message Record Name',
+ 'record_name': fields.function(_get_record_name, type='char',
+ store=True, string='Message Record Name',
help="Name get of the related document."),
'notification_ids': fields.one2many('mail.notification', 'message_id', 'Notifications'),
'subject': fields.char('Subject'),
@@ -198,19 +206,33 @@ class mail_message(osv.Model):
:param dict message: read result of a mail.message
"""
+ # TDE note: this method should be optimized, to lessen the number of queries, will be done ASAP
+ is_author = False
+ if message['author_id']:
+ is_author = message['author_id'][0] == self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
+ author_id = message['author_id']
+ elif message['email_from']:
+ author_id = (0, message['email_from'])
+
has_voted = False
- if uid in message['vote_user_ids']:
+ if uid in message.get('vote_user_ids'):
has_voted = True
is_favorite = False
- if uid in message['favorite_user_ids']:
+ if uid in message.get('favorite_user_ids'):
is_favorite = True
+ is_private = True
+ if message.get('model') and message.get('res_id'):
+ is_private = False
+
try:
attachment_ids = [{'id': attach[0], 'name': attach[1]} for attach in self.pool.get('ir.attachment').name_get(cr, uid, message['attachment_ids'], context=context)]
except (orm.except_orm, osv.except_osv):
attachment_ids = []
+ # TDE note: should we send partner_ids ?
+ # TDE note: shouldn't we separated followers and other partners ? costly to compute maybe ,
try:
partner_ids = self.pool.get('res.partner').name_get(cr, uid, message['partner_ids'], context=context)
except (orm.except_orm, osv.except_osv):
@@ -226,95 +248,106 @@ class mail_message(osv.Model):
'record_name': message['record_name'],
'subject': message['subject'],
'date': message['date'],
- 'author_id': message['author_id'],
- 'is_author': message['author_id'] and message['author_id'][0] == uid,
- # TDE note: is this useful ? to check
+ 'author_id': author_id,
+ 'is_author': is_author,
'partner_ids': partner_ids,
- 'parent_id': message['parent_id'] and message['parent_id'][0] or False,
- # TDE note: see with CHM about votes, how they are displayed (only number, or name_get ?)
- # vote: should only use number of votes
+ 'parent_id': False,
'vote_nb': len(message['vote_user_ids']),
'has_voted': has_voted,
- 'is_private': message['model'] and message['res_id'],
+ 'is_private': is_private,
'is_favorite': is_favorite,
'to_read': message['to_read'],
}
- def _message_read_expandable(self, cr, uid, message_list, read_messages,
- message_loaded_ids=[], domain=[], context=None, parent_id=False, limit=None):
- """ Create the expandable message for all parent message read
- this function is used by message_read
-
- :param list message_list: list of messages given by message_read to
- which we have to add expandables
+ def _message_read_add_expandables(self, cr, uid, message_list, read_messages,
+ thread_level=0, message_loaded_ids=[], domain=[], parent_id=False, context=None, limit=None):
+ """ Create expandables for message_read, to load new messages.
+ 1. get the expandable for new threads
+ if display is flat (thread_level == 0):
+ fetch message_ids < min(already displayed ids), because we
+ want a flat display, ordered by id
+ else:
+ fetch message_ids that are not childs of already displayed
+ messages
+ 2. get the expandables for new messages inside threads if display
+ is not flat
+ for each thread header, search for its childs
+ for each hole in the child list based on message displayed,
+ create an expandable
+
+ :param list message_list:list of message structure for the Chatter
+ widget to which expandables are added
:param dict read_messages: dict [id]: read result of the messages to
easily have access to their values, given their ID
+ :return bool: True
"""
- # sort for group items / TDE: move to message_read
- # result = sorted(result, key=lambda k: k['id'])
- tree_not = []
- # expandable for not show message
+ def _get_expandable(domain, message_nb, parent_id, id, model):
+ return {
+ 'domain': domain,
+ 'nb_messages': message_nb,
+ 'type': 'expandable',
+ 'parent_id': parent_id,
+ 'id': id,
+ # TDE note: why do we need model sometimes, and sometimes not ???
+ 'model': model,
+ }
+
+ # all_not_loaded_ids = []
id_list = sorted(read_messages.keys())
+ if not id_list:
+ return message_list
+
+ # 1. get the expandable for new threads
+ if thread_level == 0:
+ exp_domain = domain + [('id', '<', min(message_loaded_ids + id_list))]
+ else:
+ exp_domain = domain + ['!', ('id', 'child_of', message_loaded_ids + id_list)]
+ ids = self.search(cr, uid, exp_domain, context=context, limit=1)
+ if ids:
+ message_list.append(_get_expandable(exp_domain, -1, parent_id, -1, None))
+
+ # 2. get the expandables for new messages inside threads if display is not flat
+ if thread_level == 0:
+ return True
for message_id in id_list:
message = read_messages[message_id]
+ # message is not a thread header (has a parent_id)
+ # TDE note: parent_id is false is there is a parent we can not see -> ok
+ if message.get('parent_id'):
+ continue
+
# TDE note: check search is correctly implemented in mail.message
not_loaded_ids = self.search(cr, uid, [
- ('parent_id', '=', message['id']),
+ ('id', 'child_of', message['id']),
('id', 'not in', message_loaded_ids),
], context=context, limit=self._message_read_more_limit)
- # group childs not read
- id_min = None
- id_max = None
- nb = 0
+ if not not_loaded_ids:
+ continue
+ # all_not_loaded_ids += not_loaded_ids
+ # group childs not read
+ id_min, id_max, nb = max(not_loaded_ids), 0, 0
for not_loaded_id in not_loaded_ids:
if not read_messages.get(not_loaded_id):
nb += 1
- if id_min == None or id_min > not_loaded_id:
+ if id_min > not_loaded_id:
id_min = not_loaded_id
- if id_max == None or id_max < not_loaded_id:
+ if id_max < not_loaded_id:
id_max = not_loaded_id
- tree_not.append(not_loaded_id)
+ elif nb > 0:
+ exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)]
+ message_list.append(_get_expandable(exp_domain, nb, message_id, id_min, message.get('model')))
+ id_min, id_max, nb = max(not_loaded_ids), 0, 0
else:
- if nb > 0:
- message_list.append({
- 'domain': [('id', '>=', id_min), ('id', '<=', id_max), ('parent_id', '=', message_id)],
- 'nb_messages': nb,
- 'type': 'expandable',
- 'parent_id': message_id,
- 'id': id_min,
- 'model': message['model']
- })
- id_min = None
- id_max = None
- nb = 0
+ id_min, id_max, nb = max(not_loaded_ids), 0, 0
if nb > 0:
- message_list.append({
- 'domain': [('id', '>=', id_min), ('id', '<=', id_max), ('parent_id', '=', message_id)],
- 'nb_messages': nb,
- 'type': 'expandable',
- 'parent_id': message_id,
- 'id': id_min,
- 'model': message['model'],
- })
-
- for msg_id in read_messages.keys() + tree_not:
- message_loaded_ids.append(msg_id)
-
- # expandable for limit max
- ids = self.search(cr, uid, domain + [('id', 'not in', message_loaded_ids)], context=context, limit=1)
- if len(ids) > 0:
- message_list.append({
- 'domain': domain,
- 'nb_messages': 0,
- 'type': 'expandable',
- 'parent_id': parent_id,
- 'id': -1,
- 'max_limit': True,
- })
+ exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)]
+ message_list.append(_get_expandable(exp_domain, nb, message_id, id_min, message.get('model')))
- return message_list
+ # message_loaded_ids = list(set(message_loaded_ids + read_messages.keys() + all_not_loaded_ids))
+
+ return True
def _get_parent(self, cr, uid, message, context=None):
""" Tools method that tries to get the parent of a mail.message. If
@@ -331,57 +364,70 @@ class mail_message(osv.Model):
except (orm.except_orm, osv.except_osv):
return False
- def message_read(self, cr, uid, ids=False, domain=[], message_loaded_ids=[], context=None, parent_id=False, limit=None):
- """ Read messages from mail.message, and get back a structured tree
- of messages to be displayed as discussion threads. If IDs is set,
+ def message_read(self, cr, uid, ids=None, domain=None, message_unload_ids=None, thread_level=0, context=None, parent_id=False, limit=None):
+ """ Read messages from mail.message, and get back a list of structured
+ messages to be displayed as discussion threads. If IDs is set,
fetch these records. Otherwise use the domain to fetch messages.
- After having fetch messages, their parents & child will be added to obtain
- well formed threads.
-
- TDE note: update this comment after final method implementation
-
- :param domain: optional domain for searching ids
- :param limit: number of messages to fetch
- :param parent_id: if parent_id reached, stop searching for
- further parents
- :return list: list of trees of messages
+ After having fetch messages, their ancestors will be added to obtain
+ well formed threads, if uid has access to them.
+
+ After reading the messages, expandable messages are added in the
+ message list (see ``_message_read_add_expandables``). It consists
+ in messages holding the 'read more' data: number of messages to
+ read, domain to apply.
+
+ :param list ids: optional IDs to fetch
+ :param list domain: optional domain for searching ids if ids not set
+ :param list message_unload_ids: optional ids we do not want to fetch,
+ because i.e. they are already displayed somewhere
+ :param int parent_id: context of parent_id
+ - if parent_id reached when adding ancestors, stop going further
+ in the ancestor search
+ - if set in flat mode, ancestor_id is set to parent_id
+ :param int limit: number of messages to fetch, before adding the
+ ancestors and expandables
+ :return list: list of message structure for the Chatter widget
"""
- if message_loaded_ids:
- domain += [('id', 'not in', message_loaded_ids)]
+ # print 'message_read', ids, domain, message_unload_ids, thread_level, context, parent_id, limit
+ assert thread_level in [0, 1], 'message_read() thread_level should be 0 (flat) or 1 (1 level of thread); given %s.' % thread_level
+ domain = domain if domain is not None else []
+ message_unload_ids = message_unload_ids if message_unload_ids is not None else []
+ if message_unload_ids:
+ domain += [('id', 'not in', message_unload_ids)]
limit = limit or self._message_read_limit
read_messages = {}
message_list = []
- # specific IDs given: fetch those ids and return directly the message list
- if ids:
- for message in self.read(cr, uid, ids, self._message_read_fields, context=context):
- message_list.append(self._message_get_dict(cr, uid, message, context=context))
- message_list = sorted(message_list, key=lambda k: k['id'])
- return message_list
-
- # TDE FIXME: check access rights on search are implemented for mail.message
- # fetch messages according to the domain, add their parents if uid has access to
- ids = self.search(cr, uid, domain, context=context, limit=limit)
+ # no specific IDS given: fetch messages according to the domain, add their parents if uid has access to
+ if ids is None:
+ ids = self.search(cr, uid, domain, context=context, limit=limit)
for message in self.read(cr, uid, ids, self._message_read_fields, context=context):
- # if not in tree and not in message_loded list
- if not read_messages.get(message.get('id')) and message.get('id') not in message_loaded_ids:
- read_messages[message.get('id')] = message
+ message_id = message['id']
+
+ # if not in tree and not in message_loaded list
+ if not message_id in read_messages and not message_id in message_unload_ids:
+ read_messages[message_id] = message
message_list.append(self._message_get_dict(cr, uid, message, context=context))
- # get all parented message if the user have the access
+ # get the older ancestor the user can read, update its ancestor field
+ if not thread_level:
+ message_list[-1]['parent_id'] = parent_id
+ continue
parent = self._get_parent(cr, uid, message, context=context)
while parent and parent.get('id') != parent_id:
- if not read_messages.get(parent.get('id')) and parent.get('id') not in message_loaded_ids:
- read_messages[parent.get('id')] = parent
- message_list.append(self._message_get_dict(cr, uid, parent, context=context))
- parent = self._get_parent(cr, uid, parent, context=context)
+ message_list[-1]['parent_id'] = parent.get('id')
+ message = parent
+ parent = self._get_parent(cr, uid, message, context=context)
+ # if in thread: add its ancestor to the list of messages
+ if not message['id'] in read_messages and not message['id'] in message_unload_ids:
+ read_messages[message['id']] = message
+ message_list.append(self._message_get_dict(cr, uid, message, context=context))
# get the child expandable messages for the tree
message_list = sorted(message_list, key=lambda k: k['id'])
- message_list = self._message_read_expandable(cr, uid, message_list, read_messages,
- message_loaded_ids=message_loaded_ids, domain=domain, context=context, parent_id=parent_id, limit=limit)
+ self._message_read_add_expandables(cr, uid, message_list, read_messages, thread_level=thread_level,
+ message_loaded_ids=message_unload_ids, domain=domain, parent_id=parent_id, context=context, limit=limit)
- # message_list = sorted(message_list, key=lambda k: k['id'])
return message_list
# TDE Note: do we need this ?
@@ -394,7 +440,7 @@ class mail_message(osv.Model):
# return attachment_list
#------------------------------------------------------
- # Email api
+ # mail_message internals
#------------------------------------------------------
def init(self, cr):
@@ -402,23 +448,75 @@ class mail_message(osv.Model):
if not cr.fetchone():
cr.execute("""CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)""")
+ def _search(self, cr, uid, args, offset=0, limit=None, order=None,
+ context=None, count=False, access_rights_uid=None):
+ """ Override that adds specific access rights of mail.message, to remove
+ ids uid could not see according to our custom rules. Please refer
+ to check_access_rule for more details about those rules.
+
+ After having received ids of a classic search, keep only:
+ - if author_id == pid, uid is the author, OR
+ - a notification (id, pid) exists, uid has been notified, OR
+ - uid have read access to the related document is model, res_id
+ - otherwise: remove the id
+ """
+ # Rules do not apply to administrator
+ # print '_search', uid, args
+ if uid == SUPERUSER_ID:
+ return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
+ context=context, count=count, access_rights_uid=access_rights_uid)
+ # Perform a super with count as False, to have the ids, not a counter
+ ids = super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
+ context=context, count=False, access_rights_uid=access_rights_uid)
+ if not ids and count:
+ return 0
+ elif not ids:
+ return ids
+
+ pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'])['partner_id'][0]
+ author_ids, partner_ids, allowed_ids = set([]), set([]), set([])
+ model_ids = {}
+
+ messages = super(mail_message, self).read(cr, uid, ids, ['author_id', 'model', 'res_id', 'notified_partner_ids'], context=context)
+ for message in messages:
+ if message.get('author_id') and message.get('author_id')[0] == pid:
+ author_ids.add(message.get('id'))
+ elif pid in message.get('notified_partner_ids'):
+ partner_ids.add(message.get('id'))
+ elif message.get('model') and message.get('res_id'):
+ model_ids.setdefault(message.get('model'), {}).setdefault(message.get('res_id'), set()).add(message.get('id'))
+
+ model_access_obj = self.pool.get('ir.model.access')
+ for doc_model, doc_dict in model_ids.iteritems():
+ if not model_access_obj.check(cr, uid, doc_model, 'read', False):
+ continue
+ doc_ids = doc_dict.keys()
+ allowed_doc_ids = self.pool.get(doc_model).search(cr, uid, [('id', 'in', doc_ids)], context=context)
+ allowed_ids |= set([message_id for allowed_doc_id in allowed_doc_ids for message_id in doc_dict[allowed_doc_id]])
+
+ final_ids = author_ids | partner_ids | allowed_ids
+ if count:
+ return len(final_ids)
+ else:
+ return list(final_ids)
+
def check_access_rule(self, cr, uid, ids, operation, context=None):
""" Access rules of mail.message:
- read: if
- - notification exist (I receive pushed message) OR
- - author_id = pid (I am the author) OR
- - I can read the related document if res_model, res_id
- - Otherwise: raise
+ - author_id == pid, uid is the author, OR
+ - mail_notification (id, pid) exists, uid has been notified, OR
+ - uid have read access to the related document if model, res_id
+ - otherwise: raise
- create: if
- - I am in the document message_follower_ids OR
- - I can write on the related document if res_model, res_id OR
- - I create a private message (no model, no res_id)
- - Otherwise: raise
+ - no model, no res_id, I create a private message
+ - pid in message_follower_ids if model, res_id OR
+ - uid have write access on the related document if model, res_id, OR
+ - otherwise: raise
- write: if
- - I can write on the related document if res_model, res_id
+ - uid has write access on the related document if model, res_id
- Otherwise: raise
- unlink: if
- - I can write on the related document if res_model, res_id
+ - uid has write access on the related document if model, res_id
- Otherwise: raise
"""
if uid == SUPERUSER_ID:
@@ -428,54 +526,50 @@ class mail_message(osv.Model):
partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
# Read mail_message.ids to have their values
- model_record_ids = {}
message_values = dict.fromkeys(ids)
+ model_record_ids = {}
cr.execute('SELECT DISTINCT id, model, res_id, author_id FROM "%s" WHERE id = ANY (%%s)' % self._table, (ids,))
for id, rmod, rid, author_id in cr.fetchall():
message_values[id] = {'res_model': rmod, 'res_id': rid, 'author_id': author_id}
if rmod:
- model_record_ids.setdefault(rmod, set()).add(rid)
+ model_record_ids.setdefault(rmod, dict()).setdefault(rid, set()).add(id)
- # Read: Check for received notifications -> could become an ir.rule, but not till we do not have a many2one variable field
- if operation == 'read':
- not_obj = self.pool.get('mail.notification')
- not_ids = not_obj.search(cr, SUPERUSER_ID, [
- ('partner_id', '=', partner_id),
- ('message_id', 'in', ids),
- ], context=context)
- notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, SUPERUSER_ID, not_ids, context=context)]
- else:
- notified_ids = []
- # Read: Check messages you are author -> could become an ir.rule, but not till we do not have a many2one variable field
+ # Author condition, for read and create (private message) -> could become an ir.rule, but not till we do not have a many2one variable field
if operation == 'read':
author_ids = [mid for mid, message in message_values.iteritems()
if message.get('author_id') and message.get('author_id') == partner_id]
- # Create: Check messages you create that are private messages -> ir.rule ?
elif operation == 'create':
author_ids = [mid for mid, message in message_values.iteritems()
if not message.get('model') and not message.get('res_id')]
else:
author_ids = []
- # Create: Check message_follower_ids
- if operation == 'create':
- doc_follower_ids = []
- for model, mids in model_record_ids.items():
+ # Notification condition, for read (check for received notifications and create (in message_follower_ids)) -> could become an ir.rule, but not till we do not have a many2one variable field
+ if operation == 'read':
+ not_obj = self.pool.get('mail.notification')
+ not_ids = not_obj.search(cr, SUPERUSER_ID, [
+ ('partner_id', '=', partner_id),
+ ('message_id', 'in', ids),
+ ], context=context)
+ notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, SUPERUSER_ID, not_ids, context=context)]
+ elif operation == 'create':
+ notified_ids = []
+ for doc_model, doc_dict in model_record_ids.items():
fol_obj = self.pool.get('mail.followers')
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [
- ('res_model', '=', model),
- ('res_id', 'in', list(mids)),
+ ('res_model', '=', doc_model),
+ ('res_id', 'in', list(doc_dict.keys())),
('partner_id', '=', partner_id),
], context=context)
fol_mids = [follower.res_id for follower in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context)]
- doc_follower_ids += [mid for mid, message in message_values.iteritems()
- if message.get('res_model') == model and message.get('res_id') in fol_mids]
+ notified_ids += [mid for mid, message in message_values.iteritems()
+ if message.get('res_model') == doc_model and message.get('res_id') in fol_mids]
else:
- doc_follower_ids = []
+ notified_ids = []
# Calculate remaining ids, and related model/res_ids
model_record_ids = {}
- other_ids = set(ids).difference(set(notified_ids), set(author_ids), set(doc_follower_ids))
+ other_ids = set(ids).difference(set(author_ids), set(notified_ids))
for id in other_ids:
if message_values[id]['res_model']:
model_record_ids.setdefault(message_values[id]['res_model'], set()).add(message_values[id]['res_id'])
@@ -495,7 +589,7 @@ class mail_message(osv.Model):
if message.get('res_model') == model and message.get('res_id') in mids]
# Calculate remaining ids: if not void, raise an error
- other_ids = set(ids).difference(set(notified_ids), set(author_ids), set(doc_follower_ids), set(document_related_ids))
+ other_ids = other_ids - set(document_related_ids)
if not other_ids:
return
raise orm.except_orm(_('Access Denied'),
@@ -529,50 +623,123 @@ class mail_message(osv.Model):
self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context)
return super(mail_message, self).unlink(cr, uid, ids, context=context)
- def _notify_followers(self, cr, uid, newid, message, context=None):
- """ Add the related record followers to the destination partner_ids.
+ 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, headers=False)
+ return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
+
+ #------------------------------------------------------
+ # Messaging API
+ #------------------------------------------------------
+
+ # TDE note: this code is not used currently, will be improved in a future merge, when quoted context
+ # will be added to email send for notifications. Currently only WIP.
+ MAIL_TEMPLATE = """<div>
+ % if message:
+ ${display_message(message)}
+ % endif
+ % for ctx_msg in context_messages:
+ ${display_message(ctx_msg)}
+ % endfor
+ % if add_expandable:
+ ${display_expandable()}
+ % endif
+ ${display_message(header_message)}
+ </div>
+
+ <%def name="display_message(message)">
+ <div>
+ Subject: ${message.subject}<br />
+ Body: ${message.body}
+ </div>
+ </%def>
+
+ <%def name="display_expandable()">
+ <div>This is an expandable.</div>
+ </%def>
+ """
+
+ def message_quote_context(self, cr, uid, id, context=None, limit=3, add_original=False):
"""
- partners_to_notify = set([])
- # message has no subtype_id: pure log message -> no partners, no one notified
- if not message.subtype_id:
- message.write({'partner_ids': [5]})
- return True
- # all partner_ids of the mail.message have to be notified
- if message.partner_ids:
- partners_to_notify |= set(partner.id for partner in message.partner_ids)
- # all followers of the mail.message document have to be added as partners and notified
- if message.model and message.res_id:
- fol_obj = self.pool.get("mail.followers")
- fol_ids = fol_obj.search(cr, uid, [('res_model', '=', message.model), ('res_id', '=', message.res_id), ('subtype_ids', 'in', message.subtype_id.id)], context=context)
- fol_objs = fol_obj.browse(cr, uid, fol_ids, context=context)
- extra_notified = set(fol.partner_id.id for fol in fol_objs)
- missing_notified = extra_notified - partners_to_notify
- if missing_notified:
- self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, p_id) for p_id in missing_notified]}, context=context)
+ 1. message.parent_id = False: new thread, no quote_context
+ 2. get the lasts messages in the thread before message
+ 3. get the message header
+ 4. add an expandable between them
+
+ :param dict quote_context: options for quoting
+ :return string: html quote
+ """
+ add_expandable = False
+
+ message = self.browse(cr, uid, id, context=context)
+ if not message.parent_id:
+ return ''
+ context_ids = self.search(cr, uid, [
+ ('parent_id', '=', message.parent_id.id),
+ ('id', '<', message.id),
+ ], limit=limit, context=context)
+
+ if len(context_ids) >= limit:
+ add_expandable = True
+ context_ids = context_ids[0:-1]
+
+ context_ids.append(message.parent_id.id)
+ context_messages = self.browse(cr, uid, context_ids, context=context)
+ header_message = context_messages.pop()
+
+ try:
+ if not add_original:
+ message = False
+ result = MakoTemplate(self.MAIL_TEMPLATE).render_unicode(message=message,
+ context_messages=context_messages,
+ header_message=header_message,
+ add_expandable=add_expandable,
+ # context kw would clash with mako internals
+ ctx=context,
+ format_exceptions=True)
+ result = result.strip()
+ return result
+ except Exception:
+ _logger.exception("failed to render mako template for quoting message")
+ return ''
+ return result
def _notify(self, cr, uid, newid, context=None):
""" Add the related record followers to the destination partner_ids if is not a private message.
Call mail_notification.notify to manage the email sending
"""
- message = self.browse(cr, uid, newid, context=context)
- if message.model and message.res_id:
- self._notify_followers(cr, uid, newid, message, context=context)
+ message = self.read(cr, uid, newid, ['model', 'res_id', 'author_id', 'subtype_id', 'partner_ids'], context=context)
- # add myself if I wrote on my wall, otherwise remove myself author
- if ((message.model == "res.partner" and message.res_id == message.author_id.id)):
- self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, message.author_id.id)]}, context=context)
- else:
- self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(3, message.author_id.id)]}, context=context)
+ partners_to_notify = set([])
+ # message has no subtype_id: pure log message -> no partners, no one notified
+ if not message.get('subtype_id'):
+ return True
+ # all partner_ids of the mail.message have to be notified
+ if message.get('partner_ids'):
+ partners_to_notify |= set(message.get('partner_ids'))
+ # all followers of the mail.message document have to be added as partners and notified
+ if message.get('model') and message.get('res_id'):
+ fol_obj = self.pool.get("mail.followers")
+ fol_ids = fol_obj.search(cr, uid, [
+ ('res_model', '=', message.get('model')),
+ ('res_id', '=', message.get('res_id')),
+ ('subtype_ids', 'in', message.get('subtype_id')[0])
+ ], context=context)
+ fol_objs = fol_obj.read(cr, uid, fol_ids, ['partner_id'], context=context)
+ partners_to_notify |= set(fol['partner_id'][0] for fol in fol_objs)
+ # when writing to a wall
+ if message.get('author_id') and message.get('model') == "res.partner" and message.get('res_id') == message.get('author_id')[0]:
+ partners_to_notify |= set([message.get('author_id')[0]])
+ elif message.get('author_id'):
+ partners_to_notify = partners_to_notify - set([message.get('author_id')[0]])
+
+ if partners_to_notify:
+ self.write(cr, SUPERUSER_ID, [newid], {'notified_partner_ids': [(4, p_id) for p_id in partners_to_notify]}, context=context)
self.pool.get('mail.notification')._notify(cr, uid, newid, context=context)
- 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, headers=False)
- return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
-
#------------------------------------------------------
# Tools
#------------------------------------------------------