Beispiel #1
0
    def action_invite(self):
        """ Process the wizard content and proceed with sending the related
            email(s), rendering any template patterns on the fly if needed """
        self.ensure_one()
        Partner = self.env['res.partner']

        # compute partners and emails, try to find partners for given emails
        valid_partners = self.partner_ids
        valid_emails = []
        for email in emails_split.split(self.emails or ''):
            partner = False
            email_normalized = tools.email_normalize(email)
            if email_normalized:
                partner = Partner.search([('email_normalized', '=', email_normalized)])
            if partner:
                valid_partners |= partner
            else:
                email_formatted = tools.email_split_and_format(email)
                if email_formatted:
                    valid_emails.extend(email_formatted)

        if not valid_partners and not valid_emails:
            raise UserError(_("Please enter at least one valid recipient."))

        answers = self._prepare_answers(valid_partners, valid_emails)
        for answer in answers:
            self._send_mail(answer)

        return {'type': 'ir.actions.act_window_close'}
Beispiel #2
0
 def _message_get_suggested_recipients(self):
     recipients = super(MailCCMixin, self)._message_get_suggested_recipients()
     for record in self:
         if record.email_cc:
             for email in tools.email_split_and_format(record.email_cc):
                 record._message_add_suggested_recipient(recipients, email=email, reason=_('CC Email'))
     return recipients
Beispiel #3
0
    def message_new(self, msg_dict, custom_values=None):
        if custom_values is None:
            custom_values = {}

        if custom_values.get('category_id', False):
            domain = [('category_id', '=', custom_values.pop('category_id'))]
            custom_values.pop
        else:
            domain = []

        # find or create customer
        email = email_split(msg_dict.get('email_from', False))[0]
        name = email_split_and_format(msg_dict.get('email_from', False))[0]
        customer = self.env['nursery.customer'].find_or_create(email, name)

        # happy Xmas
        plants = self.env['nursery.plant'].search(domain)
        plant = self.env['nursery.plant'].browse([random.choice(plants.ids)])
        custom_values.update({
            'customer_id': customer.id,
            'line_ids': [(4, plant.id)],
        })

        return super(Order, self).message_new(msg_dict,
                                              custom_values=custom_values)
Beispiel #4
0
 def build_email(self,
                 email_from,
                 email_to,
                 subject,
                 body,
                 email_cc=None,
                 email_bcc=None,
                 reply_to=False,
                 attachments=None,
                 message_id=None,
                 references=None,
                 object_id=False,
                 subtype='plain',
                 headers=None,
                 body_alternative=None,
                 subtype_alternative='plain'):
     if tools.config.get('email_to'):
         email_to = tools.email_split_and_format(tools.config['email_to'])
         email_cc = None
         email_bcc = None
     msg = super(IrMailServer,
                 self).build_email(email_from, email_to, subject, body,
                                   email_cc, email_bcc, reply_to,
                                   attachments, message_id, references,
                                   object_id, subtype, headers,
                                   body_alternative, subtype_alternative)
     return msg
Beispiel #5
0
 def _message_get_suggested_recipients(self):
     recipients = super(MailCCMixin, self)._message_get_suggested_recipients()
     for record in self:
         if record.email_cc:
             for email in tools.email_split_and_format(record.email_cc):
                 record._message_add_suggested_recipient(recipients, email=email, reason=_('CC Email'))
     return recipients
Beispiel #6
0
    def message_new(self, msg_dict, custom_values=None):
        if custom_values is None:
            custom_values = {}

        # find or create customer
        email_address = email_split(msg_dict.get('email_from', False))[0]
        customer = self.env['plant.customer'].search(
            [('email', 'ilike', email_address)], limit=1)
        if not customer:
            customer = self.env['plant.customer'].create({
                'name':
                email_split_and_format(msg_dict.get('email_from', False))[0],
                'email':
                email_address
            })

        # happy Xmas
        plants = self.env['plant.plant'].search([])
        plant = self.env['plant.plant'].browse([random.choice(plants.ids)])
        custom_values.update({
            'customer_id': customer.id,
            'line_ids': [(4, plant.id)],
        })
        return super(Order, self).message_new(msg_dict,
                                              custom_values=custom_values)
Beispiel #7
0
 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 = pinfo['full_name']
         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._prefetch)
             self._message_add_suggested_recipient(
                 suggestions, partner=partner, reason=reason)
