##############################################################################
import logging
+import openerp
import tools
from email.header import decode_header
+from openerp import SUPERUSER_ID
from operator import itemgetter
-from osv import osv, fields
+from osv import osv, orm, fields
+from tools.translate import _
_logger = logging.getLogger(__name__)
text = decode_header(text.replace('\r', ''))
return ''.join([tools.ustr(x[0], x[1]) for x in text])
+
class mail_message(osv.Model):
""" Messages model: system notification (replacing res.log notifications),
comments (OpenChatter discussion) and incoming emails. """
def _get_record_name(self, cr, uid, ids, name, arg, context=None):
""" Return the related document name, using get_name. """
- result = dict.fromkeys(ids, '')
- for message in self.browse(cr, uid, ids, context=context):
+ result = dict.fromkeys(ids, False)
+ for message in self.browse(cr, 1, ids, context=context):
if not message.model or not message.res_id:
continue
- result[message.id] = self._shorten_name(self.pool.get(message.model).name_get(cr, uid, [message.res_id], context=context)[0][1])
+ 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
return result
def _get_unread(self, cr, uid, ids, name, arg, context=None):
""" Compute if the message is unread by the current user. """
res = dict((id, {'unread': False}) for id in ids)
- partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
+ partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
notif_obj = self.pool.get('mail.notification')
notif_ids = notif_obj.search(cr, uid, [
('partner_id', 'in', [partner_id]),
read_cond = '(read = false or read is null)'
else:
read_cond = 'read = true'
- partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
+ partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
cr.execute("SELECT message_id FROM mail_notification "\
"WHERE partner_id = %%s AND %s" % read_cond,
(partner_id,))
'unread': fields.function(_get_unread, fnct_search=_search_unread,
type='boolean', string='Unread',
help='Functional field to search for unread messages linked to uid'),
- 'vote_user_ids': fields.many2many('res.users', 'mail_vote', 'message_id', 'user_id', 'Votes'),
-
+ 'subtype_id': fields.many2one('mail.message.subtype', 'Subtype'),
+ 'vote_user_ids': fields.many2many('res.users', 'mail_vote', 'message_id', 'user_id', string='Votes',
+ help='Users that voted for this message'),
+ 'is_private': fields.boolean('Private message'),
}
def _needaction_domain_get(self, cr, uid, context=None):
return []
def _get_default_author(self, cr, uid, context=None):
- return self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
+ # remove context to avoid possible hack in browse with superadmin using context keys that could trigger a specific behavior
+ return self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=None).partner_id.id
_defaults = {
'type': 'email',
'date': lambda *a: fields.datetime.now(),
'author_id': lambda self, cr, uid, ctx={}: self._get_default_author(cr, uid, ctx),
'body': '',
+ 'is_private': True,
}
-
- #---------------------------------------------------
- #Mail Vote system (Like or Unlike comments
- #-----------------------------------------------------
+
+ #------------------------------------------------------
+ # Vote/Like
+ #------------------------------------------------------
+
def vote_toggle(self, cr, uid, ids, user_ids=None, context=None):
- '''
- Toggles when Comment is liked or unlike.
- create vote entries if current user like comment..
- '''
- if not user_ids: user_ids = [uid]
- vote_pool = self.pool.get('mail.vote')
-
- for message in self.browse(cr, uid, ids, context):
- voters_ids = [x.id for x in message.vote_user_ids if x.id == uid]
- if not voters_ids:
- self.write(cr, uid, ids, {'vote_user_ids': [(4, user_id) for user_id in user_ids]}, context=context)
- else:
- self.write(cr, uid, ids, {'vote_user_ids': [(3, uid)]}, context=context)
- return True
+ ''' Toggles voting. Done as SUPERUSER_ID because of write access on
+ mail.message not always granted. '''
+ if not user_ids:
+ user_ids = [uid]
+ for message in self.read(cr, uid, ids, ['vote_user_ids'], context=context):
+ for user_id in user_ids:
+ has_voted = user_id in message.get('vote_user_ids')
+ if not has_voted:
+ self.write(cr, SUPERUSER_ID, message.get('id'), {'vote_user_ids': [(4, user_id)]}, context=context)
+ else:
+ self.write(cr, SUPERUSER_ID, message.get('id'), {'vote_user_ids': [(3, user_id)]}, context=context)
+ return not(has_voted) or False
#------------------------------------------------------
# Message loading for web interface
#------------------------------------------------------
- def _message_dict_get(self, cr, uid, msg, context=None):
- """ Return a dict representation of the message browse record. """
- vote_pool = self.pool.get('mail.vote')
- has_voted = False;
- vote_ids = []
- attachment_ids = self.pool.get('ir.attachment').name_get(cr, uid, [x.id for x in msg.attachment_ids], context=context)
- vote_ids = vote_pool.name_get(cr, uid, [x.id for x in msg.vote_user_ids], context=context)
- if msg.vote_user_ids:
- for user_id in msg.vote_user_ids:
- if (uid == user_id.id):
- has_voted = True;
- author_id = self.pool.get('res.partner').name_get(cr, uid, [msg.author_id.id], context=context)[0]
- author_user_id = self.pool.get('res.users').name_get(cr, uid, [msg.author_id.user_ids[0].id], context=context)[0]
- partner_ids = self.pool.get('res.partner').name_get(cr, uid, [x.id for x in msg.partner_ids], context=context)
+ def _message_get_dict(self, cr, uid, message, context=None):
+ """ Return a dict representation of the message.
+
+ :param dict message: read result of a mail.message
+ """
+ if uid in message['vote_user_ids']:
+ has_voted = True
+ else:
+ has_voted = 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 = []
+
+ try:
+ partner_ids = self.pool.get('res.partner').name_get(cr, uid, [message['partner_ids']], context=context)
+ except (orm.except_orm, osv.except_osv):
+ partner_ids = []
+
return {
- 'id': msg.id,
- 'type': msg.type,
+ 'id': message['id'],
+ 'type': message['type'],
'attachment_ids': attachment_ids,
- 'body': msg.body,
- 'model': msg.model,
- 'res_id': msg.res_id,
- 'record_name': msg.record_name,
- 'subject': msg.subject,
- 'date': msg.date,
- 'author_id': author_id,
- 'author_user_id': author_user_id,
+ 'body': message['body'],
+ 'model': message['model'],
+ 'res_id': message['res_id'],
+ '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,
'partner_ids': partner_ids,
- 'child_ids': [],
- 'vote_user_ids': vote_ids,
- 'has_voted': has_voted
+ 'parent_id': message['parent_id'] and message['parent_id'][0] or False,
+ # 'vote_user_ids': vote_ids,
+ 'has_voted': has_voted,
+ # 'unread': msg.unread and msg.unread['unread'] or False
}
- def message_read_tree_flatten(self, cr, uid, messages, current_level, level, context=None):
- """ Given a tree with several roots of following structure :
- [ {'id': 1, 'child_ids': [
- {'id': 11, 'child_ids': [...] },],
- {...} ]
- Flatten it to have a maximum number of levels, 0 being flat and
- sort messages in a level according to a key of the messages.
- Perform the flattening at leafs if above the maximum depth, then get
- back in the tree.
- :param context: ``sort_key``: key for sorting (id by default)
- :param context: ``sort_reverse``: reverser order for sorting (True by default)
- """
- def _flatten(msg_dict):
- """ from {'id': x, 'child_ids': [{child1}, {child2}]}
- get [{'id': x, 'child_ids': []}, {child1}, {child2}]
- """
- child_ids = msg_dict.pop('child_ids', [])
- msg_dict['child_ids'] = []
- return [msg_dict] + child_ids
- # return sorted([msg_dict] + child_ids, key=itemgetter('id'), reverse=True)
- context = context or {}
- # Depth-first flattening
- for message in messages:
- if message.get('type') == 'expandable':
- continue
- message['child_ids'] = self.message_read_tree_flatten(cr, uid, message['child_ids'], current_level + 1, level, context=context)
- # Flatten if above maximum depth
- if current_level < level:
- return_list = messages
- else:
- return_list = []
- for message in messages:
- for flat_message in _flatten(message):
- return_list.append(flat_message)
- return sorted(return_list, key=itemgetter(context.get('sort_key', 'id')), reverse=context.get('sort_reverse', True))
-
- def message_read(self, cr, uid, ids=False, domain=[], thread_level=0, limit=None, context=None):
- """ If IDs are provided, fetch these records. Otherwise use the domain
- to fetch the matching records.
- After having fetched the records provided by IDs, it will fetch the
- parents to have well-formed threads.
- :return list: list of trees of messages
+ def _message_read_expandable(self, cr, uid, tree, result, message_loaded, domain, context, parent_id, limit):
+ """ Create the expandable message for all parent message read
+ this function is used by message_read
+ TDE note: place use default values for args, and comment your vars !!
+
+ :param dict tree: tree of message ids
"""
- limit = limit or self._message_read_limit
- context = context or {}
- if not ids:
- ids = self.search(cr, uid, domain, context=context, limit=limit)
- messages = self.browse(cr, uid, ids, context=context)
-
- result = []
- tree = {} # key: ID, value: record
- for msg in messages:
- if len(result) < (limit - 1):
- record = self._message_dict_get(cr, uid, msg, context=context)
- if thread_level and msg.parent_id:
- while msg.parent_id:
- if msg.parent_id.id in tree:
- record_parent = tree[msg.parent_id.id]
- else:
- record_parent = self._message_dict_get(cr, uid, msg.parent_id, context=context)
- if msg.parent_id.parent_id:
- tree[msg.parent_id.id] = record_parent
- if record['id'] not in [x['id'] for x in record_parent['child_ids']]:
- record_parent['child_ids'].append(record)
- record = record_parent
- msg = msg.parent_id
- if msg.id not in tree:
- result.append(record)
- tree[msg.id] = record
- else:
+ tree_not = []
+ # expandable for not show message
+ for msg_id in tree:
+ # get all childs
+ not_loaded_ids = self.search(cr, SUPERUSER_ID, [
+ ('parent_id', '=', msg_id),
+ ('id', 'not in', message_loaded)
+ ], context=context, limit=1000)
+ # group childs not read
+ id_min = None
+ id_max = None
+ nb = 0
+ for not_loaded_id in not_loaded_ids:
+ if not_loaded_id not in tree:
+ nb += 1
+ if id_min == None or id_min > not_loaded_id:
+ id_min = not_loaded_id
+ if id_max == None or id_max < not_loaded_id:
+ id_max = not_loaded_id
+ tree_not.append(not_loaded_id)
+ else:
+ if nb > 0:
+ result.append({
+ 'domain': [('id', '>=', id_min), ('id', '<=', id_max), ('parent_id', '=', msg_id)],
+ 'nb_messages': nb,
+ 'type': 'expandable',
+ 'parent_id': msg_id,
+ 'id': id_min,
+ })
+ id_min = None
+ id_max = None
+ nb = 0
+ if nb > 0:
result.append({
+ 'domain': [('id', '>=', id_min), ('id', '<=', id_max), ('parent_id', '=', msg_id)],
+ 'nb_messages': nb,
'type': 'expandable',
- 'domain': [('id', '<=', msg.id)] + domain,
- 'context': context,
- 'thread_level': thread_level, # should be improve accodting to level of records
- 'id': -1,
+ 'parent_id': msg_id,
+ 'id': id_min
})
- break
- # Flatten the result
- if thread_level > 0:
- result = self.message_read_tree_flatten(cr, uid, result, 0, thread_level, context=context)
+ # expandable for limit max
+ ids = self.search(cr, SUPERUSER_ID, domain + [('id', 'not in', message_loaded + tree + tree_not)], context=context, limit=1)
+ if len(ids) > 0:
+ result.append({
+ 'domain': domain,
+ 'nb_messages': 0,
+ 'type': 'expandable',
+ 'parent_id': parent_id,
+ 'id': -1
+ })
+
+ result = sorted(result, key=lambda k: k['id'])
+
return result
+ _message_read_fields = ['id', 'parent_id', 'model', 'res_id', 'body', 'subject', 'date', 'type', 'vote_user_ids', 'attachment_ids', 'author_id', 'partner_ids', 'record_name']
+
+ def _get_parent(self, cr, uid, message, context=None):
+ """ Tools method that try to get the parent of a mail.message. If
+ no parent, or if uid has no access right on the parent, False
+ is returned.
+
+ :param dict message: read result of a mail.message
+ """
+ if not message['parent_id']:
+ return False
+ parent_id = message['parent_id'][0]
+ try:
+ return self.read(cr, uid, parent_id, self._message_read_fields, context=context)
+ except (orm.except_orm, osv.except_osv):
+ return False
+
+ def message_read(self, cr, uid, ids=False, domain=[], 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,
+ fetch these records. Otherwise use the domain to fetch messages.
+ After having fetch messages, their parents will be added to obtain
+ well formed threads.
+
+ :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
+ """
+ # don't read the message display by .js, in context message_loaded list
+ # TDE note: use an argument, do not use context
+ if context is None:
+ context = {}
+ if context.get('message_loaded'):
+ domain += [('id', 'not in', context.get('message_loaded'))]
+ limit = limit or self._message_read_limit
+ # tree = []
+ # result = []
+ record = None
+
+ id_tree = []
+ message_list = []
+
+ # select ids
+ # TDE note: should not receive [None] !!
+ if ids and ids != [None]:
+ for message in self.read(cr, uid, ids, self._message_read_fields, context=context):
+ message_list.append(self._message_dict_get(cr, uid, message, context=context))
+ return message_list
+
+ # key: ID, value: record
+ ids = self.search(cr, SUPERUSER_ID, domain, context=context, limit=limit)
+
+ for message in self.read(cr, uid, ids, self._message_read_fields, context=context):
+ # if not in record and not in message_loded list
+ if message['id'] not in id_tree and message['id'] not in context.get('message_loaded', []):
+ record = self._message_get_dict(cr, uid, message, context=context)
+ id_tree.append(message['id'])
+ message_list.append(record)
+
+ parent = self._get_parent(cr, uid, message, context=context)
+ while parent and parent['id'] != parent_id:
+ parent = message.parent_id.id
+ if parent['id'] not in id_tree:
+ message = parent
+ id_tree.append(message['id'])
+ # if not in record and not in message_loded list
+ if message['id'] not in context.get('message_loaded', []):
+ record = self._message_get_dict(cr, uid, message, context=context)
+ message_list.append(record)
+
+ message_list = sorted(message_list, key=lambda k: k['id'])
+
+ message_list = self._message_read_expandable(cr, uid, id_tree, message_list, context.get('message_loaded', []), domain, context, parent_id, limit)
+
+ return message_list
+
+ # TDE Note: do we need this ?
+ # def user_free_attachment(self, cr, uid, context=None):
+ # attachment_list = []
+
+ # attachment = self.pool.get('ir.attachment')
+ # attachment_ids = attachment.search(cr, uid, [('res_model','=',''),('create_uid','=',uid)])
+ # if len(attachment_ids):
+ # attachment_list = [{'id': attach.id, 'name': attach.name, 'date': attach.create_date} for attach in attachment.browse(cr, uid, attachment_ids, context=context)]
+
+ # return attachment_list
+
#------------------------------------------------------
# Email api
#------------------------------------------------------
cr.execute("""CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)""")
def check_access_rule(self, cr, uid, ids, operation, context=None):
- """ mail.message access rule check
- - message received (a notification exists) -> ok
- - check rules of related document if exists
- - fallback on normal mail.message check """
+ """ 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
+ - create: if
+ - I am in the document message_follower_ids OR
+ - I can write on the related document if res_model, res_id
+ - Otherwise: raise
+ - write: if
+ - I can write on the related document if res_model, res_id
+ - Otherwise: raise
+ - unlink: if
+ - I can write on the related document if res_model, res_id
+ - Otherwise: raise
+ """
+ if uid == SUPERUSER_ID:
+ return
if isinstance(ids, (int, long)):
ids = [ids]
+ partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
- # check messages for which you have a notification
- partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
- not_obj = self.pool.get('mail.notification')
- not_ids = not_obj.search(cr, uid, [
- ('partner_id', '=', partner_id),
- ('message_id', 'in', ids),
- ], context=context)
- notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, uid, not_ids, context=context)
- if notification.message_id.id in ids]
+ # Read mail_message.ids to have their values
+ model_record_ids = {}
+ message_values = dict.fromkeys(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)
+
+ # 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
+ 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]
+ else:
+ author_ids = []
+
+ # Create: Check message_follower_ids
+ if operation == 'create':
+ doc_follower_ids = []
+ for model, mids 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)),
+ ('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]
+ else:
+ doc_follower_ids = []
- # check messages linked to an existing document
+ # Calculate remaining ids, and related model/res_ids
model_record_ids = {}
- document_ids = []
- cr.execute('SELECT DISTINCT id, model, res_id FROM mail_message WHERE id = ANY (%s)', (ids,))
- for id, rmod, rid in cr.fetchall():
- if not (rmod and rid):
- continue
- document_ids.append(id)
- model_record_ids.setdefault(rmod, set()).add(rid)
+ other_ids = set(ids).difference(set(notified_ids), set(author_ids), set(doc_follower_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'])
+
+ # CRUD: Access rights related to the document
+ document_related_ids = []
for model, mids in model_record_ids.items():
model_obj = self.pool.get(model)
mids = model_obj.exists(cr, uid, mids)
- model_obj.check_access_rights(cr, uid, operation)
- model_obj.check_access_rule(cr, uid, mids, operation, context=context)
-
- # fall back on classic operation for other ids
- other_ids = set(ids).difference(set(notified_ids), set(document_ids))
- super(mail_message, self).check_access_rule(cr, uid, other_ids, operation, context=None)
+ if operation in ['create', 'write', 'unlink']:
+ model_obj.check_access_rights(cr, uid, 'write')
+ model_obj.check_access_rule(cr, uid, mids, 'write', context=context)
+ else:
+ model_obj.check_access_rights(cr, uid, operation)
+ model_obj.check_access_rule(cr, uid, mids, operation, context=context)
+ document_related_ids += [mid for mid, message in message_values.iteritems()
+ 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))
+ if not other_ids:
+ return
+ raise orm.except_orm(_('Access Denied'),
+ _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \
+ (self._description, operation))
def create(self, cr, uid, values, context=None):
if not values.get('message_id') and values.get('res_id') and values.get('model'):
- values['message_id'] = tools.generate_tracking_message_id('%(model)s-%(res_id)s' % values)
+ values['message_id'] = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values)
newid = super(mail_message, self).create(cr, uid, values, context)
- self.notify(cr, uid, newid, context=context)
+ self._notify(cr, SUPERUSER_ID, newid, context=context)
return newid
+ def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
+ """ Override to explicitely call check_access_rule, that is not called
+ by the ORM. It instead directly fetches ir.rules and apply them. """
+ res = super(mail_message, self).read(cr, uid, ids, fields=fields, context=context, load=load)
+ self.check_access_rule(cr, uid, ids, 'read', context=context)
+ return res
+
def unlink(self, cr, uid, ids, context=None):
# cascade-delete attachments that are directly attached to the message (should only happen
- # for mail.messages that act as parent for a standalone mail.mail record.
+ # for mail.messages that act as parent for a standalone mail.mail record).
attachments_to_delete = []
- for mail in self.browse(cr, uid, ids, context=context):
- for attach in mail.attachment_ids:
- if attach.res_model == 'mail.message' and attach.res_id == mail.id:
+ for message in self.browse(cr, uid, ids, context=context):
+ for attach in message.attachment_ids:
+ if attach.res_model == self._name and attach.res_id == message.id:
attachments_to_delete.append(attach.id)
if attachments_to_delete:
self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context)
- return super(mail_message,self).unlink(cr, uid, ids, context=context)
+ return super(mail_message, self).unlink(cr, uid, ids, context=context)
- def notify(self, cr, uid, newid, context=None):
+ def _notify_followers(self, cr, uid, newid, message, context=None):
""" Add the related record followers to the destination partner_ids.
- Call mail_notification.notify to manage the email sending
"""
- message = self.browse(cr, uid, newid, context=context)
partners_to_notify = set([])
- # add all partner_ids of the message
+ # 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)
- # add all followers and set add them in partner_ids
+ # all followers of the mail.message document have to be added as partners and notified
if message.model and message.res_id:
- record = self.pool.get(message.model).browse(cr, uid, message.res_id, context=context)
- extra_notified = set(partner.id for partner in record.message_follower_ids)
+ 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
+ missing_notified = missing_notified
if missing_notified:
- message.write({'partner_ids': [(4, p_id) for p_id in missing_notified]})
+ self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, p_id) for p_id in missing_notified]}, context=context)
partners_to_notify |= extra_notified
- self.pool.get('mail.notification').notify(cr, uid, list(partners_to_notify), newid, context=context)
+
+ 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 and (message.is_private!=False and message.is_private!=None):
+ self._notify_followers(cr, uid, newid, message, context=context)
+
+ # add myself if I wrote on my wall,
+ # unless 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)
+
+ 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"""
default = {}
default.update(message_id=False, headers=False)
return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
+
+ #------------------------------------------------------
+ # Tools
+ #------------------------------------------------------
+
+ def check_partners_email(self, cr, uid, partner_ids, context=None):
+ """ Verify that selected partner_ids have an email_address defined.
+ Otherwise throw a warning. """
+ partner_wo_email_lst = []
+ for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
+ if not partner.email:
+ partner_wo_email_lst.append(partner)
+ if not partner_wo_email_lst:
+ return {}
+ warning_msg = _('The following partners chosen as recipients for the email have no email address linked :')
+ for partner in partner_wo_email_lst:
+ warning_msg += '\n- %s' % (partner.name)
+ return {'warning': {
+ 'title': _('Partners email addresses not found'),
+ 'message': warning_msg,
+ }
+ }