X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;ds=sidebyside;f=addons%2Fmail%2Fmail_message.py;h=a2d38287062b0a6bad6dfb1ea48523341c6c4b8f;hb=1d9bdb43bf2adc7b6b55b616d65f0a5db44b67dc;hp=d545f3782b4c534a2d808db540d3da0536d0561b;hpb=6fd2f14e3b368359aa630d730fdb37829e3fa9ef;p=odoo%2Fodoo.git diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index d545f37..a2d3828 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -20,13 +20,17 @@ ############################################################################## import logging +import re + from openerp import tools from email.header import decode_header +from email.utils import formataddr from openerp import SUPERUSER_ID from openerp.osv import osv, orm, fields from openerp.tools import html_email_clean from openerp.tools.translate import _ +from HTMLParser import HTMLParser _logger = logging.getLogger(__name__) @@ -43,6 +47,19 @@ def decode(text): text = decode_header(text.replace('\r', '')) return ''.join([tools.ustr(x[0], x[1]) for x in text]) +class MLStripper(HTMLParser): + def __init__(self): + self.reset() + self.fed = [] + def handle_data(self, d): + self.fed.append(d) + def get_data(self): + return ''.join(self.fed) + +def strip_tags(html): + s = MLStripper() + s.feed(html) + return s.get_data() class mail_message(osv.Model): """ Messages model: system notification (replacing res.log notifications), @@ -61,7 +78,8 @@ class mail_message(osv.Model): def default_get(self, cr, uid, fields, context=None): # protection for `default_type` values leaking from menu action context (e.g. for invoices) - if context and context.get('default_type') and context.get('default_type') not in self._columns['type'].selection: + if context and context.get('default_type') and context.get('default_type') not in [ + val[0] for val in self._columns['type'].selection]: context = dict(context, default_type=None) return super(mail_message, self).default_get(cr, uid, fields, context=context) @@ -76,15 +94,15 @@ class mail_message(osv.Model): # 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.get('model') or not message.get('res_id') or not self.pool.get(message['model']): + if not message.get('model') or not message.get('res_id') or message['model'] not in self.pool: continue - result[message['id']] = self._shorten_name(self.pool.get(message['model']).name_get(cr, SUPERUSER_ID, [message['res_id']], context=context)[0][1]) + result[message['id']] = self.pool[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): """ Compute if the message is unread by the current user. """ res = dict((id, False) for id in ids) - partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] + partner_id = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id notif_obj = self.pool.get('mail.notification') notif_ids = notif_obj.search(cr, uid, [ ('partner_id', 'in', [partner_id]), @@ -103,7 +121,7 @@ class mail_message(osv.Model): def _get_starred(self, cr, uid, ids, name, arg, context=None): """ Compute if the message is unread by the current user. """ res = dict((id, False) for id in ids) - partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] + partner_id = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id notif_obj = self.pool.get('mail.notification') notif_ids = notif_obj.search(cr, uid, [ ('partner_id', 'in', [partner_id]), @@ -125,7 +143,7 @@ class mail_message(osv.Model): ids = [ids] res = [] for message in self.browse(cr, uid, ids, context=context): - name = '%s: %s' % (message.subject or '', message.body or '') + name = '%s: %s' % (message.subject or '', strip_tags(message.body or '') or '') res.append((message.id, self._shorten_name(name.lstrip(' :')))) return res @@ -139,9 +157,12 @@ class mail_message(osv.Model): "message, comment for other messages such as user replies"), 'email_from': fields.char('From', help="Email address of the sender. This field is set when no matching partner is found for incoming emails."), + 'reply_to': fields.char('Reply-To', + help='Reply email address. Setting the reply_to bypasses the automatic thread creation.'), 'author_id': fields.many2one('res.partner', 'Author', select=1, ondelete='set null', help="Author of the message. If not set, email_from may hold an email address that did not match any partner."), + 'author_avatar': fields.related('author_id', 'image_small', type="binary", string="Author's Avatar"), 'partner_ids': fields.many2many('res.partner', string='Recipients'), 'notified_partner_ids': fields.many2many('res.partner', 'mail_notification', 'message_id', 'partner_id', 'Notified partners', @@ -174,19 +195,29 @@ class mail_message(osv.Model): 'vote_user_ids': fields.many2many('res.users', 'mail_vote', 'message_id', 'user_id', string='Votes', help='Users that voted for this message'), + 'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1), } def _needaction_domain_get(self, cr, uid, context=None): return [('to_read', '=', True)] + def _get_default_from(self, cr, uid, context=None): + this = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context) + if this.alias_name and this.alias_domain: + return formataddr((this.name, '%s@%s' % (this.alias_name, this.alias_domain))) + elif this.email: + return formataddr((this.name, this.email)) + raise osv.except_osv(_('Invalid Action!'), _("Unable to send email, please configure the sender's email address or alias.")) + def _get_default_author(self, cr, uid, context=None): - return self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] + return self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context).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), + 'date': fields.datetime.now, + 'author_id': lambda self, cr, uid, ctx=None: self._get_default_author(cr, uid, ctx), 'body': '', + 'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx), } #------------------------------------------------------ @@ -224,58 +255,70 @@ class mail_message(osv.Model): # Notification API #------------------------------------------------------ - def set_message_read(self, cr, uid, msg_ids, read, context=None): + def set_message_read(self, cr, uid, msg_ids, read, create_missing=True, context=None): """ Set messages as (un)read. Technically, the notifications related to uid are set to (un)read. If for some msg_ids there are missing notifications (i.e. due to load more or thread parent fetching), they are created. :param bool read: set notification as (un)read + :param bool create_missing: create notifications for missing entries + (i.e. when acting on displayed messages not notified) + + :return number of message mark as read """ notification_obj = self.pool.get('mail.notification') - user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] - notif_ids = notification_obj.search(cr, uid, [ - ('partner_id', '=', user_pid), - ('message_id', 'in', msg_ids) - ], context=context) + user_pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id + domain = [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)] + if not create_missing: + domain += [('read', '=', not read)] + notif_ids = notification_obj.search(cr, uid, domain, context=context) # all message have notifications: already set them as (un)read - if len(notif_ids) == len(msg_ids): - return notification_obj.write(cr, uid, notif_ids, {'read': read}, context=context) + if len(notif_ids) == len(msg_ids) or not create_missing: + notification_obj.write(cr, uid, notif_ids, {'read': read}, context=context) + return len(notif_ids) # some messages do not have notifications: find which one, create notification, update read status notified_msg_ids = [notification.message_id.id for notification in notification_obj.browse(cr, uid, notif_ids, context=context)] to_create_msg_ids = list(set(msg_ids) - set(notified_msg_ids)) for msg_id in to_create_msg_ids: notification_obj.create(cr, uid, {'partner_id': user_pid, 'read': read, 'message_id': msg_id}, context=context) - return notification_obj.write(cr, uid, notif_ids, {'read': read}, context=context) + notification_obj.write(cr, uid, notif_ids, {'read': read}, context=context) + return len(notif_ids) - def set_message_starred(self, cr, uid, msg_ids, starred, context=None): + def set_message_starred(self, cr, uid, msg_ids, starred, create_missing=True, context=None): """ Set messages as (un)starred. Technically, the notifications related - to uid are set to (un)starred. If for some msg_ids there are missing - notifications (i.e. due to load more or thread parent fetching), - they are created. + to uid are set to (un)starred. :param bool starred: set notification as (un)starred + :param bool create_missing: create notifications for missing entries + (i.e. when acting on displayed messages not notified) """ notification_obj = self.pool.get('mail.notification') - user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] - notif_ids = notification_obj.search(cr, uid, [ - ('partner_id', '=', user_pid), - ('message_id', 'in', msg_ids) - ], context=context) + user_pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id + domain = [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)] + if not create_missing: + domain += [('starred', '=', not starred)] + values = { + 'starred': starred + } + if starred: + values['read'] = False + + notif_ids = notification_obj.search(cr, uid, domain, context=context) # all message have notifications: already set them as (un)starred - if len(notif_ids) == len(msg_ids): - notification_obj.write(cr, uid, notif_ids, {'starred': starred}, context=context) + if len(notif_ids) == len(msg_ids) or not create_missing: + notification_obj.write(cr, uid, notif_ids, values, context=context) return starred # some messages do not have notifications: find which one, create notification, update starred status notified_msg_ids = [notification.message_id.id for notification in notification_obj.browse(cr, uid, notif_ids, context=context)] to_create_msg_ids = list(set(msg_ids) - set(notified_msg_ids)) for msg_id in to_create_msg_ids: - notification_obj.create(cr, uid, {'partner_id': user_pid, 'starred': starred, 'message_id': msg_id}, context=context) - notification_obj.write(cr, uid, notif_ids, {'starred': starred}, context=context) + notification_obj.create(cr, uid, dict(values, partner_id=user_pid, message_id=msg_id), context=context) + notification_obj.write(cr, uid, notif_ids, values, context=context) return starred #------------------------------------------------------ @@ -291,7 +334,7 @@ class mail_message(osv.Model): """ res_partner_obj = self.pool.get('res.partner') ir_attachment_obj = self.pool.get('ir.attachment') - pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0] + pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id # 1. Aggregate partners (author_id and partner_ids) and attachments partner_ids = set() @@ -299,7 +342,9 @@ class mail_message(osv.Model): for key, message in message_tree.iteritems(): if message.author_id: partner_ids |= set([message.author_id.id]) - if message.partner_ids: + if message.subtype_id and message.notified_partner_ids: # take notified people of message with a subtype + partner_ids |= set([partner.id for partner in message.notified_partner_ids]) + elif not message.subtype_id and message.partner_ids: # take specified people of message without a subtype (log) partner_ids |= set([partner.id for partner in message.partner_ids]) if message.attachment_ids: attachment_ids |= set([attachment.id for attachment in message.attachment_ids]) @@ -308,8 +353,13 @@ class mail_message(osv.Model): partner_tree = dict((partner[0], partner) for partner in partners) # 2. Attachments as SUPERUSER, because could receive msg and attachments for doc uid cannot see - attachments = ir_attachment_obj.read(cr, SUPERUSER_ID, list(attachment_ids), ['id', 'datas_fname'], context=context) - attachments_tree = dict((attachment['id'], {'id': attachment['id'], 'filename': attachment['datas_fname']}) for attachment in attachments) + attachments = ir_attachment_obj.read(cr, SUPERUSER_ID, list(attachment_ids), ['id', 'datas_fname', 'name', 'file_type_icon'], context=context) + attachments_tree = dict((attachment['id'], { + 'id': attachment['id'], + 'filename': attachment['datas_fname'], + 'name': attachment['name'], + 'file_type_icon': attachment['file_type_icon'], + }) for attachment in attachments) # 3. Update message dictionaries for message_dict in messages: @@ -320,9 +370,12 @@ class mail_message(osv.Model): else: author = (0, message.email_from) partner_ids = [] - for partner in message.partner_ids: - if partner.id in partner_tree: - partner_ids.append(partner_tree[partner.id]) + if message.subtype_id: + partner_ids = [partner_tree[partner.id] for partner in message.notified_partner_ids + if partner.id in partner_tree] + else: + partner_ids = [partner_tree[partner.id] for partner in message.partner_ids + if partner.id in partner_tree] attachment_ids = [] for attachment in message.attachment_ids: if attachment.id in attachments_tree: @@ -332,6 +385,7 @@ class mail_message(osv.Model): 'author_id': author, 'partner_ids': partner_ids, 'attachment_ids': attachment_ids, + 'user_pid': pid }) return True @@ -350,9 +404,22 @@ class mail_message(osv.Model): vote_nb = len(message.vote_user_ids) has_voted = uid in [user.id for user in message.vote_user_ids] + try: + if parent_id: + max_length = 300 + else: + max_length = 100 + body_short = html_email_clean(message.body, remove=False, shorten=True, max_length=max_length) + + except Exception: + body_short = '