Beispiel #8
0
    def action_invite(self):
        """ Process the wizard content and proceed with sending the related
            email(s), rendering any template patterns on the fly if needed """
        self.ensure_one()
        Partner = self.env['res.partner']

        # compute partners and emails, try to find partners for given emails
        valid_partners = self.partner_ids
        langs = set(valid_partners.mapped('lang')) - {False}
        if len(langs) == 1:
            self = self.with_context(lang=langs.pop())
        valid_emails = []
        for email in emails_split.split(self.emails or ''):
            partner = False
            email_normalized = tools.email_normalize(email)
            if email_normalized:
                limit = None if self.survey_users_login_required else 1
                partner = Partner.search(
                    [('email_normalized', '=', email_normalized)], limit=limit)
            if partner:
                valid_partners |= partner
            else:
                email_formatted = tools.email_split_and_format(email)
                if email_formatted:
                    valid_emails.extend(email_formatted)

        if not valid_partners and not valid_emails:
            raise UserError(_("Please enter at least one valid recipient."))

        answers = self._prepare_answers(valid_partners, valid_emails)
        for answer in answers:
            self._send_mail(answer)

        return {'type': 'ir.actions.act_window_close'}
Beispiel #9
0
    def action_invite(self):
        """ Process the wizard content and proceed with sending the related
            email(s), rendering any template patterns on the fly if needed """
        self.ensure_one()
        Partner = self.env['res.partner']

        # compute partners and emails, try to find partners for given emails
        valid_partners = self.partner_ids
        valid_emails = []
        for email in emails_split.split(self.emails or ''):
            partner = False
            email_normalized = tools.email_normalize(email)
            if email_normalized:
                partner = Partner.search([('email_normalized', '=',
                                           email_normalized)])
            if partner:
                valid_partners |= partner
            else:
                email_formatted = tools.email_split_and_format(email)
                if email_formatted:
                    valid_emails.extend(email_formatted)

        if not valid_partners and not valid_emails:
            raise UserError(_("Please enter at least one valid recipient."))

        answers = self._prepare_answers(valid_partners, valid_emails)
        for answer in answers:
            self._send_mail(answer)

        return {'type': 'ir.actions.act_window_close'}
Beispiel #10
0
    def message_get_suggested_recipients(self):
        """Adds email Cc recipients as suggested recipients.

        If the recipient has a res.partner, use it.
        """
        res = super().message_get_suggested_recipients()
        ResPartnerObj = self.env['res.partner']
        email_cc_formated_list = []
        for record in self:
            emails_cc = record.message_ids.mapped('email_cc')
            for email in emails_cc:
                email_cc_formated_list.extend(email_split_and_format(email))
        email_cc_formated_list = set(email_cc_formated_list)
        for cc in email_cc_formated_list:
            email_parts = getaddresses([cc])[0]
            partner_id = record.message_partner_info_from_emails(
                [email_parts[1]])[0].get('partner_id')
            if not partner_id:
                record._message_add_suggested_recipient(
                    res, email=cc, reason=_('Cc'))
            else:
                partner = ResPartnerObj.browse(partner_id, self._prefetch)
                record._message_add_suggested_recipient(
                    res, partner=partner, reason=_('Cc'))
        return res
    def _send_wecom_prepare_values(self, partner=None):
        """
        根据合作伙伴返回有关特定电子邮件值的字典,或者对整个邮件都是通用的。对于特定电子邮件值取决于对伙伴的字典,或者对mail.email_to给出的整个收件人来说都是通用的。

        :param Model partner: 具体的收件人合作伙伴
        """
        self.ensure_one()
        body_html = self._send_prepare_body_html()
        body_json = self._send_prepare_body_json()
        body_markdown = self._send_prepare_body_markdown()
        # body_alternative = tools.html2plaintext(body_html)
        if partner:
            email_to = [
                tools.formataddr((partner.name or "False", partner.email or "False"))
            ]
            message_to_user = [
                tools.formataddr(
                    (partner.name or "False", partner.wecom_userid or "False")
                )
            ]
        else:
            email_to = tools.email_split_and_format(self.email_to)
            message_to_user = self.message_to_user
        res = {
            # "message_body_text": message_body_text,
            # "message_body_html": message_body_html,
            "email_to": email_to,
            "message_to_user": message_to_user,
            "body_json": body_json,
            "body_html": body_html,
            "body_markdown": body_markdown,
        }

        return res
Beispiel #12
0
    def test_message_cc_update_no_old(self):
        record = self.env['mail.test.cc'].create({})
        self.alias.write({'alias_force_thread_id': record.id})

        self.format_and_process(MAIL_TEMPLATE, self.email_from, '*****@*****.**',
                                cc='cc2 <*****@*****.**>, [email protected]', target_model='mail.test.cc')
        cc = email_split_and_format(record.email_cc)
        self.assertEqual(sorted(cc), ['cc2 <*****@*****.**>', '*****@*****.**'], 'new cc should have been added on record (unique)')
