Пример #1
0
    def generate_recipients(self, results, res_ids):
        """Generates the recipients of the template. Default values can ben generated
        instead of the template values if requested by template or context.
        Emails (email_to, email_cc) can be transformed into partners if requested
        in the context. """
        self.ensure_one()

        if self.use_default_to or self._context.get('tpl_force_default_to'):
            default_recipients = self.env[
                'mail.thread'].message_get_default_recipients(
                    res_model=self.model, res_ids=res_ids)
            for res_id, recipients in default_recipients.items():
                results[res_id].pop('partner_to', None)
                results[res_id].update(recipients)

        for res_id, values in results.items():
            partner_ids = values.get('partner_ids', list())
            if self._context.get('tpl_partners_only'):
                mails = tools.email_split(values.pop(
                    'email_to', '')) + tools.email_split(
                        values.pop('email_cc', ''))
                for mail in mails:
                    partner_id = self.env['res.partner'].find_or_create(mail)
                    partner_ids.append(partner_id)
            partner_to = values.pop('partner_to', '')
            if partner_to:
                # placeholders could generate '', 3, 2 due to some empty field values
                tpl_partner_ids = [
                    int(pid) for pid in partner_to.split(',') if pid
                ]
                partner_ids += self.env['res.partner'].sudo().browse(
                    tpl_partner_ids).exists().ids
            results[res_id]['partner_ids'] = partner_ids
        return results
Пример #2
0
    def generate_recipients(self, results, res_ids):
        """Generates the recipients of the template. Default values can ben generated
        instead of the template values if requested by template or context.
        Emails (email_to, email_cc) can be transformed into partners if requested
        in the context. """
        self.ensure_one()

        if self.use_default_to or self._context.get('tpl_force_default_to'):
            records = self.env[self.model].browse(res_ids).sudo()
            default_recipients = records._message_get_default_recipients()
            for res_id, recipients in default_recipients.items():
                results[res_id].pop('partner_to', None)
                results[res_id].update(recipients)

        records_company = None
        if self._context.get(
                'tpl_partners_only'
        ) and self.model and results and 'company_id' in self.env[
                self.model]._fields:
            records = self.env[self.model].browse(results.keys()).read(
                ['company_id'])
            records_company = {
                rec['id']:
                (rec['company_id'][0] if rec['company_id'] else None)
                for rec in records
            }

        for res_id, values in results.items():
            partner_ids = values.get('partner_ids', list())
            if self._context.get('tpl_partners_only'):
                mails = tools.email_split(values.pop(
                    'email_to', '')) + tools.email_split(
                        values.pop('email_cc', ''))
                Partner = self.env['res.partner']
                if records_company:
                    Partner = Partner.with_context(
                        default_company_id=records_company[res_id])
                for mail in mails:
                    partner = Partner.find_or_create(mail)
                    partner_ids.append(partner.id)
            partner_to = values.pop('partner_to', '')
            if partner_to:
                # placeholders could generate '', 3, 2 due to some empty field values
                tpl_partner_ids = [
                    int(pid) for pid in partner_to.split(',') if pid
                ]
                partner_ids += self.env['res.partner'].sudo().browse(
                    tpl_partner_ids).exists().ids
            results[res_id]['partner_ids'] = partner_ids
        return results
Пример #3
0
 def message_new(self, msg, custom_values=None):
     """ Overrides mail_thread message_new that is called by the mailgateway
         through message_process.
         This override updates the document according to the email.
     """
     if custom_values is None:
         custom_values = {}
     email = tools.email_split(msg.get('from')) and tools.email_split(msg.get('from'))[0] or False
     user = self.env['res.users'].search([('login', '=', email)], limit=1)
     if user:
         employee = self.env['hr.employee'].search([('user_id', '=', user.id)], limit=1)
         if employee:
             custom_values['employee_id'] = employee and employee[0].id
     return super(MaintenanceRequest, self).message_new(msg, custom_values=custom_values)
Пример #4
0
    def message_route(self,
                      message,
                      message_dict,
                      model=None,
                      thread_id=None,
                      custom_values=None):
        """ Override to udpate mass mailing statistics based on bounce emails """
        bounce_alias = self.env['ir.config_parameter'].sudo().get_param(
            "mail.bounce.alias")
        email_to = decode_message_header(message, 'To')
        email_to_localpart = (tools.email_split(email_to)
                              or [''])[0].split('@', 1)[0].lower()

        if bounce_alias and bounce_alias in email_to_localpart:
            bounce_re = re.compile(
                "%s\+(\d+)-?([\w.]+)?-?(\d+)?" % re.escape(bounce_alias),
                re.UNICODE)
            bounce_match = bounce_re.search(email_to)
            if bounce_match:
                bounced_mail_id = bounce_match.group(1)
                self.env['mail.mail.statistics'].set_bounced(
                    mail_mail_ids=[bounced_mail_id])

        return super(MailThread,
                     self).message_route(message, message_dict, model,
                                         thread_id, custom_values)
