diff options
| -rw-r--r-- | addons/mail/models/mail_activity.py | 5 | ||||
| -rw-r--r-- | addons/mail/models/mail_mail.py | 4 | ||||
| -rw-r--r-- | addons/mail/models/mail_message.py | 122 | ||||
| -rw-r--r-- | addons/mail/models/mail_thread.py | 35 | ||||
| -rw-r--r-- | addons/mail/static/src/js/models/messages/message.js | 2 | ||||
| -rw-r--r-- | addons/test_mail/tests/test_message_compose.py | 4 | ||||
| -rw-r--r-- | addons/test_mail/tests/test_performance.py | 2 |
7 files changed, 111 insertions, 63 deletions
diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py index a989ac4c983..ab098ed52a1 100644 --- a/addons/mail/models/mail_activity.py +++ b/addons/mail/models/mail_activity.py @@ -386,13 +386,14 @@ class MailActivity(models.Model): engine='ir.qweb', minimal_qcontext=True ) - self.env['mail.thread'].message_notify( + record = self.env[activity.res_model].browse(activity.res_id) + record.message_notify( partner_ids=activity.user_id.partner_id.ids, body=body, subject=_('%s: %s assigned to you') % (activity.res_name, activity.summary or activity.activity_type_id.name), record_name=activity.res_name, model_description=model_description, - notif_layout='mail.mail_notification_light' + notif_layout='mail.mail_notification_light', ) @api.multi diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py index a2ba820656f..ffc8aa33399 100644 --- a/addons/mail/models/mail_mail.py +++ b/addons/mail/models/mail_mail.py @@ -168,7 +168,7 @@ class MailMail(models.Model): 'failure_type': failure_type, 'failure_reason': failure_reason, }) - messages = notifications.mapped('mail_message_id').filtered(lambda m: m.res_id and m.model) + messages = notifications.mapped('mail_message_id').filtered(lambda m: m.is_thread_message()) messages._notify_failure_update() # notify user that we have a failure (notifications - failed).sudo().write({ 'email_status': 'sent', @@ -312,7 +312,7 @@ class MailMail(models.Model): bounce_alias = ICP.get_param("mail.bounce.alias") catchall_domain = ICP.get_param("mail.catchall.domain") if bounce_alias and catchall_domain: - if mail.model and mail.res_id: + if mail.mail_message_id.is_thread_message(): headers['Return-Path'] = '%s+%d-%s-%d@%s' % (bounce_alias, mail.id, mail.model, mail.res_id, catchall_domain) else: headers['Return-Path'] = '%s+%d@%s' % (bounce_alias, mail.id, catchall_domain) diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index c9a54701ca7..0e595654885 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -59,7 +59,8 @@ class Message(models.Model): message_type = fields.Selection([ ('email', 'Email'), ('comment', 'Comment'), - ('notification', 'System notification')], + ('notification', 'System notification'), + ('user_notification', 'User Specific Notification')], 'Type', required=True, default='email', help="Message type: email for email message, notification for system " "message, comment for other messages such as user replies", @@ -79,8 +80,7 @@ class Message(models.Model): author_avatar = fields.Binary("Author's avatar", related='author_id.image_small', readonly=False) # recipients: include inactive partners (they may have been archived after # the message was sent, but they should remain visible in the relation) - partner_ids = fields.Many2many('res.partner', string='Recipients', - context={'active_test': False}) + partner_ids = fields.Many2many('res.partner', string='Recipients', context={'active_test': False}) needaction_partner_ids = fields.Many2many( 'res.partner', 'mail_message_res_partner_needaction_rel', string='Partners with Need Action', context={'active_test': False}) @@ -393,7 +393,8 @@ class Message(models.Model): customer_email_data.append((partner_tree[notification.res_partner_id.id][0], partner_tree[notification.res_partner_id.id][1], notification.email_status)) has_access_to_model = message.model and self.env[message.model].check_access_rights('read', raise_exception=False) - main_attachment = has_access_to_model and message.res_id and self.env[message.model].search([('id', '=',message.res_id)]) and getattr(self.env[message.model].browse(message.res_id), 'message_main_attachment_id') + if message.attachment_ids: + main_attachment = has_access_to_model and message.res_id and self.env[message.model].search([('id', '=', message.res_id)]) and getattr(self.env[message.model].browse(message.res_id), 'message_main_attachment_id') attachment_ids = [] for attachment in message.attachment_ids: if attachment.id in attachments_tree: @@ -417,7 +418,13 @@ class Message(models.Model): @api.multi def message_fetch_failed(self): - messages = self.search([('has_error', '=', True), ('author_id.id', '=', self.env.user.partner_id.id), ('res_id', '!=', 0), ('model', '!=', False)]) + messages = self.search([ + ('has_error', '=', True), + ('author_id.id', '=', self.env.user.partner_id.id), + ('res_id', '!=', 0), + ('model', '!=', False), + ('message_type', '!=', 'user_notification') + ]) return messages._format_mail_failures() @api.model @@ -515,7 +522,7 @@ class Message(models.Model): message['needaction_partner_ids'] = notif_dict.get(message['id'], dict()).get('partner_id', []) message['is_note'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['id'] == note_id message['is_discussion'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['id'] == com_id - message['is_notification'] = message['is_note'] and not message['model'] and not message['res_id'] + message['is_notification'] = message['message_type'] == 'user_notification' message['subtype_description'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['description'] if message['model'] and self.env[message['model']]._original_module: message['module_icon'] = modules.module.get_module_icon(self.env[message['model']]._original_module) @@ -583,6 +590,19 @@ class Message(models.Model): if not self._cr.fetchone(): self._cr.execute("""CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)""") + @api.multi + def is_thread_message(self, vals=None): + if vals: + res_id = vals.get('res_id') + model = vals.get('model') + message_type = vals.get('message_type') + else: + self.ensure_one() + res_id = self.res_id + model = self.model + message_type = self.message_type + return res_id and model and message_type != 'user_notification' + @api.model def _find_allowed_model_wise(self, doc_model, doc_dict): doc_ids = list(doc_dict) @@ -640,7 +660,7 @@ class Message(models.Model): super(Message, self.sudo(access_rights_uid or self._uid)).check_access_rights('read') self._cr.execute(""" - SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, + SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.message_type, COALESCE(partner_rel.res_partner_id, needaction_rel.res_partner_id), channel_partner.channel_id as channel_id FROM "%s" m @@ -656,14 +676,14 @@ class Message(models.Model): ON channel_partner.channel_id = channel.id AND channel_partner.partner_id = %%(pid)s WHERE m.id = ANY (%%(ids)s)""" % self._table, dict(pid=pid, ids=ids)) - for id, rmod, rid, author_id, partner_id, channel_id in self._cr.fetchall(): + for id, rmod, rid, author_id, message_type, partner_id, channel_id in self._cr.fetchall(): if author_id == pid: author_ids.add(id) elif partner_id == pid: partner_ids.add(id) elif channel_id: channel_ids.add(id) - elif rmod and rid: + elif rmod and rid and message_type != 'user_notification': model_ids.setdefault(rmod, {}).setdefault(rid, set()).add(id) allowed_ids = self._find_allowed_doc_ids(model_ids) @@ -733,13 +753,14 @@ class Message(models.Model): (self._description, operation)) # Read mail_message.ids to have their values - message_values = dict((res_id, {}) for res_id in self.ids) + message_values = dict((message_id, {}) for message_id in self.ids) if operation == 'read': self._cr.execute(""" SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.parent_id, COALESCE(partner_rel.res_partner_id, needaction_rel.res_partner_id), - channel_partner.channel_id as channel_id, m.moderation_status + channel_partner.channel_id as channel_id, m.moderation_status, + m.message_type as message_type FROM "%s" m LEFT JOIN "mail_message_res_partner_rel" partner_rel ON partner_rel.mail_message_id = m.id AND partner_rel.res_partner_id = %%(pid)s @@ -752,7 +773,7 @@ class Message(models.Model): LEFT JOIN "mail_channel_partner" channel_partner ON channel_partner.channel_id = channel.id AND channel_partner.partner_id = %%(pid)s WHERE m.id = ANY (%%(ids)s)""" % self._table, dict(pid=self.env.user.partner_id.id, ids=self.ids)) - for mid, rmod, rid, author_id, parent_id, partner_id, channel_id, moderation_status in self._cr.fetchall(): + for mid, rmod, rid, author_id, parent_id, partner_id, channel_id, moderation_status, message_type in self._cr.fetchall(): message_values[mid] = { 'model': rmod, 'res_id': rid, @@ -760,13 +781,15 @@ class Message(models.Model): 'parent_id': parent_id, 'moderation_status': moderation_status, 'moderator_id': False, - 'notified': any((message_values[mid].get('notified'), partner_id, channel_id)) + 'notified': any((message_values[mid].get('notified'), partner_id, channel_id)), + 'message_type': message_type, } elif operation == 'write': self._cr.execute(""" SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.parent_id, m.moderation_status, COALESCE(partner_rel.res_partner_id, needaction_rel.res_partner_id), - channel_partner.channel_id as channel_id, channel_moderator_rel.res_users_id as moderator_id + channel_partner.channel_id as channel_id, channel_moderator_rel.res_users_id as moderator_id, + m.message_type as message_type FROM "%s" m LEFT JOIN "mail_message_res_partner_rel" partner_rel ON partner_rel.mail_message_id = m.id AND partner_rel.res_partner_id = %%(pid)s @@ -783,7 +806,7 @@ class Message(models.Model): LEFT JOIN "mail_channel_moderator_rel" channel_moderator_rel ON channel_moderator_rel.mail_channel_id = moderated_channel.id AND channel_moderator_rel.res_users_id = %%(uid)s WHERE m.id = ANY (%%(ids)s)""" % self._table, dict(pid=self.env.user.partner_id.id, uid=self.env.user.id, ids=self.ids)) - for mid, rmod, rid, author_id, parent_id, moderation_status, partner_id, channel_id, moderator_id in self._cr.fetchall(): + for mid, rmod, rid, author_id, parent_id, moderation_status, partner_id, channel_id, moderator_id, message_type in self._cr.fetchall(): message_values[mid] = { 'model': rmod, 'res_id': rid, @@ -791,35 +814,38 @@ class Message(models.Model): 'parent_id': parent_id, 'moderation_status': moderation_status, 'moderator_id': moderator_id, - 'notified': any((message_values[mid].get('notified'), partner_id, channel_id)) + 'notified': any((message_values[mid].get('notified'), partner_id, channel_id)), + 'message_type': message_type, } elif operation == 'create': - self._cr.execute("""SELECT DISTINCT id, model, res_id, author_id, parent_id, moderation_status FROM "%s" WHERE id = ANY (%%s)""" % self._table, (self.ids,)) - for mid, rmod, rid, author_id, parent_id, moderation_status in self._cr.fetchall(): + self._cr.execute("""SELECT DISTINCT id, model, res_id, author_id, parent_id, moderation_status, message_type FROM "%s" WHERE id = ANY (%%s)""" % self._table, (self.ids,)) + for mid, rmod, rid, author_id, parent_id, moderation_status, message_type in self._cr.fetchall(): message_values[mid] = { 'model': rmod, 'res_id': rid, 'author_id': author_id, 'parent_id': parent_id, 'moderation_status': moderation_status, - 'moderator_id': False + 'moderator_id': False, + 'message_type': message_type, } else: # unlink - self._cr.execute("""SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.parent_id, m.moderation_status, channel_moderator_rel.res_users_id as moderator_id + self._cr.execute("""SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.parent_id, m.moderation_status, channel_moderator_rel.res_users_id as moderator_id, m.message_type as message_type FROM "%s" m LEFT JOIN "mail_channel" moderated_channel ON m.moderation_status = 'pending_moderation' AND m.res_id = moderated_channel.id LEFT JOIN "mail_channel_moderator_rel" channel_moderator_rel ON channel_moderator_rel.mail_channel_id = moderated_channel.id AND channel_moderator_rel.res_users_id = (%%s) WHERE m.id = ANY (%%s)""" % self._table, (self.env.user.id, self.ids,)) - for mid, rmod, rid, author_id, parent_id, moderation_status, moderator_id in self._cr.fetchall(): + for mid, rmod, rid, author_id, parent_id, moderation_status, moderator_id, message_type in self._cr.fetchall(): message_values[mid] = { 'model': rmod, 'res_id': rid, 'author_id': author_id, 'parent_id': parent_id, 'moderation_status': moderation_status, - 'moderator_id': moderator_id + 'moderator_id': moderator_id, + 'message_type': message_type, } # Author condition (READ, WRITE, CREATE (private)) @@ -832,7 +858,7 @@ class Message(models.Model): if message.get('moderation_status') != 'pending_moderation' and message.get('author_id') == self.env.user.partner_id.id] elif operation == 'create': author_ids = [mid for mid, message in message_values.items() - if not message.get('model') and not message.get('res_id')] + if not self.is_thread_message(message)] # Parent condition, for create (check for received notifications for the created message parent) notified_ids = [] @@ -873,7 +899,10 @@ class Message(models.Model): ]) fol_mids = [follower.res_id for follower in followers] notified_ids += [mid for mid, message in message_values.items() - if message.get('model') == doc_model and message.get('res_id') in fol_mids] + if message.get('model') == doc_model and + message.get('res_id') in fol_mids and + message.get('message_type') != 'user_notification' + ] # CRUD: Access rights related to the document other_ids = other_ids.difference(set(notified_ids)) @@ -886,13 +915,13 @@ class Message(models.Model): DocumentModel.check_mail_message_access(mids.ids, operation) # ?? mids ? else: self.env['mail.thread'].check_mail_message_access(mids.ids, operation, model_name=model) - if operation in ['write', 'unlink']: - document_related_ids += [mid for mid, message in message_values.items() - if message.get('model') == model and message.get('res_id') in mids.ids and - message.get('moderation_status') != 'pending_moderation'] - else: - document_related_ids += [mid for mid, message in message_values.items() - if message.get('model') == model and message.get('res_id') in mids.ids] + document_related_ids += [ + mid for mid, message in message_values.items() + if (message.get('model') == model and + message.get('res_id') in mids.ids and + message.get('message_type') != 'user_notification' and + (message.get('moderation_status') != 'pending_moderation' or + operation not in ['write', 'unlink']))] # Calculate remaining ids: if not void, raise an error other_ids = other_ids.difference(set(document_related_ids)) @@ -915,15 +944,22 @@ class Message(models.Model): @api.model def _get_reply_to(self, values): """ Return a specific reply_to for the document """ - model, res_id, email_from = values.get('model', self._context.get('default_model')), values.get('res_id', self._context.get('default_res_id')), values.get('email_from') # ctx values / defualt_get res ? - records = self.env[model].browse([res_id]) if model and res_id else None - return self.env['mail.thread']._notify_get_reply_to_on_records(default=email_from, records=records)[res_id or False] + model = values.get('model', self._context.get('default_model')) + res_id = values.get('res_id', self._context.get('default_res_id')) + email_from = values.get('email_from') + message_type = values.get('message_type') + records = None + if self.is_thread_message({'model': model, 'res_id': res_id, 'message_type': message_type}): + records = self.env[model].browse([res_id]) + else: + res_id = False + return self.env['mail.thread']._notify_get_reply_to_on_records(default=email_from, records=records)[res_id] @api.model def _get_message_id(self, values): if values.get('no_auto_thread', False) is True: message_id = tools.generate_tracking_message_id('reply_to') - elif values.get('res_id') and values.get('model'): + elif self.is_thread_message(values): message_id = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values) else: message_id = tools.generate_tracking_message_id('private') @@ -933,7 +969,7 @@ class Message(models.Model): def _invalidate_documents(self): """ Invalidate the cache of the documents followed by ``self``. """ for record in self: - if record.model and record.res_id and 'message_ids' in self.env[record.model]: + if record.is_thread_message() and 'message_ids' in self.env[record.model]: self.env[record.model].invalidate_cache(fnames=[ 'message_ids', 'message_unread', @@ -996,7 +1032,7 @@ class Message(models.Model): if other_cmd: message.sudo().write({'tracking_value_ids': tracking_values_cmd}) - if values.get('model') and values.get('res_id'): + if message.is_thread_message(values): message._invalidate_documents() return message @@ -1010,13 +1046,14 @@ class Message(models.Model): @api.multi def write(self, vals): - if 'model' in vals or 'res_id' in vals: + record_changed = 'model' in vals or 'res_id' in vals + if record_changed or 'message_type' in vals: self._invalidate_documents() res = super(Message, self).write(vals) if vals.get('attachment_ids'): for mail in self: mail.attachment_ids.check(mode='read') - if 'notification_ids' in vals or 'model' in vals or 'res_id' in vals: + if 'notification_ids' in vals or record_changed: self._invalidate_documents() return res @@ -1058,6 +1095,7 @@ class Message(models.Model): notification templates); :param mail_auto_delete: delete notification emails once sent; """ + msg_vals = msg_vals if msg_vals else {} rdata = self._notify_compute_recipients(record, msg_vals) return self._notify_recipients( @@ -1079,7 +1117,9 @@ class Message(models.Model): 'partners': [], 'channels': [], } - res = self.env['mail.followers']._get_recipient_data(record, subtype_id, pids, cids) + res = [] + user_notification = msg_vals.get('message_type') == 'user_notification' + res = self.env['mail.followers']._get_recipient_data(not user_notification and record, subtype_id, pids, cids) author_id = msg_vals.get('author_id') or self.author_id.id if res else False for pid, cid, active, pshare, ctype, notif, groups in res: if pid and pid == author_id and not self.env.context.get('mail_notify_author'): # do not notify the author of its own messages @@ -1214,7 +1254,7 @@ class Message(models.Model): }) # proceed with notification process to send notification emails and Inbox messages for message in self: - record = self.env[message.model].browse(message.res_id) if message.model and message.res_id else None + record = self.env[message.model].browse(message.res_id) if message.is_thread_message() else None message._notify(record, {}) @api.multi diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index b55f5cc0937..b53f9713583 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -91,7 +91,7 @@ class MailThread(models.AbstractModel): compute='_get_followers', search='_search_follower_channels') message_ids = fields.One2many( 'mail.message', 'res_id', string='Messages', - domain=lambda self: [('model', '=', self._name)], auto_join=True) + domain=lambda self: [('model', '=', self._name), ('message_type', '!=', 'user_notification')], auto_join=True) message_unread = fields.Boolean( 'Unread Messages', compute='_get_message_unread', help="If checked new messages require your attention.") @@ -187,8 +187,9 @@ class MailThread(models.AbstractModel): ON (cp.channel_id = rel.mail_channel_id AND cp.partner_id = %s AND (cp.seen_message_id IS NULL OR cp.seen_message_id < msg.id)) WHERE msg.model = %s AND msg.res_id = ANY(%s) AND + msg.message_type != 'user_notification' AND (msg.author_id IS NULL OR msg.author_id != %s) AND - (msg.message_type != 'notification' OR msg.model != 'mail.channel')""", + (msg.message_type not in ('notification', 'user_notification') OR msg.model != 'mail.channel')""", (partner_id, self._name, list(self.ids), partner_id,)) for result in self._cr.fetchall(): res[result[0]] += 1 @@ -205,7 +206,7 @@ class MailThread(models.AbstractModel): self._cr.execute(""" SELECT msg.res_id FROM mail_message msg RIGHT JOIN mail_message_res_partner_needaction_rel rel ON rel.mail_message_id = msg.id AND rel.res_partner_id = %s AND (rel.is_read = false OR rel.is_read IS NULL) - WHERE msg.model = %s AND msg.res_id in %s""", + WHERE msg.model = %s AND msg.res_id in %s AND msg.message_type != 'user_notification'""", (self.env.user.partner_id.id, self._name, tuple(self.ids),)) for result in self._cr.fetchall(): res[result[0]] += 1 @@ -225,7 +226,7 @@ class MailThread(models.AbstractModel): self._cr.execute(""" SELECT msg.res_id, COUNT(msg.res_id) FROM mail_message msg RIGHT JOIN mail_message_res_partner_needaction_rel rel ON rel.mail_message_id = msg.id AND rel.email_status in ('exception','bounce') - WHERE msg.author_id = %s AND msg.model = %s AND msg.res_id in %s + WHERE msg.author_id = %s AND msg.model = %s AND msg.res_id in %s AND msg.message_type != 'user_notification' GROUP BY msg.res_id""", (self.env.user.partner_id.id, self._name, tuple(self.ids),)) res.update(self._cr.fetchall()) @@ -341,7 +342,7 @@ class MailThread(models.AbstractModel): cascaded, because link is done through (res_model, res_id). """ if not self: return True - self.env['mail.message'].search([('model', '=', self._name), ('res_id', 'in', self.ids)]).unlink() + self.env['mail.message'].search([('model', '=', self._name), ('res_id', 'in', self.ids), ('message_type', '!=', 'user_notification')]).unlink() res = super(MailThread, self).unlink() self.env['mail.followers'].sudo().search( [('res_model', '=', self._name), ('res_id', 'in', self.ids)] @@ -534,10 +535,12 @@ class MailThread(models.AbstractModel): msg_comment = MailMessage.search([ ('model', '=', self._name), ('res_id', '=', self.id), + ('message_type', '!=', 'user_notification'), ('subtype_id', '=', subtype_comment)]) msg_not_comment = MailMessage.search([ ('model', '=', self._name), ('res_id', '=', self.id), + ('message_type', '!=', 'user_notification'), ('subtype_id', '!=', subtype_comment)]) # update the messages @@ -1948,7 +1951,7 @@ class MailThread(models.AbstractModel): # set in the context. model = False if self.ids: - self.ensure_one() + self.ensure_one() # not so usefull model = kwargs.get('model', False) if self._name == 'mail.thread' else self._name if model and model != self._name and hasattr(self.env[model], 'message_post'): return self.env[model].browse(self.ids).message_post( @@ -1991,8 +1994,10 @@ class MailThread(models.AbstractModel): subtype = 'mail.%s' % subtype subtype_id = self.env['ir.model.data'].xmlid_to_res_id(subtype) + user_notification = message_type == 'user_notification' + # automatically subscribe recipients if asked to - if self._context.get('mail_post_autofollow') and self.ids and partner_ids: + if self._context.get('mail_post_autofollow') and self.ids and not user_notification and partner_ids: partner_to_subscribe = partner_ids if self._context.get('mail_post_autofollow_partner_ids'): partner_to_subscribe = [p for p in partner_ids if p in self._context.get('mail_post_autofollow_partner_ids')] @@ -2000,8 +2005,8 @@ class MailThread(models.AbstractModel): # _mail_flat_thread: automatically set free messages to the first posted message MailMessage = self.env['mail.message'] - if self._mail_flat_thread and model and not parent_id and self.ids: - messages = MailMessage.search(['&', ('res_id', '=', self.ids[0]), ('model', '=', model)], order="id ASC", limit=1) + if self._mail_flat_thread and model and not parent_id and self.ids and not user_notification: + messages = MailMessage.search(['&', ('res_id', '=', self.ids[0]), ('model', '=', model), ('message_type', '!=', 'user_notification')], order="id ASC", limit=1) parent_id = messages.ids and messages.ids[0] or False # 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: @@ -2047,7 +2052,8 @@ class MailThread(models.AbstractModel): canned_response_ids = values.pop('canned_response_ids', False) new_message = MailMessage.create(values) values['canned_response_ids'] = canned_response_ids - self._message_post_after_hook(new_message, values, model_description=model_description, mail_auto_delete=mail_auto_delete) + record = self.env['mail.thread'] if user_notification else self + record._message_post_after_hook(new_message, values, model_description=model_description, mail_auto_delete=mail_auto_delete) return new_message def _message_post_after_hook(self, message, msg_vals, model_description=False, mail_auto_delete=True): @@ -2137,8 +2143,8 @@ class MailThread(models.AbstractModel): return composer.send_mail() def message_notify(self, partner_ids, body='', subject=False, **kwargs): - """ Shortcut allowing to notify partners of messages not linked to - any document. It pushes notifications on inbox or by email depending + """ Shortcut allowing to notify partners of messages that shouldn't be + displayed on a document. It pushes notifications on inbox or by email depending on the user configuration, like other notifications. """ kw_author = kwargs.pop('author_id', False) if kw_author: @@ -2154,16 +2160,15 @@ class MailThread(models.AbstractModel): 'body': body, 'author_id': author.id, 'email_from': email_from, - 'message_type': 'notification', + 'message_type': 'user_notification', 'partner_ids': partner_ids, - 'model': False, 'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'), 'record_name': False, 'reply_to': self.env['mail.thread']._notify_get_reply_to(default=email_from, records=None)[False], 'message_id': tools.generate_tracking_message_id('message-notify'), } msg_values.update(kwargs) - return self.env['mail.thread'].message_post(**msg_values) + return self.message_post(**msg_values) def _message_log(self, body='', subject=False, message_type='notification', **kwargs): """ Shortcut allowing to post note on a document. It does not perform diff --git a/addons/mail/static/src/js/models/messages/message.js b/addons/mail/static/src/js/models/messages/message.js index 2e49975c9d9..73c4ece9025 100644 --- a/addons/mail/static/src/js/models/messages/message.js +++ b/addons/mail/static/src/js/models/messages/message.js @@ -368,7 +368,7 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin * @return {boolean} */ isLinkedToDocumentThread: function () { - return !!(this._documentModel !== 'mail.channel' && this._documentID); + return !!(this._documentModel !== 'mail.channel' && this._documentID && this._type !== 'user_notification'); }, /** * State whether the current user is the author of this message diff --git a/addons/test_mail/tests/test_message_compose.py b/addons/test_mail/tests/test_message_compose.py index 2397023db24..b756d52d510 100644 --- a/addons/test_mail/tests/test_message_compose.py +++ b/addons/test_mail/tests/test_message_compose.py @@ -205,17 +205,19 @@ class TestMessagePost(BaseFunctionalTest, MockEmails, TestRecipients): def test_post_notify(self): self.user_employee.write({'notification_type': 'inbox'}) - new_notification = self.env['mail.thread'].message_notify( + new_notification = self.test_record.message_notify( subject='This should be a subject', body='<p>You have received a notification</p>', partner_ids=[(4, self.partner_1.id), (4, self.user_employee.partner_id.id)], ) self.assertEqual(new_notification.subtype_id, self.env.ref('mail.mt_note')) + self.assertEqual(new_notification.message_type, 'user_notification') self.assertEqual(new_notification.body, '<p>You have received a notification</p>') self.assertEqual(new_notification.author_id, self.env.user.partner_id) self.assertEqual(new_notification.email_from, formataddr((self.env.user.name, self.env.user.email))) self.assertEqual(new_notification.needaction_partner_ids, self.partner_1 | self.user_employee.partner_id) + self.assertNotIn(new_notification, self.test_record.message_ids) class TestComposer(BaseFunctionalTest, MockEmails, TestRecipients): diff --git a/addons/test_mail/tests/test_performance.py b/addons/test_mail/tests/test_performance.py index 08ded3c4769..f87686cdf5d 100644 --- a/addons/test_mail/tests/test_performance.py +++ b/addons/test_mail/tests/test_performance.py @@ -485,7 +485,7 @@ class TestHeavyMailPerformance(TransactionCase): 'user_id': self.env.uid, }) self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id) - with self.assertQueryCount(__system__=60, emp=75): # com runbot: 60 - 75 // test_mail only: 60 - 72 + with self.assertQueryCount(__system__=61, emp=75): # com runbot: 60 - 75 // test_mail only: 60 - 72 rec.write({'user_id': self.user_portal.id}) self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id | self.user_portal.partner_id) # write tracking message |