Beispiel #13
0
 def test_message_cc_new(self):
     record = self.format_and_process(MAIL_TEMPLATE,
                                      self.email_from,
                                      '*****@*****.**',
                                      cc='[email protected], [email protected]',
                                      target_model='mail.test.cc')
     cc = email_split_and_format(record.email_cc)
     self.assertEqual(sorted(cc), ['*****@*****.**', '*****@*****.**'])
Beispiel #14
0
    def test_message_cc_update_no_old(self):
        record = self.env['mail.test.cc'].create({})
        self.alias.write({'alias_force_thread_id': record.id})

        self.format_and_process(MAIL_TEMPLATE, self.email_from, '*****@*****.**',
                                cc='cc2 <*****@*****.**>, [email protected]', target_model='mail.test.cc')
        cc = email_split_and_format(record.email_cc)
        self.assertEqual(sorted(cc), ['cc2 <*****@*****.**>', '*****@*****.**'], 'new cc should have been added on record (unique)')
Beispiel #15
0
 def send_get_mail_to(self, partner=None):
     """Forge the email_to with the following heuristic:
       - if 'partner', recipient specific (Partner Name <email>)
       - else fallback on mail.email_to splitting """
     self.ensure_one()
     if partner:
         email_to = [formataddr((partner.name, partner.email))]
     else:
         email_to = tools.email_split_and_format(self.email_to)
     return email_to
Beispiel #16
0
 def test_message_cc_new(self):
     alias = self.env['mail.alias'].create({
         'alias_name': 'cc_record',
         'alias_user_id': False,
         'alias_model_id': self.env['ir.model']._get('mail.test.cc').id,
         'alias_contact': 'everyone'})
     record = self.format_and_process(MAIL_TEMPLATE, target_model='mail.test.cc', to='*****@*****.**',
                                      cc='[email protected], [email protected]')
     cc = email_split_and_format(record.email_cc)
     self.assertEqual(sorted(cc), ['*****@*****.**', '*****@*****.**'], 'cc should contains exactly 2 cc')
Beispiel #17
0
 def send_get_mail_to(self, partner=None):
     """Forge the email_to with the following heuristic:
       - if 'partner', recipient specific (Partner Name <email>)
       - else fallback on mail.email_to splitting """
     self.ensure_one()
     if partner:
         email_to = [formataddr((partner.name, partner.email))]
     else:
         email_to = tools.email_split_and_format(self.email_to)
     return email_to
    def _get_recipients_from_record(self, form):
        """
        Get's all mail recipients from res.partner and formio.mail.recipient.

        :return array: With mail recipients in a dictionary.
        """
        res = []
        for line in self.mail_recipient_line:
            for record in line.mail_recipients_partner_id:
                mail_values = {}
                mail = tools.email_split_and_format(record.email)
                if mail:
                    mail_values['recipient'] = mail[0]
                if record.lang:
                    mail_values['lang'] = record.lang
                mail_values['template'] = line.mail_template_id.id
                mail_values['report'] = line.mail_report_id.id
                res.append(mail_values)
            for record in line.mail_recipients_address_id:
                mail_values = {}
                mail = tools.email_split_and_format(record.email)
                if mail:
                    mail_values['recipient'] = mail[0]
                mail_values['template'] = line.mail_template_id.id
                mail_values['report'] = line.mail_report_id.id
                res.append(mail_values)
            for record in line.mail_recipients_formio_component_id:
                mail_values = {}
                component_values = []
                if record.key not in form._formio.input_components.keys():
                    continue
                obj = form._formio.input_components[record.key]
                component_values.extend(self._get_component_mail(obj))
                for value in component_values:
                    mail = tools.email_split_and_format(value)
                    if mail:
                        mail_values['recipient'] = mail[0]
                mail_values['template'] = line.mail_template_id.id
                mail_values['report'] = line.mail_report_id.id
                res.append(mail_values)
        return res
Beispiel #19
0
 def test_message_cc_update_no_old(self):
     record = self.env['mail.test.cc'].create({})
     alias = self.env['mail.alias'].create({
         'alias_name': 'cc_record',
         'alias_user_id': False,
         'alias_model_id': self.env['ir.model']._get('mail.test.cc').id,
         'alias_contact': 'everyone',
         'alias_force_thread_id': record.id})
     self.format_and_process(MAIL_TEMPLATE, subject='Re: Frogs', target_model='mail.test.cc',
                             msg_id='*****@*****.**',
                             to='*****@*****.**',
                             cc='cc2 <*****@*****.**>, [email protected]')
     cc = email_split_and_format(record.email_cc)
     self.assertEqual(sorted(cc), ['cc2 <*****@*****.**>', '*****@*****.**'], 'new cc should have been added on record (unique)')