Пример #5
0
    def _parse_partner_name(self, text):
        """ Parse partner name (given by text) in order to find a name and an
        email. Supported syntax:

          * Raoul <*****@*****.**>
          * "Raoul le Grand" <*****@*****.**>
          * Raoul [email protected] (strange fault tolerant support from df40926d2a57c101a3e2d221ecfd08fbb4fea30e)

        Otherwise: default, everything is set as the name. Starting from 13.3
        returned email will be normalized to have a coherent encoding.
         """
        name, email = '', ''
        split_results = tools.email_split_tuples(text)
        if split_results:
            name, email = split_results[0]

        if email and not name:
            fallback_emails = tools.email_split(text.replace(' ', ','))
            if fallback_emails:
                email = fallback_emails[0]
                name = text[:text.index(email)].replace('"', '').replace('<', '').strip()

        if email:
            email = tools.email_normalize(email)
        else:
            name, email = text, ''

        return name, email
Пример #6
0
 def get_valid_email(self, msg):
     emails_list = []
     valid_email = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or ''))
     team_aliases = self.mapped('team_id.alias_name')
     for eml in valid_email:
         if eml.split('@')[0] not in team_aliases:
             emails_list+= [eml]
     return emails_list
Пример #7
0
    def message_new(self, msg_dict, custom_values=None):
        if custom_values is None:
            custom_values = {}

        email_address = email_split(msg_dict.get('email_from', False))[0]

        employee = self.env['hr.employee'].search([
            '|',
            ('work_email', 'ilike', email_address),
            ('user_id.email', 'ilike', email_address)
        ], limit=1)

        expense_description = msg_dict.get('subject', '')

        # Match the first occurence of '[]' in the string and extract the content inside it
        # Example: '[foo] bar (baz)' becomes 'foo'. This is potentially the product code
        # of the product to encode on the expense. If not, take the default product instead
        # which is 'Fixed Cost'
        default_product = self.env.ref('hr_expense.product_product_fixed_cost')
        pattern = '\[([^)]*)\]'
        product_code = re.search(pattern, expense_description)
        if product_code is None:
            product = default_product
        else:
            expense_description = expense_description.replace(product_code.group(), '')
            products = self.env['product.product'].search([('default_code', 'ilike', product_code.group(1))]) or default_product
            product = products.filtered(lambda p: p.default_code == product_code.group(1)) or products[0]
        account = product.product_tmpl_id._get_product_accounts()['expense']

        pattern = '[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?'
        # Match the last occurence of a float in the string
        # Example: '[foo] 50.3 bar 34.5' becomes '34.5'. This is potentially the price
        # to encode on the expense. If not, take 1.0 instead
        expense_price = re.findall(pattern, expense_description)
        # TODO: International formatting
        if not expense_price:
            price = 1.0
        else:
            price = expense_price[-1][0]
            expense_description = expense_description.replace(price, '')
            try:
                price = float(price)
            except ValueError:
                price = 1.0

        custom_values.update({
            'name': expense_description.strip(),
            'employee_id': employee.id,
            'product_id': product.id,
            'product_uom_id': product.uom_id.id,
            'tax_ids': [(4, tax.id, False) for tax in product.supplier_taxes_id],
            'quantity': 1,
            'unit_amount': price,
            'company_id': employee.company_id.id,
        })
        if account:
            custom_values['account_id'] = account.id
        return super(HrExpense, self).message_new(msg_dict, custom_values)
Пример #8
0
 def test_email_split(self):
     cases = [
         ("John <*****@*****.**>", ['*****@*****.**']),  # regular form
         ("d@x; 1@2", ['d@x', '1@2']),  # semi-colon + extra space
         ("'(ss)' <*****@*****.**>, 'foo' <foo@bar>", ['*****@*****.**', 'foo@bar']),  # comma + single-quoting
         ('"*****@*****.**"<*****@*****.**>', ['*****@*****.**']),  # double-quoting
         ('"<jg>" <*****@*****.**>', ['*****@*****.**']),  # double-quoting with brackets
     ]
     for text, expected in cases:
         self.assertEqual(email_split(text), expected, 'email_split is broken')
