Odoo 模板發送郵件機制分析及功能擴展

前言

因開源項目“企業微信 For Odoo”需要,需要開發出企業微信用戶使用企業微信的“消息推送”功能,改Odoo原使用郵件方式爲企業微信消息推送方式

一、分析 “發送邀請郵件”,“發送重置密碼郵件”功能 源代碼

代碼路徑:addons\auth_signup\models\res_users.py

def action_reset_password(self):
        """ 爲每個用戶創建註冊令牌,並通過電子郵件發送其註冊url """
        if self.env.context.get('install_mode', False):
            return
        if self.filtered(lambda user: not user.active):
            raise UserError(_("您無法對已歸檔的用戶執行此操作."))
        # 準備重置密碼註冊
        create_mode = bool(self.env.context.get('create_user'))

        # 初次邀請沒有時間限制,僅重設密碼
        expiration = False if create_mode else now(days=+1)

        self.mapped('partner_id').signup_prepare(signup_type="reset", expiration=expiration)

        # 通過註冊網址向用戶發送電子郵件
        template = False
        if create_mode:
            try:
                template = self.env.ref('auth_signup.set_password_email', raise_if_not_found=False)
            except ValueError:
                pass
        if not template:
            template = self.env.ref('auth_signup.reset_password_email')
        assert template._name == 'mail.template'

        template_values = {
            'email_to': '${object.email|safe}',
            'email_cc': False,
            'auto_delete': True,
            'partner_to': False,
            'scheduled_date': False,
        }
        template.write(template_values)

        for user in self:
            if not user.email:
                raise UserError(_("無法發送電子郵件:%s用戶沒有電子郵件地址.", user.name))
            # TDE FIXME:使此模板具有技術性(qweb)
            with self.env.cr.savepoint():
                force_send = not(self.env.context.get('import_file', False))
                template.send_mail(user.id, force_send=force_send, raise_exception=True)
            _logger.info("Password reset email sent for user <%s> to <%s>", user.login, user.email)

上面的代碼,我們可以看到 “發送邀請郵件”使用了模板:auth_signup.set_password_email “發送重置密碼郵件”使用了模板:auth_signup.reset_password_email 模板文件路徑:"\addons\auth_signup\data\auth_signup_data.xml" 使用的模型爲:"mail.template" 調用 template.send_mail(user.id, force_send=force_send, raise_exception=True)進行郵件發送

二、分析模型"mail.template"的"send_mail"方法

代碼路徑:addons\mail\models\mail_template.py

def send_mail(self, res_id, force_send=False, raise_exception=False, email_values=None, notif_layout=False):
        """ 生成一個新的mail.mail。 模板在由res_id和來自模板的模型給定的記錄中呈現。
        :param int res_id: 呈現模板的記錄的ID
        :param bool force_send: 立即發送電子郵件; 否則使用郵件隊列(推薦);
        :param dict email_values: 使用這些值更新生成的郵件,以進一步自定義郵件;
        :param str notif_layout: 可選的通知佈局,用於封裝生成的電子郵件;
        :returns: 創建的mail.mail的ID
	"""
        # 僅在訪問相關文檔時才授予對send_mail的訪問權限
        self.ensure_one()
        self._send_check_access([res_id])

        Attachment = self.env['ir.attachment']  # TDE FIXME:應從上下文中刪除default_type

        # 根據值創建一個mail_mail,不帶附件
        values = self.generate_email(res_id, ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'scheduled_date'])
        values['recipient_ids'] = [(4, pid) for pid in values.get('partner_ids', list())]
        values['attachment_ids'] = [(4, aid) for aid in values.get('attachment_ids', list())]
        values.update(email_values or {})
        attachment_ids = values.pop('attachment_ids', [])
        attachments = values.pop('attachments', [])
        # 添加防止無效的email_from的保護措施
        if 'email_from' in values and not values.get('email_from'):
            values.pop('email_from')
        # 封裝體
        if notif_layout and values['body_html']:
            try:
                template = self.env.ref(notif_layout, raise_if_not_found=True)
            except ValueError:
                _logger.warning('QWeb template %s not found when sending template %s. Sending without layouting.' % (notif_layout, self.name))
            else:
                record = self.env[self.model].browse(res_id)
                template_ctx = {
                    'message': self.env['mail.message'].sudo().new(dict(body=values['body_html'], record_name=record.display_name)),
                    'model_description': self.env['ir.model']._get(record._name).display_name,
                    'company': 'company_id' in record and record['company_id'] or self.env.company,
                    'record': record,
                }
                body = template._render(template_ctx, engine='ir.qweb', minimal_qcontext=True)
                values['body_html'] = self.env['mail.render.mixin']._replace_local_links(body)
        mail = self.env['mail.mail'].sudo().create(values)

        # 管理附件
        for attachment in attachments:
            attachment_data = {
                'name': attachment[0],
                'datas': attachment[1],
                'type': 'binary',
                'res_model': 'mail.message',
                'res_id': mail.mail_message_id.id,
            }
            attachment_ids.append((4, Attachment.create(attachment_data).id))
        if attachment_ids:
            mail.write({'attachment_ids': attachment_ids})

        if force_send:
            mail.send(raise_exception=raise_exception)
        return mail.id  # TDE CLEANME:退回郵件+ api.returns?