Beispiel #20
0
 def _track_sendgrid_emails(self):
     """ Create tracking e-mails after successfully sent with Sendgrid. """
     self.ensure_one()
     m_tracking = self.env['mail.tracking.email'].sudo()
     track_vals = self._prepare_sendgrid_tracking()
     for recipient in tools.email_split_and_format(self.email_to):
         track_vals['recipient'] = recipient
         m_tracking += m_tracking.create(track_vals)
     for partner in self.recipient_ids:
         track_vals.update({
             'partner_id': partner.id,
             'recipient': partner.email,
         })
         m_tracking += m_tracking.create(track_vals)
     return m_tracking
Beispiel #21
0
 def _onchange_emails(self):
     if self.emails and (self.survey_users_login_required and not self.survey_id.users_can_signup):
         raise UserError(_('This survey does not allow external people to participate. You should create user accounts or update survey access mode accordingly.'))
     if not self.emails:
         return
     valid, error = [], []
     emails = list(set(emails_split.split(self.emails or "")))
     for email in emails:
         email_check = tools.email_split_and_format(email)
         if not email_check:
             error.append(email)
         else:
             valid.extend(email_check)
     if error:
         raise UserError(_("Some emails you just entered are incorrect: %s") % (', '.join(error)))
     self.emails = '\n'.join(valid)
Beispiel #22
0
    def find_or_create(self, email):
        """ Find a partner with the given ``email`` or use :py:method:`~.name_create`
            to create one

            :param str email: email-like string, which should contain at least one email,
                e.g. ``"Raoul Grosbedon <*****@*****.**>"``"""
        assert email, 'an email is required for find_or_create to work'
        emails = tools.email_split(email)
        name_emails = tools.email_split_and_format(email)
        if emails:
            email = emails[0]
            name_email = name_emails[0]
        else:
            name_email = email
        partners = self.search([('email', '=ilike', email)], limit=1)
        return partners.id or self.name_create(name_email)[0]
 def _onchange_emails(self):
     if self.emails and (self.survey_users_login_required and not self.survey_id.users_can_signup):
         raise UserError(_('This survey does not allow external people to participate. You should create user accounts or update survey access mode accordingly.'))
     if not self.emails:
         return
     valid, error = [], []
     emails = list(set(emails_split.split(self.emails or "")))
     for email in emails:
         email_check = tools.email_split_and_format(email)
         if not email_check:
             error.append(email)
         else:
             valid.extend(email_check)
     if error:
         raise UserError(_("Some emails you just entered are incorrect: %s") % (', '.join(error)))
     self.emails = '\n'.join(valid)
 def _get_recipients_from_component(self):
     """
     Computes all formio.components specified in the mail_recipients_formio_component_ids field.
     :return array: With mail recipients in a dictionary.
     """
     values = []
     result = []
     components = self.builder_id.mail_recipients_formio_component_ids
     for comp in components:
         if comp.key not in self._formio.input_components.keys():
             continue
         comp_obj = self._formio.input_components[comp.key]
         values.extend(self.builder_id._get_component_mail(comp_obj))
     for v in values:
         mail = tools.email_split_and_format(v)
         if mail:
             result.append({'recipient': mail[0]})
     return result
Beispiel #25
0
    def _send_prepare_values(self, partner=None):
        """Return a dictionary for specific email values, depending on a
        partner, or generic to the whole recipients given by mail.email_to.

            :param Model partner: specific recipient partner
        """
        self.ensure_one()
        body = self._send_prepare_body()
        body_alternative = tools.html2plaintext(body)
        if partner:
            email_to = [formataddr((partner.name or 'False', partner.email or 'False'))]
        else:
            email_to = tools.email_split_and_format(self.email_to)
        res = {
            'body': body,
            'body_alternative': body_alternative,
            'email_to': email_to,
        }
        return res