Пример #9
0
 def _parse_partner_name(self, text, context=None):
     """ Supported syntax:
         - 'Raoul <*****@*****.**>': will find name and email address
         - otherwise: default, everything is set as the name """
     emails = tools.email_split(text.replace(' ', ','))
     if emails:
         email = emails[0]
         name = text[:text.index(email)].replace('"', '').replace('<', '').strip()
     else:
         name, email = text, ''
     return name, email
Пример #10
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)
        if emails:
            email = emails[0]
        partners = self.search([('email', '=ilike', email)], limit=1)
        return partners.id or self.name_create(email)[0]
Пример #11
0
 def _alias_check_contact(self, message, message_dict, alias):
     if alias.alias_contact == 'employees' and self.ids:
         email_from = tools.decode_message_header(message, 'From')
         email_address = tools.email_split(email_from)[0]
         employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1)
         if not employee:
             employee = self.env['hr.employee'].search([('user_id.email', 'ilike', email_address)], limit=1)
         if not employee:
             return {
                 'error_message': 'restricted to employees',
                 'error_template': self.env.ref('hr.mail_template_data_unknown_employee_email_address').body_html,
             }
         return True
     return super(MailAlias, self)._alias_check_contact(message, message_dict, alias)
Пример #12
0
 def send_get_email_dict(self, partner=None):
     # TDE: temporary addition (mail was parameter) due to semi-new-API
     res = super(MailMail, self).send_get_email_dict(partner)
     base_url = self.env['ir.config_parameter'].sudo().get_param(
         'web.base.url')
     if self.mailing_id and res.get('body') and res.get('email_to'):
         emails = tools.email_split(res.get('email_to')[0])
         email_to = emails and emails[0] or False
         unsubscribe_url = self._get_unsubscribe_url(email_to)
         link_to_replace = base_url + '/unsubscribe_from_list'
         if link_to_replace in res['body']:
             res['body'] = res['body'].replace(
                 link_to_replace,
                 unsubscribe_url if unsubscribe_url else '#')
     return res
Пример #13
0
 def _alias_get_error_message(self, message, message_dict, alias):
     if alias.alias_contact == 'employees':
         email_from = tools.decode_message_header(message, 'From')
         email_address = tools.email_split(email_from)[0]
         employee = self.env['hr.employee'].search(
             [('work_email', 'ilike', email_address)], limit=1)
         if not employee:
             employee = self.env['hr.employee'].search(
                 [('user_id.email', 'ilike', email_address)], limit=1)
         if not employee:
             return _('restricted to employees')
         return False
     return super(BaseModel,
                  self)._alias_get_error_message(message, message_dict,
                                                 alias)
Пример #14
0
    def _send_prepare_values(self, partner=None):
        # TDE: temporary addition (mail was parameter) due to semi-new-API
        res = super(MailMail, self)._send_prepare_values(partner)
        base_url = self.env['ir.config_parameter'].sudo().get_param(
            'web.base.url').rstrip('/')
        if self.mailing_id and res.get('body') and res.get('email_to'):
            emails = tools.email_split(res.get('email_to')[0])
            email_to = emails and emails[0] or False

            urls_to_replace = [
                (base_url + '/unsubscribe_from_list',
                 self.mailing_id._get_unsubscribe_url(email_to, self.res_id)),
                (base_url + '/view',
                 self.mailing_id._get_view_url(email_to, self.res_id))
            ]

            for url_to_replace, new_url in urls_to_replace:
                if url_to_replace in res['body']:
                    res['body'] = res['body'].replace(
                        url_to_replace, new_url if new_url else '#')
        return res
Пример #15
0
 def send_mail_test(self):
     self.ensure_one()
     mails = self.env['mail.mail']
     mailing = self.mass_mailing_id
     test_emails = tools.email_split(self.email_to)
     for test_mail in test_emails:
         # Convert links in absolute URLs before the application of the shortener
         mailing.write({
             'body_html':
             self.env['mail.template']._replace_local_links(
                 mailing.body_html)
         })
         mail_values = {
             'email_from':
             mailing.email_from,
             'reply_to':
             mailing.reply_to,
             'email_to':
             test_mail,
             'subject':
             mailing.name,
             'body_html':
             tools.html_sanitize(mailing.body_html,
                                 sanitize_attributes=True,
                                 sanitize_style=True,
                                 strip_classes=True),
             'notification':
             True,
             'mailing_id':
             mailing.id,
             'attachment_ids':
             [(4, attachment.id) for attachment in mailing.attachment_ids],
             'auto_delete':
             True,
         }
         mail = self.env['mail.mail'].create(mail_values)
         mails |= mail
     mails.send()
     return True
