前言
因開源項目“企業微信 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()
四、開始寫代碼
- 繼承和擴展"\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()
- 繼承和擴展"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,
)