Beispiel #26
0
 def message_get_suggested_recipients(self):
     res = super().message_get_suggested_recipients()
     ResPartnerObj = self.env['res.partner']
     for record in self:
         messages = record.message_ids.filtered('email_cc')
         for msg in messages:
             email_cc_list = email_split_and_format(msg.email_cc)
             for cc in email_cc_list:
                 email_parts = getaddresses([cc])[0]
                 partner_id = record.message_partner_info_from_emails(
                     [email_parts[1]])[0].get('partner_id')
                 if not partner_id:
                     res[record.id].append((False, cc, _('Cc')))
                 else:
                     partner = ResPartnerObj.browse(partner_id,
                                                    self._prefetch)
                     record._message_add_suggested_recipient(
                         res, partner=partner, reason=_('Cc'))
     return res
    def message_parse(self, message, save_original=False):
        """
        解析表示RFC-2822电子邮件的 email.message.message,并返回包含消息详细信息的通用dict。

        :param message: email to parse
        :type message: email.message.Message
        :param bool save_original: whether the returned dict should include
            an ``original`` attachment containing the source of the message
        :rtype: dict
        :return: A dict with the following structure, where each field may not
            be present if missing in original message::

            { 'message_id': msg_id,
              'subject': subject,
              'email_from': from,
              'to': to + delivered-to,
              'cc': cc,
              'recipients': delivered-to + to + cc + resent-to + resent-cc,
              'partner_ids': partners found based on recipients emails,
              'body': unified_body,
              'references': references,
              'in_reply_to': in-reply-to,
              'parent_id': parent mail.message based on in_reply_to or references,
              'is_internal': answer to an internal message (note),
              'date': date,
              'attachments': [('file1', 'bytes'),
                              ('file2', 'bytes')}
        """
        if not isinstance(message, EmailMessage):
            raise ValueError(
                _("Message should be a valid EmailMessage instance"))
        msg_dict = {"message_type": "email"}

        message_id = message.get("Message-Id")
        if not message_id:
            # 非常不寻常的情况,就是我们在这里应该容错
            message_id = "<%s@localhost>" % time.time()
            _logger.debug(
                "Parsing Message without message-id, generating a random one: %s",
                message_id,
            )
        msg_dict["message_id"] = message_id.strip()

        if message.get("Subject"):
            msg_dict["subject"] = tools.decode_message_header(
                message, "Subject")

        email_from = tools.decode_message_header(message, "From")
        email_cc = tools.decode_message_header(message, "cc")
        email_from_list = tools.email_split_and_format(email_from)
        email_cc_list = tools.email_split_and_format(email_cc)
        msg_dict["email_from"] = email_from_list[
            0] if email_from_list else email_from
        msg_dict["from"] = msg_dict[
            "email_from"]  # compatibility for message_new
        msg_dict["cc"] = ",".join(email_cc_list) if email_cc_list else email_cc
        # Delivered-To is a safe bet in most modern MTAs, but we have to fallback on To + Cc values
        # for all the odd MTAs out there, as there is no standard header for the envelope's `rcpt_to` value.
        msg_dict["recipients"] = ",".join(
            set(formatted_email for address in [
                tools.decode_message_header(message, "Delivered-To"),
                tools.decode_message_header(message, "To"),
                tools.decode_message_header(message, "Cc"),
                tools.decode_message_header(message, "Resent-To"),
                tools.decode_message_header(message, "Resent-Cc"),
            ] if address
                for formatted_email in tools.email_split_and_format(address)))
        msg_dict["to"] = ",".join(
            set(formatted_email for address in [
                tools.decode_message_header(message, "Delivered-To"),
                tools.decode_message_header(message, "To"),
            ] if address
                for formatted_email in tools.email_split_and_format(address)))
        partner_ids = [
            x.id for x in self._mail_find_partner_from_emails(
                tools.email_split(msg_dict["recipients"]), records=self) if x
        ]
        msg_dict["partner_ids"] = partner_ids
        # compute references to find if email_message is a reply to an existing thread
        msg_dict["references"] = tools.decode_message_header(
            message, "References")
        msg_dict["in_reply_to"] = tools.decode_message_header(
            message, "In-Reply-To").strip()

        if message.get("Date"):
            try:
                date_hdr = tools.decode_message_header(message, "Date")
                parsed_date = dateutil.parser.parse(date_hdr, fuzzy=True)
                if parsed_date.utcoffset() is None:
                    # naive datetime, so we arbitrarily decide to make it
                    # UTC, there's no better choice. Should not happen,
                    # as RFC2822 requires timezone offset in Date headers.
                    stored_date = parsed_date.replace(tzinfo=pytz.utc)
                else:
                    stored_date = parsed_date.astimezone(tz=pytz.utc)
            except Exception:
                _logger.info(
                    "Failed to parse Date header %r in incoming mail "
                    "with message-id %r, assuming current date/time.",
                    message.get("Date"),
                    message_id,
                )
                stored_date = datetime.datetime.now()
            msg_dict["date"] = stored_date.strftime(
                tools.DEFAULT_SERVER_DATETIME_FORMAT)

        parent_ids = False
        if msg_dict["in_reply_to"]:
            parent_ids = self.env["mail.message"].search(
                [("message_id", "=", msg_dict["in_reply_to"])], limit=1)
        if msg_dict["references"] and not parent_ids:
            references_msg_id_list = tools.mail_header_msgid_re.findall(
                msg_dict["references"])
            parent_ids = self.env["mail.message"].search(
                [("message_id", "in",
                  [x.strip() for x in references_msg_id_list])],
                limit=1,
            )
        if parent_ids:
            msg_dict["parent_id"] = parent_ids.id
            msg_dict["is_internal"] = (parent_ids.subtype_id
                                       and parent_ids.subtype_id.internal
                                       or False)

        msg_dict.update(
            self._message_parse_extract_payload(message,
                                                save_original=save_original))
        msg_dict.update(self._message_parse_extract_bounce(message, msg_dict))
        return msg_dict