Пример #16
0
    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['name'], base64.b64decode(a['datas']),
                                a['mimetype'])
                               for a in attachments.sudo().read(
                                   ['name', 'datas', 'mimetype'])
                               if a['datas'] is not False]

                # 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)

                # 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.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)
                if mail.headers:
                    try:
                        headers.update(ast.literal_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([
                    ('notification_type', '=', 'email'),
                    ('mail_id', 'in', mail.ids),
                    ('notification_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.sudo().write({
                        'notification_status': 'exception',
                        'failure_type': 'UNKNOWN',
                        'failure_reason': notif_msg,
                    })
                    # `test_mail_bounce_during_send`, force immediate update to obtain the lock.
                    # see rev. 56596e5240ef920df14d99087451ce6f06ac6d36
                    notifs.flush(fnames=[
                        'notification_status', 'failure_type', 'failure_reason'
                    ],
                                 records=notifs)

                # 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(mail.email_cc),
                        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, 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(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:
                            value = '. '.join(e.args)
                        raise MailDeliveryException(value)
                    raise

            if auto_commit is True:
                self._cr.commit()
        return True
Пример #17
0
    def send_mail_test(self):
        self.ensure_one()
        ctx = dict(self.env.context)
        ctx.pop('default_state', None)
        self = self.with_context(ctx)

        mails_sudo = self.env['mail.mail'].sudo()
        mailing = self.mass_mailing_id
        test_emails = tools.email_split(self.email_to)
        mass_mail_layout = self.env.ref(
            'mass_mailing.mass_mailing_mail_layout')

        record = self.env[mailing.mailing_model_real].search([], limit=1)
        body = mailing._prepend_preview(mailing.body_html, mailing.preview)
        subject = mailing.subject

        # If there is atleast 1 record for the model used in this mailing, then we use this one to render the template
        # Downside: Jinja syntax is only tested when there is atleast one record of the mailing's model
        if record:
            # Returns a proper error if there is a syntax error with jinja
            body = self.env['mail.render.mixin']._render_template(
                body,
                mailing.mailing_model_real,
                record.ids,
                post_process=True)[record.id]
            subject = self.env['mail.render.mixin']._render_template(
                subject, mailing.mailing_model_real, record.ids)[record.id]

        # Convert links in absolute URLs before the application of the shortener
        body = self.env['mail.render.mixin']._replace_local_links(body)
        body = tools.html_sanitize(body,
                                   sanitize_attributes=True,
                                   sanitize_style=True)

        for test_mail in test_emails:
            mail_values = {
                'email_from':
                mailing.email_from,
                'reply_to':
                mailing.reply_to,
                'email_to':
                test_mail,
                'subject':
                subject,
                'body_html':
                mass_mail_layout._render({'body': body},
                                         engine='ir.qweb',
                                         minimal_qcontext=True),
                'notification':
                True,
                'mailing_id':
                mailing.id,
                'attachment_ids':
                [(4, attachment.id) for attachment in mailing.attachment_ids],
                'auto_delete':
                True,
                'mail_server_id':
                mailing.mail_server_id.id,
            }
            mail = self.env['mail.mail'].sudo().create(mail_values)
            mails_sudo |= mail
        mails_sudo.send()
        return True
Пример #18
0
    def generate_data(self, event, isCreating=False):
        if event.allday:
            start_date = event.start_date
            final_date = (datetime.strptime(event.stop_date, tools.DEFAULT_SERVER_DATE_FORMAT) + timedelta(days=1)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
            type = 'date'
            vstype = 'dateTime'
        else:
            start_date = fields.Datetime.context_timestamp(self, fields.Datetime.from_string(event.start)).isoformat('T')
            final_date = fields.Datetime.context_timestamp(self, fields.Datetime.from_string(event.stop)).isoformat('T')
            type = 'dateTime'
            vstype = 'date'
        attendee_list = []
        for attendee in event.attendee_ids:
            email = tools.email_split(attendee.email)
            email = email[0] if email else '*****@*****.**'
            attendee_list.append({
                'email': email,
                'displayName': attendee.partner_id.name,
                'responseStatus': attendee.state or 'needsAction',
            })

        reminders = []
        for alarm in event.alarm_ids:
            reminders.append({
                "method": "email" if alarm.type == "email" else "popup",
                "minutes": alarm.duration_minutes
            })
        data = {
            "summary": event.name or '',
            "description": event.description or '',
            "start": {
                type: start_date,
                vstype: None,
                'timeZone': self.env.context.get('tz') or 'UTC',
            },
            "end": {
                type: final_date,
                vstype: None,
                'timeZone': self.env.context.get('tz') or 'UTC',
            },
            "attendees": attendee_list,
            "reminders": {
                "overrides": reminders,
                "useDefault": "false"
            },
            "location": event.location or '',
            "visibility": event['privacy'] or 'public',
        }
        if event.recurrency and event.rrule:
            data["recurrence"] = ["RRULE:" + event.rrule]

        if not event.active:
            data["state"] = "cancelled"

        if not self.get_need_synchro_attendee():
            data.pop("attendees")
        if isCreating:
            other_google_ids = [other_att.google_internal_event_id for other_att in event.attendee_ids
                                if other_att.google_internal_event_id and not other_att.google_internal_event_id.startswith('_')]
            if other_google_ids:
                data["id"] = other_google_ids[0]
        return data
Пример #19
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 and \
                                    mail.keep_days < 0:
                        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))

                # 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

                # 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(mail.email_cc),
                        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:
                # 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(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 extract_email(email):
    """ extract the email address from a user-friendly email address """
    addresses = email_split(email)
    return addresses[0] if addresses else ''
Пример #21
0
    def get_mail_values(self, res_ids):
        """ Override method that generated the mail content by creating the
        mail.mail.statistics values in the o2m of mail_mail, when doing pure
        email mass mailing. """
        self.ensure_one()
        res = super(MailComposeMessage, self).get_mail_values(res_ids)
        # use only for allowed models in mass mailing
        if self.composition_mode == 'mass_mail' and \
                (self.mass_mailing_name or self.mass_mailing_id) and \
                self.env['ir.model'].sudo().search([('model', '=', self.model), ('is_mail_thread', '=', True)], limit=1):
            mass_mailing = self.mass_mailing_id
            if not mass_mailing:
                reply_to_mode = 'email' if self.no_auto_thread else 'thread'
                reply_to = self.reply_to if self.no_auto_thread else False
                mass_mailing = self.env['mail.mass_mailing'].create({
                        'mass_mailing_campaign_id': self.mass_mailing_campaign_id.id,
                        'name': self.mass_mailing_name,
                        'template_id': self.template_id.id,
                        'state': 'done',
                        'reply_to_mode': reply_to_mode,
                        'reply_to': reply_to,
                        'sent_date': fields.Datetime.now(),
                        'body_html': self.body,
                        'mailing_model_id': self.env['ir.model']._get(self.model).id,
                        'mailing_domain': self.active_domain,
                })

            # Preprocess res.partners to batch-fetch from db
            # if recipient_ids is present, it means they are partners
            # (the only object to fill get_default_recipient this way)
            recipient_partners_ids = []
            read_partners = {}
            for res_id in res_ids:
                mail_values = res[res_id]
                if mail_values.get('recipient_ids'):
                    # recipient_ids is a list of x2m command tuples at this point
                    recipient_partners_ids.append(mail_values.get('recipient_ids')[0][1])
            read_partners = self.env['res.partner'].browse(recipient_partners_ids)

            partners_email = {p.id: p.email for p in read_partners}

            blacklist = self._context.get('mass_mailing_blacklist')
            seen_list = self._context.get('mass_mailing_seen_list')
            for res_id in res_ids:
                mail_values = res[res_id]
                if mail_values.get('email_to'):
                    recips = tools.email_split(mail_values['email_to'])
                else:
                    recips = tools.email_split(partners_email.get(res_id))
                mail_to = recips[0].lower() if recips else False
                if (blacklist and mail_to in blacklist) or (seen_list and mail_to in seen_list):
                    # prevent sending to blocked addresses that were included by mistake
                    mail_values['state'] = 'cancel'
                elif seen_list is not None:
                    seen_list.add(mail_to)
                stat_vals = {
                    'model': self.model,
                    'res_id': res_id,
                    'mass_mailing_id': mass_mailing.id
                }
                # propagate exception state to stat when still-born
                if mail_values.get('state') == 'cancel':
                    stat_vals['exception'] = fields.Datetime.now()
                mail_values.update({
                    'mailing_id': mass_mailing.id,
                    'statistics_ids': [(0, 0, stat_vals)],
                    # email-mode: keep original message for routing
                    'notification': mass_mailing.reply_to_mode == 'thread',
                    'auto_delete': not mass_mailing.keep_archives,
                })
        return res
Пример #22
0
 def email_split(self, msg):
     email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or ''))
     # check left-part is not already an alias
     aliases = self.mapped('project_id.alias_name')
     return [x for x in email_list if x.split('@')[0] not in aliases]