前言
因开源项目“企业微信 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,
)