Beispiel #28
0
    def _send(self,
              auto_commit=False,
              raise_exception=False,
              smtp_session=None):
        IrMailServer = self.env['ir.mail_server']
        for mail_id in self.ids:
            try:
                mail = self.browse(mail_id)
                if mail.state != 'outgoing':
                    if mail.state != 'exception' and mail.auto_delete:
                        mail.sudo().unlink()
                    continue
                # TDE note: remove me when model_id field is present on mail.message - done here to avoid doing it multiple times in the sub method
                if mail.model:
                    model = self.env['ir.model']._get(mail.model)[0]
                else:
                    model = None
                if model:
                    mail = mail.with_context(model_name=model.name)

                # load attachment binary data with a separate read(), as prefetching all
                # `datas` (binary field) could bloat the browse cache, triggerring
                # soft/hard mem limits with temporary data.
                attachments = [(a['datas_fname'], base64.b64decode(a['datas']),
                                a['mimetype'])
                               for a in mail.attachment_ids.sudo().read(
                                   ['datas_fname', 'datas', 'mimetype'])]

                # specific behavior to customize the send email for notified partners
                email_list = []
                if mail.email_to:
                    email_list.append(mail.send_get_email_dict())
                for partner in mail.recipient_ids:
                    email_list.append(
                        mail.send_get_email_dict(partner=partner))

                if mail.cc_visible:
                    email_cc_list = []
                    for partner_cc in mail.recipient_cc_ids:
                        email_to = formataddr(
                            (partner_cc.name or 'False', partner_cc.email
                             or 'False'))
                        email_cc_list.append(email_to)
                    # Convert Email List To String For BCC & CC
                    email_cc_string = ','.join(email_cc_list)
                else:
                    email_cc_string = ''

                if mail.bcc_visible:
                    email_bcc_list = []
                    for partner_bcc in mail.recipient_bcc_ids:
                        email_to = formataddr(
                            (partner_bcc.name or 'False', partner_bcc.email
                             or 'False'))
                        email_bcc_list.append(email_to)
                    # Convert Email List To String For BCC & CC
                    email_bcc_string = ','.join(email_bcc_list)
                else:
                    email_bcc_string = ''

                # headers
                headers = {}
                ICP = self.env['ir.config_parameter'].sudo()
                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:
                        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)
                if mail.headers:
                    try:
                        headers.update(safe_eval(mail.headers))
                    except Exception:
                        pass

                # Writing on the mail object may fail (e.g. lock on user) which
                # would trigger a rollback *after* actually sending the email.
                # To avoid sending twice the same email, provoke the failure earlier
                mail.write({
                    'state':
                    'exception',
                    'failure_reason':
                    _('Error without exception. Probably due do sending an email without computed recipients.'
                      ),
                })
                mail_sent = False

                # Update notification in a transient exception state to avoid concurrent
                # update in case an email bounces while sending all emails related to current
                # mail record.
                notifs = self.env['mail.notification'].search([
                    ('is_email', '=', True),
                    ('mail_message_id', 'in',
                     mail.mapped('mail_message_id').ids),
                    ('res_partner_id', 'in', mail.mapped('recipient_ids').ids),
                    ('email_status', 'not in', ('sent', 'canceled'))
                ])
                if notifs:
                    notifs.sudo().write({
                        'email_status': 'exception',
                    })

                # build an RFC2822 email.message.Message object and send it without queuing
                res = None
                for email in email_list:
                    msg = IrMailServer.build_email(
                        email_from=mail.email_from,
                        email_to=email.get('email_to'),
                        subject=mail.subject,
                        body=email.get('body'),
                        body_alternative=email.get('body_alternative'),
                        email_cc=tools.email_split_and_format(email_cc_string),
                        email_bcc=tools.email_split_and_format(
                            email_bcc_string),
                        reply_to=mail.reply_to,
                        attachments=attachments,
                        message_id=mail.message_id,
                        references=mail.references,
                        object_id=mail.res_id
                        and ('%s-%s' % (mail.res_id, mail.model)),
                        subtype='html',
                        subtype_alternative='plain',
                        headers=headers)
                    try:
                        res = IrMailServer.send_email(
                            msg,
                            mail_server_id=mail.mail_server_id.id,
                            smtp_session=smtp_session)
                    except AssertionError as error:
                        if str(error) == IrMailServer.NO_VALID_RECIPIENT:
                            # No valid recipient found for this particular
                            # mail item -> ignore error to avoid blocking
                            # delivery to next recipients, if any. If this is
                            # the only recipient, the mail will show as failed.
                            _logger.info(
                                "Ignoring invalid recipients for mail.mail %s: %s",
                                mail.message_id, email.get('email_to'))
                        else:
                            raise
                if res:
                    mail.write({
                        'state': 'sent',
                        'message_id': res,
                        'failure_reason': False
                    })
                    mail_sent = True

                # /!\ can't use mail.state here, as mail.refresh() will cause an error
                # see revid:[email protected] in 6.1
                if mail_sent:
                    _logger.info(
                        'Mail with ID %r and Message-Id %r successfully sent',
                        mail.id, mail.message_id)
                mail._postprocess_sent_message(mail_sent=mail_sent)
            except MemoryError:
                # prevent catching transient MemoryErrors, bubble up to notify user or abort cron job
                # instead of marking the mail as failed
                _logger.exception(
                    'MemoryError while processing mail with ID %r and Msg-Id %r. Consider raising the --limit-memory-hard startup option',
                    mail.id, mail.message_id)
                raise
            except (psycopg2.Error, smtplib.SMTPServerDisconnected):
                # If an error with the database or SMTP session occurs, chances are that the cursor
                # or SMTP session are unusable, causing further errors when trying to save the state.
                _logger.exception(
                    'Exception while processing mail with ID %r and Msg-Id %r.',
                    mail.id, mail.message_id)
                raise
            except Exception as e:
                failure_reason = tools.ustr(e)
                _logger.exception('failed sending mail (id: %s) due to %s',
                                  mail.id, failure_reason)
                mail.write({
                    'state': 'exception',
                    'failure_reason': failure_reason
                })
                mail._postprocess_sent_message(mail_sent=False)
                if raise_exception:
                    if isinstance(e, AssertionError):
                        # get the args of the original error, wrap into a value and throw a MailDeliveryException
                        # that is an except_orm, with name and value as arguments
                        value = '. '.join(e.args)
                        raise MailDeliveryException(_("Mail Delivery Failed"),
                                                    value)
                    raise

            if auto_commit is True:
                self._cr.commit()
        return True
    def _send(self,
              auto_commit=False,
              raise_exception=False,
              smtp_session=None):
        IrMailServer = self.env['ir.mail_server']
        IrAttachment = self.env['ir.attachment']
        for mail_id in self.ids:
            success_pids = []
            failure_type = None
            processing_pid = None
            mail = None
            try:
                mail = self.browse(mail_id)
                if mail.state != 'outgoing':
                    if mail.state != 'exception' and mail.auto_delete:
                        mail.sudo().unlink()
                    continue

                # remove attachments if user send the link with the access_token
                body = mail.body_html or ''
                attachments = mail.attachment_ids
                for link in re.findall(r'/web/(?:content|image)/([0-9]+)',
                                       body):
                    attachments = attachments - IrAttachment.browse(int(link))

                # load attachment binary data with a separate read(), as prefetching all
                # `datas` (binary field) could bloat the browse cache, triggerring
                # soft/hard mem limits with temporary data.
                attachments = [(a['datas_fname'], base64.b64decode(a['datas']),
                                a['mimetype'])
                               for a in attachments.sudo().read(
                                   ['datas_fname', 'datas', 'mimetype'])]

                # specific behavior to customize the send email for notified partners
                email_list = []
                if mail.email_to:
                    email_list.append(mail._send_prepare_values())
                for partner in mail.recipient_ids:
                    values = mail._send_prepare_values(partner=partner)
                    values['partner_id'] = partner
                    email_list.append(values)

                if mail.cc_visible:
                    email_cc_list = []
                    for partner_cc in mail.recipient_cc_ids:
                        email_to = formataddr(
                            (partner_cc.name or 'False', partner_cc.email
                             or 'False'))
                        email_cc_list.append(email_to)
                    # Convert Email List To String For BCC & CC
                    email_cc_string = ','.join(email_cc_list)
                else:
                    email_cc_string = ''

                if mail.bcc_visible:
                    email_bcc_list = []
                    for partner_bcc in mail.recipient_bcc_ids:
                        email_to = formataddr(
                            (partner_bcc.name or 'False', partner_bcc.email
                             or 'False'))
                        email_bcc_list.append(email_to)
                    # Convert Email List To String For BCC & CC
                    email_bcc_string = ','.join(email_bcc_list)
                else:
                    email_bcc_string = ''

                # headers
                headers = {}
                ICP = self.env['ir.config_parameter'].sudo()
                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:
                        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)
                if mail.headers:
                    try:
                        headers.update(safe_eval(mail.headers))
                    except Exception:
                        pass

                # Writing on the mail object may fail (e.g. lock on user) which
                # would trigger a rollback *after* actually sending the email.
                # To avoid sending twice the same email, provoke the failure earlier
                mail.write({
                    'state':
                    'exception',
                    'failure_reason':
                    _('Error without exception. Probably due do sending an email without computed recipients.'
                      ),
                })
                # Update notification in a transient exception state to avoid concurrent
                # update in case an email bounces while sending all emails related to current
                # mail record.
                notifs = self.env['mail.notification'].search([
                    ('is_email', '=', True), ('mail_id', 'in', mail.ids),
                    ('email_status', 'not in', ('sent', 'canceled'))
                ])
                if notifs:
                    notif_msg = _(
                        'Error without exception. Probably due do concurrent access update of notification records. Please see with an administrator.'
                    )
                    notifs.write({
                        'email_status': 'exception',
                        'failure_type': 'UNKNOWN',
                        'failure_reason': notif_msg,
                    })

                # build an RFC2822 email.message.Message object and send it without queuing

                res = None
                for email in email_list:
                    msg = IrMailServer.build_email(
                        email_from=mail.email_from,
                        email_to=email.get('email_to'),
                        subject=mail.subject,
                        body=email.get('body'),
                        body_alternative=email.get('body_alternative'),
                        email_cc=tools.email_split_and_format(email_cc_string),
                        email_bcc=tools.email_split_and_format(
                            email_bcc_string),
                        reply_to=mail.reply_to,
                        attachments=attachments,
                        message_id=mail.message_id,
                        references=mail.references,
                        object_id=mail.res_id
                        and ('%s-%s' % (mail.res_id, mail.model)),
                        subtype='html',
                        subtype_alternative='plain',
                        headers=headers)
                    processing_pid = email.pop("partner_id", None)
                    try:
                        res = IrMailServer.send_email(
                            msg,
                            mail_server_id=mail.mail_server_id.id,
                            smtp_session=smtp_session)
                        if processing_pid:
                            success_pids.append(processing_pid)
                        processing_pid = None
                    except AssertionError as error:
                        if str(error) == IrMailServer.NO_VALID_RECIPIENT:
                            failure_type = "RECIPIENT"
                            # No valid recipient found for this particular
                            # mail item -> ignore error to avoid blocking
                            # delivery to next recipients, if any. If this is
                            # the only recipient, the mail will show as failed.
                            _logger.info(
                                "Ignoring invalid recipients for mail.mail %s: %s",
                                mail.message_id, email.get('email_to'))
                        else:
                            raise
                if res:  # mail has been sent at least once, no major exception occured
                    mail.write({
                        'state': 'sent',
                        'message_id': res,
                        'failure_reason': False
                    })
                    _logger.info(
                        'Mail with ID %r and Message-Id %r successfully sent',
                        mail.id, mail.message_id)
                    # /!\ can't use mail.state here, as mail.refresh() will cause an error
                    # see revid:[email protected] in 6.1
                mail._postprocess_sent_message(success_pids=success_pids,
                                               failure_type=failure_type)
            except MemoryError:
                # prevent catching transient MemoryErrors, bubble up to notify user or abort cron job
                # instead of marking the mail as failed
                _logger.exception(
                    'MemoryError while processing mail with ID %r and Msg-Id %r. Consider raising the --limit-memory-hard startup option',
                    mail.id, mail.message_id)
                # mail status will stay on ongoing since transaction will be rollback
                raise
            except psycopg2.Error:
                # If an error with the database occurs, chances are that the cursor is unusable.
                # This will lead to an `psycopg2.InternalError` being raised when trying to write
                # `state`, shadowing the original exception and forbid a retry on concurrent
                # update. Let's bubble it.
                raise
            except Exception as e:
                failure_reason = tools.ustr(e)
                _logger.exception('failed sending mail (id: %s) due to %s',
                                  mail.id, failure_reason)
                mail.write({
                    'state': 'exception',
                    'failure_reason': failure_reason
                })
                mail._postprocess_sent_message(success_pids=success_pids,
                                               failure_reason=failure_reason,
                                               failure_type='UNKNOWN')
                if raise_exception:
                    if isinstance(e, (AssertionError, UnicodeEncodeError)):
                        if isinstance(e, UnicodeEncodeError):
                            value = "Invalid text: %s" % e.object
                        else:
                            # get the args of the original error, wrap into a value and throw a MailDeliveryException
                            # that is an except_orm, with name and value as arguments
                            value = '. '.join(e.args)
                        raise MailDeliveryException(_("Mail Delivery Failed"),
                                                    value)
                    raise

            if auto_commit is True:
                self._cr.commit()
        return True
Beispiel #30
0
 def test_message_cc_new(self):
     record = self.format_and_process(MAIL_TEMPLATE, self.email_from, '*****@*****.**',
                                      cc='[email protected], [email protected]', target_model='mail.test.cc')
     cc = email_split_and_format(record.email_cc)
     self.assertEqual(sorted(cc), ['*****@*****.**', '*****@*****.**'])