Encoding Error :
Unable to convert this message (id: %s).

' % message.id + _logger.exception(Exception) + return {'id': message.id, 'type': message.type, - 'body': html_email_clean(message.body or ''), + 'subtype': message.subtype_id.name if message.subtype_id else False, + 'body': message.body, + 'body_short': body_short, 'model': message.model, 'res_id': message.res_id, 'record_name': message.record_name, @@ -362,6 +429,7 @@ class mail_message(osv.Model): 'parent_id': parent_id, 'is_private': is_private, 'author_id': False, + 'author_avatar': message.author_avatar, 'is_author': False, 'partner_ids': [], 'vote_nb': vote_nb, @@ -529,6 +597,7 @@ class mail_message(osv.Model): message_id_list.sort(key=lambda item: item['id']) message_id_list.insert(0, self._message_read_dict(cr, uid, message_tree[key], context=context)) + # create final ordered message_list based on parent_tree parent_list = parent_tree.items() parent_list = sorted(parent_list, key=lambda item: max([msg.get('id') for msg in item[1]]) if item[1] else item[0], reverse=True) message_list = [message for (key, msg_list) in parent_list for message in msg_list] @@ -550,7 +619,7 @@ class mail_message(osv.Model): def _find_allowed_model_wise(self, cr, uid, doc_model, doc_dict, context=None): doc_ids = doc_dict.keys() - allowed_doc_ids = self.pool.get(doc_model).search(cr, uid, [('id', 'in', doc_ids)], context=context) + allowed_doc_ids = self.pool[doc_model].search(cr, uid, [('id', 'in', doc_ids)], context=context) return set([message_id for allowed_doc_id in allowed_doc_ids for message_id in doc_dict[allowed_doc_id]]) def _find_allowed_doc_ids(self, cr, uid, model_ids, context=None): @@ -586,7 +655,7 @@ class mail_message(osv.Model): elif not ids: return ids - pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'])['partner_id'][0] + pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id author_ids, partner_ids, allowed_ids = set([]), set([]), set([]) model_ids = {} @@ -600,12 +669,14 @@ class mail_message(osv.Model): model_ids.setdefault(message.get('model'), {}).setdefault(message.get('res_id'), set()).add(message.get('id')) allowed_ids = self._find_allowed_doc_ids(cr, uid, model_ids, context=context) - final_ids = author_ids | partner_ids | allowed_ids + if count: return len(final_ids) else: - return list(final_ids) + # re-construct a list based on ids, because set did not keep the original order + id_list = [id for id in ids if id in final_ids] + return id_list def check_access_rule(self, cr, uid, ids, operation, context=None): """ Access rules of mail.message: @@ -618,14 +689,14 @@ class mail_message(osv.Model): - no model, no res_id, I create a private message OR - pid in message_follower_ids if model, res_id OR - mail_notification (parent_id.id, pid) exists, uid has been notified of the parent, OR - - uid have write access on the related document if model, res_id, OR + - uid have write or create access on the related document if model, res_id, OR - otherwise: raise - write: if - author_id == pid, uid is the author, OR - - uid has write access on the related document if model, res_id + - uid has write or create access on the related document if model, res_id - otherwise: raise - unlink: if - - uid has write access on the related document if model, res_id + - uid has write or create access on the related document if model, res_id - otherwise: raise """ def _generate_model_record_ids(msg_val, msg_ids=[]): @@ -634,8 +705,9 @@ class mail_message(osv.Model): """ model_record_ids = {} for id in msg_ids: - if msg_val[id]['model'] and msg_val[id]['res_id']: - model_record_ids.setdefault(msg_val[id]['model'], dict()).setdefault(msg_val[id]['res_id'], set()).add(msg_val[id]['res_id']) + vals = msg_val.get(id, {}) + if vals.get('model') and vals.get('res_id'): + model_record_ids.setdefault(vals['model'], set()).add(vals['res_id']) return model_record_ids if uid == SUPERUSER_ID: @@ -644,10 +716,10 @@ class mail_message(osv.Model): ids = [ids] not_obj = self.pool.get('mail.notification') fol_obj = self.pool.get('mail.followers') - partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0] + partner_id = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=None).partner_id.id # Read mail_message.ids to have their values - message_values = dict.fromkeys(ids) + message_values = dict.fromkeys(ids, {}) cr.execute('SELECT DISTINCT id, model, res_id, author_id, parent_id FROM "%s" WHERE id = ANY (%%s)' % self._table, (ids,)) for id, rmod, rid, author_id, parent_id in cr.fetchall(): message_values[id] = {'model': rmod, 'res_id': rid, 'author_id': author_id, 'parent_id': parent_id} @@ -681,10 +753,10 @@ class mail_message(osv.Model): ], context=context) notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, SUPERUSER_ID, not_ids, context=context)] elif operation == 'create': - for doc_model, doc_dict in model_record_ids.items(): + for doc_model, doc_ids in model_record_ids.items(): fol_ids = fol_obj.search(cr, SUPERUSER_ID, [ ('res_model', '=', doc_model), - ('res_id', 'in', list(doc_dict.keys())), + ('res_id', 'in', list(doc_ids)), ('partner_id', '=', partner_id), ], context=context) fol_mids = [follower.res_id for follower in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context)] @@ -695,15 +767,13 @@ class mail_message(osv.Model): other_ids = other_ids.difference(set(notified_ids)) model_record_ids = _generate_model_record_ids(message_values, other_ids) document_related_ids = [] - for model, doc_dict in model_record_ids.items(): - model_obj = self.pool.get(model) - mids = model_obj.exists(cr, uid, doc_dict.keys()) - 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) + for model, doc_ids in model_record_ids.items(): + model_obj = self.pool[model] + mids = model_obj.exists(cr, uid, list(doc_ids)) + if hasattr(model_obj, 'check_mail_message_access'): + model_obj.check_mail_message_access(cr, uid, mids, operation, context=context) else: - model_obj.check_access_rights(cr, uid, operation) - model_obj.check_access_rule(cr, uid, mids, operation, context=context) + self.pool['mail.thread'].check_mail_message_access(cr, uid, mids, operation, model_obj=model_obj, context=context) document_related_ids += [mid for mid, message in message_values.iteritems() if message.get('model') == model and message.get('res_id') in mids] @@ -715,16 +785,67 @@ class mail_message(osv.Model): _('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 _get_reply_to(self, cr, uid, values, context=None): + """ Return a specific reply_to: alias of the document through message_get_reply_to + or take the email_from + """ + email_reply_to = None + + ir_config_parameter = self.pool.get("ir.config_parameter") + catchall_domain = ir_config_parameter.get_param(cr, SUPERUSER_ID, "mail.catchall.domain", context=context) + + # model, res_id, email_from: comes from values OR related message + model, res_id, email_from = values.get('model'), values.get('res_id'), values.get('email_from') + + # if model and res_id: try to use ``message_get_reply_to`` that returns the document alias + if not email_reply_to and model and res_id and catchall_domain and hasattr(self.pool[model], 'message_get_reply_to'): + email_reply_to = self.pool[model].message_get_reply_to(cr, uid, [res_id], context=context)[0] + # no alias reply_to -> catchall alias + if not email_reply_to and catchall_domain: + catchall_alias = ir_config_parameter.get_param(cr, SUPERUSER_ID, "mail.catchall.alias", context=context) + if catchall_alias: + email_reply_to = '%s@%s' % (catchall_alias, catchall_domain) + # still no reply_to -> reply_to will be the email_from + if not email_reply_to and email_from: + email_reply_to = email_from + + # format 'Document name ' + if email_reply_to and model and res_id: + emails = tools.email_split(email_reply_to) + if emails: + email_reply_to = emails[0] + document_name = self.pool[model].name_get(cr, SUPERUSER_ID, [res_id], context=context)[0] + if document_name: + # generate reply to + email_reply_to = formataddr((_('Followers of %s') % document_name[1], email_reply_to)) + + return email_reply_to + + def _get_message_id(self, cr, uid, values, context=None): + if values.get('reply_to'): + message_id = tools.generate_tracking_message_id('reply_to') + elif values.get('res_id') and values.get('model'): + message_id = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values) + else: + message_id = tools.generate_tracking_message_id('private') + return message_id + def create(self, cr, uid, values, context=None): if context is None: context = {} default_starred = context.pop('default_starred', False) - if not values.get('message_id') and values.get('res_id') and values.get('model'): - values['message_id'] = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values) - elif not values.get('message_id'): - values['message_id'] = tools.generate_tracking_message_id('private') + + if 'email_from' not in values: # needed to compute reply_to + values['email_from'] = self._get_default_from(cr, uid, context=context) + if 'message_id' not in values: + values['message_id'] = self._get_message_id(cr, uid, values, context=context) + if 'reply_to' not in values: + values['reply_to'] = self._get_reply_to(cr, uid, values, context=context) + newid = super(mail_message, self).create(cr, uid, values, context) - self._notify(cr, uid, newid, context=context) + self._notify(cr, uid, newid, context=context, + force_send=context.get('mail_notify_force_send', True), + user_signature=context.get('mail_notify_user_signature', True)) # TDE FIXME: handle default_starred. Why not setting an inv on starred ? # Because starred will call set_message_starred, that looks for notifications. # When creating a new mail_message, it will create a notification to a message @@ -749,7 +870,7 @@ class mail_message(osv.Model): attachments_to_delete = [] 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: + if attach.res_model == self._name and (attach.res_id == message.id or attach.res_id == 0): attachments_to_delete.append(attach.id) if attachments_to_delete: self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context) @@ -838,40 +959,42 @@ class mail_message(osv.Model): return '' return result - def _notify(self, cr, uid, newid, context=None): + def _notify(self, cr, uid, newid, context=None, force_send=False, user_signature=True): """ 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 """ notification_obj = self.pool.get('mail.notification') message = self.browse(cr, uid, newid, context=context) - partners_to_notify = set([]) - # message has no subtype_id: pure log message -> no partners, no one notified - if not message.subtype_id: - return True - # all partner_ids of the mail.message have to be notified - if message.partner_ids: - partners_to_notify |= set(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: + + # all followers of the mail.message document have to be added as partners and notified if a subtype is defined (otherwise: log message) + if message.subtype_id and message.model and message.res_id: fol_obj = self.pool.get("mail.followers") # browse as SUPERUSER because rules could restrict the search results - fol_ids = fol_obj.search(cr, SUPERUSER_ID, [ - ('res_model', '=', message.model), - ('res_id', '=', message.res_id), - ('subtype_ids', 'in', message.subtype_id.id) + fol_ids = fol_obj.search( + cr, SUPERUSER_ID, [ + ('res_model', '=', message.model), + ('res_id', '=', message.res_id), ], context=context) - partners_to_notify |= set(fo.partner_id for fo in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context)) + partners_to_notify |= set( + fo.partner_id.id for fo in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context) + if message.subtype_id.id in [st.id for st in fo.subtype_ids] + ) # remove me from notified partners, unless the message is written on my own wall - if message.author_id and message.model == "res.partner" and message.res_id == message.author_id.id: - partners_to_notify |= set([message.author_id]) + if message.subtype_id and message.author_id and message.model == "res.partner" and message.res_id == message.author_id.id: + partners_to_notify |= set([message.author_id.id]) elif message.author_id: - partners_to_notify = partners_to_notify - set([message.author_id]) + partners_to_notify -= set([message.author_id.id]) + + # all partner_ids of the mail.message have to be notified regardless of the above (even the author if explicitly added!) + if message.partner_ids: + partners_to_notify |= set([p.id for p in message.partner_ids]) # notify - if partners_to_notify: - self.write(cr, SUPERUSER_ID, [newid], {'notified_partner_ids': [(4, p.id) for p in partners_to_notify]}, context=context) - notification_obj._notify(cr, uid, newid, context=context) + notification_obj._notify( + cr, uid, newid, partners_to_notify=list(partners_to_notify), context=context, + force_send=force_send, user_signature=user_signature + ) message.refresh() # An error appear when a user receive a notification without notifying