# Copyright 2019 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from email.utils import getaddresses from lxml import etree from odoo import _, api, fields, models from odoo.tools import email_split, email_split_and_format class MailThread(models.AbstractModel): _inherit = "mail.thread" failed_message_ids = fields.One2many( "mail.message", "res_id", string="Failed Messages", domain=lambda self: [("model", "=", self._name)] + self._get_failed_message_domain(), ) def _get_failed_message_domain(self): """Domain used to display failed messages on the 'failed_messages' widget""" failed_states = self.env["mail.message"].get_failed_states() return [ ("mail_tracking_needs_action", "=", True), ("mail_tracking_ids.state", "in", list(failed_states)), ] @api.model def _message_route_process(self, message, message_dict, routes): """Adds CC recipient to the message. Because Odoo implementation avoid store 'from, to, cc' recipients we ensure that this information its written into the mail.message record. """ message_dict.update( { "email_cc": message_dict.get("cc", False), "email_to": message_dict.get("to", False), } ) return super()._message_route_process(message, message_dict, routes) def _message_get_suggested_recipients(self): """Adds email 'extra' recipients as suggested recipients. If the recipient has a res.partner, use it. """ res = super()._message_get_suggested_recipients() self._add_extra_recipients_suggestions(res, "email_cc", _("Cc")) self._add_extra_recipients_suggestions(res, "email_to", _("Anon. To")) return res def _add_extra_recipients_suggestions(self, suggestions, field_mail, reason): ResPartnerObj = self.env["res.partner"] aliases = self.env["mail.alias"].get_aliases() email_extra_formated_list = [] for record in self: emails_extra = record.message_ids.mapped(field_mail) for email in emails_extra: email_extra_formated_list.extend(email_split_and_format(email)) email_extra_formated_list = set(email_extra_formated_list) email_extra_list = [x[1] for x in getaddresses(email_extra_formated_list)] partners_info = self._message_partner_info_from_emails(email_extra_list) for pinfo in partners_info: partner_id = pinfo["partner_id"] email = email_split(pinfo["full_name"])[0].lower() if not partner_id: if email not in aliases: self._message_add_suggested_recipient( suggestions, email=email, reason=reason ) else: partner = ResPartnerObj.browse(partner_id) self._message_add_suggested_recipient( suggestions, partner=partner, reason=reason ) @api.model def _fields_view_get( self, view_id=None, view_type="form", toolbar=False, submenu=False ): """Add filters for failed messages. These filters will show up on any form or search views of any model inheriting from ``mail.thread``. """ res = super()._fields_view_get( view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu ) if view_type not in {"search", "form"}: return res doc = etree.XML(res["arch"]) if view_type == "search": # Modify view to add new filter element nodes = doc.xpath("//search") if nodes: # Create filter element new_filter = etree.Element( "filter", { "string": _("Failed sent messages"), "name": "failed_message_ids", "domain": str( [ [ "failed_message_ids.mail_tracking_ids.state", "in", list(self.env["mail.message"].get_failed_states()), ], [ "failed_message_ids.mail_tracking_needs_action", "=", True, ], ] ), }, ) nodes[0].append(etree.Element("separator")) nodes[0].append(new_filter) elif view_type == "form": # Modify view to add new field element nodes = doc.xpath("//field[@name='message_ids' and @widget='mail_thread']") if nodes: # Create field field_failed_messages = etree.Element( "field", {"name": "failed_message_ids", "widget": "mail_failed_message"}, ) nodes[0].addprevious(field_failed_messages) res["arch"] = etree.tostring(doc, encoding="unicode") return res