上面的代碼,我們可以看到 模型"mail.template"的方法"send_mail"中,先使用模型"mail.mail"的"create"方法創建郵件,然後使用模型"mail.mail"的"send"方法發送郵件

三、分析模型"mail.mail"的"send"方法

代碼路徑:addons\mail\models\mail_mail.py

def send(self, auto_commit=False, raise_exception=False):
        """ 
	立即發送選定的電子郵件,而忽略它們的當前狀態(除非已被重新發送,否則不應傳遞已發送的電子郵件)。
	成功發送的電子郵件被標記爲“已發送”,而失敗發送的電子郵件被標記爲“例外”,並且相應的錯誤郵件將輸出到服務器日誌中。
	:param bool auto_commit: 發送每封郵件後是否強制提交郵件狀態(僅用於調度程序處理);
				在正常交易期間,絕對不能爲True(默認值:False)
	:param bool raise_exception: 如果電子郵件發送過程失敗,是否引發異常
	:return: True
        """
        for server_id, batch_ids in self._split_by_server():
            smtp_session = None
            try:
                smtp_session = self.env['ir.mail_server'].connect(mail_server_id=server_id)
            except Exception as exc:
                if raise_exception:
                    # 爲了與mail_mail.send()引發的異常保持一致並向後兼容,將其封裝到Odoo MailDeliveryException中
                    raise MailDeliveryException(_('Unable to connect to SMTP Server'), exc)
                else:
                    batch = self.browse(batch_ids)
                    batch.write({'state': 'exception', 'failure_reason': exc})
                    batch._postprocess_sent_message(success_pids=[], failure_type="SMTP")
            else:
                self.browse(batch_ids)._send(
                    auto_commit=auto_commit,
                    raise_exception=raise_exception,
                    smtp_session=smtp_session)
                _logger.info(
                    'Sent batch %s emails via mail server ID #%s',
                    len(batch_ids), server_id)
            finally:
                if smtp_session:
                    smtp_session.quit()

四、開始寫代碼

  1. 繼承和擴展"\addons\auth_signup\models\res_users.py"中的"action_reset_password"方法:
def action_reset_password(self):
        if self.notification_type == "wxwork":
            # 判斷用戶的通知類型爲企業微信
            # 通過註冊網址向用戶推送企業微信消息
            # 準備重置密碼註冊
            create_mode = bool(self.env.context.get("create_user"))

            template = False
            if create_mode:
                try:
                    template = self.env["wxwork.message.template"].search(
                        [("res_id", "=", "auth_signup.set_password_email"),], limit=1,
                    )
                except ValueError:
                    pass
            if not template:
                template = self.env["wxwork.message.template"].search(
                    [("res_id", "=", "auth_signup.reset_password_email"),], limit=1,
                )
        return super(ResUsers, self).action_reset_password()
  1. 繼承和擴展"addons\mail\models\mail_template.py"中的方法”send_mail“:
def send_mail(
        self,
        res_id,
        force_send=False,
        raise_exception=False,
        email_values=None,
        notif_layout=False,
    ):
        values = self.generate_email(
            res_id,
            [
                "subject",
                "body_html",
                "email_from",
                "email_to",
                "partner_to",
                "email_cc",
                "reply_to",
                "scheduled_date",
            ],
        )
        # res_id 是用戶id
        if self.env["res.users"].browse(res_id).notification_type == "wxwork":
            # 攔截 用戶通知類型爲企業微信的發送方式
            values = self.env["wxwork.message.template"].generate_message(
                res_id,
                [
                    "subject",
                    "body_html",
                    "email_from",
                    "email_to",
                    "partner_to",
                    "email_cc",
                    "reply_to",
                    "scheduled_date",
                ],
            )

        return super(MailTemplate, self).send_mail(
            res_id,
            force_send=False,
            raise_exception=False,
            email_values=None,
            notif_layout=False,
        )
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章