feat: setting email template content (#15974)

* feat: setting email template content

* perf: tempale list

* perf: custom template render to string

* perf: content serialize valid

* perf: Custom msg template base class

* perf: Template content reset

* perf: Update templates config

* perf: Remove useless code

---------

Co-authored-by: wangruidong <940853815@qq.com>
This commit is contained in:
fit2bot
2025-09-10 16:49:52 +08:00
committed by GitHub
parent 231b7287c1
commit 79cabe1b3c
13 changed files with 327 additions and 54 deletions

View File

@@ -5,12 +5,23 @@ from accounts.models import Account
from acls.models import LoginACL, LoginAssetACL
from assets.models import Asset
from audits.models import UserLoginLog
from common.views.template import custom_render_to_string
from notifications.notifications import UserMessage
from users.models import User
class UserLoginReminderMsg(UserMessage):
subject = _('User login reminder')
template_name = 'acls/user_login_reminder.html'
contexts = [
{"name": "city", "label": _('Login city'), "default": "北京"},
{"name": "username", "label": _('User'), "default": "zhangsan"},
{"name": "ip", "label": "IP", "default": "8.8.8.8"},
{"name": "recipient_name", "label": '接收人名称', "default": "zhangsan"},
{"name": "recipient_username", "label": '接收人用户名', "default": "张三"},
{"name": "user_agent", "label": _('User agent'), "default": "Mozilla/5.0"},
{"name": "acl_name", "label": _('ACL name'), "default": "login acl"},
]
def __init__(self, user, user_log: UserLoginLog, acl: LoginACL):
self.user_log = user_log
@@ -23,11 +34,12 @@ class UserLoginReminderMsg(UserMessage):
'ip': user_log.ip,
'city': user_log.city,
'username': user_log.username,
'recipient': self.user,
'acl_name': self.acl_name,
'recipient_name': self.user.name,
'recipient_username': self.user.username,
'user_agent': user_log.user_agent,
}
message = render_to_string('acls/user_login_reminder.html', context)
message = custom_render_to_string(self.template_name, context)
return {
'subject': str(self.subject),
@@ -43,6 +55,18 @@ class UserLoginReminderMsg(UserMessage):
class AssetLoginReminderMsg(UserMessage):
subject = _('User login alert for asset')
template_name = 'acls/asset_login_reminder.html'
contexts = [
{"name": "city", "label": _('Login city'), "default": "北京"},
{"name": "username", "label": _('User'), "default": "zhangsan"},
{"name": "name", "label": _('Name'), "default": "zhangsan"},
{"name": "asset", "label": _('Asset'), "default": "dev server"},
{"name": "recipient_name", "label": '接收人名称', "default": "zhangsan"},
{"name": "recipient_username", "label": '接收人用户名', "default": "张三"},
{"name": "account", "label": _('Account Input username'), "default": "root"},
{"name": "account_name", "label": _('Account name'), "default": "root"},
{"name": "acl_name", "label": _('ACL name'), "default": "login acl"},
]
def __init__(
self, user, asset: Asset, login_user: User,
@@ -51,6 +75,7 @@ class AssetLoginReminderMsg(UserMessage):
):
self.ip = ip
self.asset = asset
self.login_user = login_user
self.account = account
self.acl_name = str(acl)
self.login_user = login_user
@@ -60,7 +85,8 @@ class AssetLoginReminderMsg(UserMessage):
def get_html_msg(self) -> dict:
context = {
'ip': self.ip,
'recipient': self.user,
'recipient_name': self.user.name,
'recipient_username': self.user.username,
'username': self.login_user.username,
'name': self.login_user.name,
'asset': str(self.asset),
@@ -68,7 +94,7 @@ class AssetLoginReminderMsg(UserMessage):
'account_name': self.account.name,
'acl_name': self.acl_name,
}
message = render_to_string('acls/asset_login_reminder.html', context)
message = custom_render_to_string(self.template_name, context)
return {
'subject': str(self.subject),

View File

@@ -1,6 +1,6 @@
{% load i18n %}
<h3>{% trans 'Dear' %}: {{ recipient.name }}[{{ recipient.username }}]</h3>
<h3>{% trans 'Dear' %}: {{ recipient_name }}[{{ recipient_username }}]</h3>
<hr>
<p>{% trans 'We would like to inform you that a user has recently logged:' %}<p>
<p><strong>{% trans 'User details' %}:</strong></p>

View File

@@ -1,14 +1,24 @@
from django.template.loader import render_to_string
from django.utils.translation import gettext as _
from common.utils import get_logger
from common.utils.timezone import local_now_display
from common.views.template import custom_render_to_string
from notifications.notifications import UserMessage
logger = get_logger(__file__)
class DifferentCityLoginMessage(UserMessage):
subject = _('Different city login reminder')
template_name = 'authentication/_msg_different_city.html'
contexts = [
{"name": "city", "label": _('Login city'), "default": "北京"},
{"name": "username", "label": _('User'), "default": "zhangsan"},
{"name": "name", "label": _('Name'), "default": "zhangsan"},
{"name": "ip", "label": "IP", "default": "8.8.8.8"},
{"name": "time", "label": _('Login Date'), "default": "2025-01-01 12:00:00"},
]
def __init__(self, user, ip, city):
self.ip = ip
self.city = city
@@ -16,18 +26,16 @@ class DifferentCityLoginMessage(UserMessage):
def get_html_msg(self) -> dict:
now = local_now_display()
subject = _('Different city login reminder')
context = dict(
subject=subject,
name=self.user.name,
username=self.user.username,
ip=self.ip,
time=now,
city=self.city,
)
message = render_to_string('authentication/_msg_different_city.html', context)
message = custom_render_to_string(self.template_name, context)
return {
'subject': subject,
'subject': self.subject,
'message': message
}
@@ -41,6 +49,16 @@ class DifferentCityLoginMessage(UserMessage):
class OAuthBindMessage(UserMessage):
subject = _('OAuth binding reminder')
template_name = 'authentication/_msg_oauth_bind.html'
contexts = [
{"name": "username", "label": _('User'), "default": "zhangsan"},
{"name": "name", "label": _('Name'), "default": "zhangsan"},
{"name": "ip", "label": "IP", "default": "8.8.8.8"},
{"name": "oauth_name", "label": _('OAuth name'), "default": "WeCom"},
{"name": "oauth_id", "label": _('OAuth ID'), "default": "000001"},
]
def __init__(self, user, ip, oauth_name, oauth_id):
super().__init__(user)
self.ip = ip
@@ -51,7 +69,6 @@ class OAuthBindMessage(UserMessage):
now = local_now_display()
subject = self.oauth_name + ' ' + _('binding reminder')
context = dict(
subject=subject,
name=self.user.name,
username=self.user.username,
ip=self.ip,
@@ -59,7 +76,7 @@ class OAuthBindMessage(UserMessage):
oauth_name=self.oauth_name,
oauth_id=self.oauth_id
)
message = render_to_string('authentication/_msg_oauth_bind.html', context)
message = custom_render_to_string(self.template_name, context)
return {
'subject': subject,
'message': message

View File

@@ -13,5 +13,5 @@
<br>
<p>
{% trans 'This link is valid for 1 hour. After it expires' %}
<a href="{{ forget_password_url }}?email={{ user.email }}">{% trans 'request new one' %}</a>
<a href="{{ forget_password_url }}?email={{ email }}">{% trans 'request new one' %}</a>
</p>

View File

@@ -0,0 +1,43 @@
import logging
import os
from django.conf import settings
from django.template import Context
from django.template import Engine, TemplateSyntaxError
from django.template.loader import render_to_string
from django.utils._os import safe_join
logger = logging.getLogger(__name__)
def safe_render_to_string(template_name, context=None, request=None, using=None):
with open(template_name, encoding="utf-8") as f:
template_code = f.read()
safe_engine = Engine(
debug=False,
libraries={}, # 禁用自定义 tag 库
builtins=[], # 不自动加载内置标签
)
try:
template = safe_engine.from_string(template_code)
except TemplateSyntaxError as e:
logger.error(e)
return template_code
return template.render(Context(context or {}))
def _get_data_template_path(template_name: str):
# 保存到 data/template/<原路径>.html
# 例如 template_name users/_msg_x.html -> data/template/users/_msg_x.html
rel_path = template_name.replace('/', os.sep)
return safe_join(settings.DATA_DIR, 'template', rel_path)
def custom_render_to_string(template_name, context=None, request=None, using=None):
# 如果自定的义模板存在,则使用自定义模板,否则使用系统模板
custom_template = _get_data_template_path(template_name)
if os.path.exists(custom_template):
template = safe_render_to_string(custom_template, context=context, request=request, using=using)
else:
template = render_to_string(template_name, context=context, request=request, using=using)
return template

View File

@@ -1,20 +1,28 @@
import os
from django.template.loader import render_to_string
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.mixins import ListModelMixin, UpdateModelMixin, RetrieveModelMixin
from rest_framework.response import Response
from rest_framework.views import APIView
from common.api import JMSGenericViewSet
from common.permissions import IsValidUser
from common.permissions import OnlySuperUser, IsValidUser
from common.views.template import _get_data_template_path
from notifications.backends import BACKEND
from notifications.models import SystemMsgSubscription, UserMsgSubscription
from notifications.notifications import CustomMsgTemplateBase
from notifications.notifications import system_msgs
from notifications.serializers import (
SystemMsgSubscriptionSerializer, SystemMsgSubscriptionByCategorySerializer,
UserMsgSubscriptionSerializer,
)
from notifications.serializers import TemplateEditSerializer
__all__ = (
'BackendListView', 'SystemMsgSubscriptionViewSet',
'UserMsgSubscriptionViewSet', 'get_all_test_messages'
'UserMsgSubscriptionViewSet', 'get_all_test_messages', 'TemplateViewSet',
)
@@ -130,3 +138,72 @@ def get_all_test_messages(request):
<hr />
""").format(msg_cls.__name__, msg_text)
return HttpResponse(html_data + text_data)
class TemplateViewSet(JMSGenericViewSet):
permission_classes = [OnlySuperUser]
def list(self, request):
result = []
metas = [cls.as_dict() for cls in CustomMsgTemplateBase._registry]
for meta in metas:
item = {
'template_name': meta['template_name'],
'subject': meta.get('subject', ''),
'contexts': meta.get('contexts', []),
'content': None,
'content_error': None,
'source': None,
}
data_path = _get_data_template_path(meta['template_name'])
try:
if os.path.exists(data_path):
with open(data_path, 'r', encoding='utf-8') as f:
item['content'] = f.read()
item['source'] = 'data'
else:
ctx = {x.get('name'): x.get('default') for x in item['contexts']}
try:
rendered = render_to_string(meta['template_name'], ctx)
item['content'] = rendered
item['source'] = 'original'
except Exception as e:
item['content_error'] = str(e)
item['source'] = 'original_error'
except Exception as e:
item['content_error'] = str(e)
result.append(item)
return Response(result)
@action(detail=False, methods=['patch'], url_path='edit', name='edit')
def edit(self, request):
"""保存前端编辑的模板内容到 data/template/<template_name> 目录"""
serializer = TemplateEditSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
template_name = serializer.validated_data['EMAIL_TEMPLATE_NAME']
content = serializer.validated_data['EMAIL_TEMPLATE_CONTENT']
data_path = _get_data_template_path(template_name)
data_dir = os.path.dirname(data_path)
try:
os.makedirs(data_dir, exist_ok=True)
with open(data_path, 'w', encoding='utf-8') as f:
f.write(content)
except Exception as e:
return Response({'ok': False, 'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response({'ok': True, 'path': data_path})
@action(detail=False, methods=['post'], url_path='reset', name='reset')
def reset(self, request):
template_name = request.data.get('template_name')
data_path = _get_data_template_path(template_name)
try:
if os.path.exists(data_path):
os.remove(data_path)
except Exception as e:
return Response({'ok': False, 'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response({'ok': True, 'path': data_path})

View File

@@ -54,7 +54,24 @@ def publish_task(receive_user_ids, backends_msg_mapper):
Message.send_msg(receive_user_ids, backends_msg_mapper)
class Message(metaclass=MessageType):
class CustomMsgTemplateBase:
_registry = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if cls is not CustomMsgTemplateBase and getattr(cls, "template_name", None):
CustomMsgTemplateBase._registry.append(cls)
@classmethod
def as_dict(cls):
return {
"template_name": cls.template_name,
"subject": cls.subject,
"contexts": cls.contexts,
}
class Message(CustomMsgTemplateBase, metaclass=MessageType):
"""
这里封装了什么?
封装不同消息的模板,提供统一的发送消息的接口

View File

@@ -1,3 +1,4 @@
from django.template import Engine, TemplateSyntaxError
from rest_framework import serializers
from common.serializers import BulkModelSerializer
@@ -40,3 +41,16 @@ class UserMsgSubscriptionSerializer(BulkModelSerializer):
class Meta:
model = UserMsgSubscription
fields = ('user_id', 'receive_backends',)
class TemplateEditSerializer(serializers.Serializer):
EMAIL_TEMPLATE_NAME = serializers.CharField(max_length=256)
EMAIL_TEMPLATE_CONTENT = serializers.CharField()
def validate_EMAIL_TEMPLATE_CONTENT(self, value):
safe_engine = Engine(debug=False, libraries={}, builtins=[])
try:
safe_engine.from_string(value)
except TemplateSyntaxError as e:
raise serializers.ValidationError(f'Template syntax error at: {e.token.lineno}')
return value

View File

@@ -10,9 +10,9 @@ router = BulkRouter()
router.register('system-msg-subscription', api.SystemMsgSubscriptionViewSet, 'system-msg-subscription')
router.register('user-msg-subscription', api.UserMsgSubscriptionViewSet, 'user-msg-subscription')
router.register('site-messages', api.SiteMessageViewSet, 'site-message')
router.register('templates', api.TemplateViewSet, 'template')
urlpatterns = [
path('backends/', api.BackendListView.as_view(), name='backends')
path('backends/', api.BackendListView.as_view(), name='backends'),
]
urlpatterns += router.urls

View File

@@ -8,7 +8,7 @@ from common.serializers.fields import EncryptedField
__all__ = [
'MailTestSerializer', 'EmailSettingSerializer',
'EmailContentSettingSerializer', 'SMSBackendSerializer',
'EmailContentSettingSerializer', 'SMSBackendSerializer'
]
@@ -35,7 +35,8 @@ class EmailSettingSerializer(serializers.Serializer):
)
EMAIL_HOST_PASSWORD = EncryptedField(
max_length=1024, required=False, label=_("Password"),
help_text=_("Password to use for the email server. It is used in conjunction with `Account` when authenticating to the email server")
help_text=_(
"Password to use for the email server. It is used in conjunction with `Account` when authenticating to the email server")
)
EMAIL_FROM = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('Sender'),

View File

@@ -8,6 +8,7 @@ from common.sdk.im.wecom import wecom_tool
from common.utils import get_logger, reverse
from common.utils import lazyproperty
from common.utils.timezone import local_now_display
from common.views.template import custom_render_to_string
from notifications.backends import BACKEND
from notifications.models import SystemMsgSubscription
from notifications.notifications import SystemMessage, UserMessage
@@ -280,7 +281,17 @@ class StorageConnectivityMessage(SystemMessage):
class SessionSharingMessage(UserMessage):
subject = _('Session sharing')
message_type_label = _('Session sharing')
template_name = 'terminal/_msg_session_sharing.html'
contexts = [
{"name": "asset", "label": _('Asset'), "default": "dev server"},
{"name": "created_by", "label": _('Created by'), "default": "2025-01-01 10:00:00"},
{"name": "account", "label": _('Account'), "default": "root"},
{"name": "url", "label": _('URL'), "default": "http://example.com/session/xxxx"},
{"name": "verify_code", "label": _('Verify code'), "default": "123456"},
{"name": "org", "label": _('Organization'), "default": "Default Default"},
]
def __init__(self, user, instance):
super().__init__(user)
@@ -289,14 +300,14 @@ class SessionSharingMessage(UserMessage):
def get_html_msg(self) -> dict:
instance = self.instance
context = {
'asset': instance.session.asset,
'asset': str(instance.session.asset),
'created_by': instance.created_by,
'account': instance.session.account,
'account': str(instance.session.account),
'url': instance.url,
'verify_code': instance.verify_code,
'org': instance.org_name,
}
message = render_to_string('terminal/_msg_session_sharing.html', context)
message = custom_render_to_string(self.template_name, context)
return {
'subject': self.message_type_label + ' ' + self.instance.created_by,
'message': message

View File

@@ -7,10 +7,25 @@ from django.utils import timezone
from django.utils.translation import gettext as _
from common.utils import reverse, get_request_ip_or_data, get_request_user_agent
from common.views.template import custom_render_to_string
from notifications.notifications import UserMessage
class UserCreatedMsg(UserMessage):
subject = str(settings.EMAIL_CUSTOM_USER_CREATED_SUBJECT)
template_name = 'users/_msg_user_created.html'
contexts = [
{"name": "honorific", "label": _('Honorific'), "default": "zhangsan"},
{"name": "content", "label": _('Content'), "default": "Welcome to use our system."},
{"name": "username", "label": _('Username'), "default": "zhangsan"},
{"name": "name", "label": _('Name'), "default": "张三"},
{"name": "email", "label": _('Email'), "default": "123456@qq.com"},
{"name": "rest_password_url", "label": _('Reset password url'),
"default": "https://example.com/reset-password"},
{"name": "rest_password_token", "label": _('Reset password token'), "default": "abcdefg1234567"},
{"name": "forget_password_url", "label": _('Login url'), "default": "https://example.com/forget-password"},
]
def get_html_msg(self) -> dict:
user = self.user
@@ -27,13 +42,12 @@ class UserCreatedMsg(UserMessage):
context = {
**mail_context,
'user': user,
**user_info,
'rest_password_url': reverse('authentication:reset-password', external=True),
'rest_password_token': user.generate_reset_token(),
'forget_password_url': reverse('authentication:forgot-password', external=True),
'login_url': reverse('authentication:login', external=True),
'forget_password_url': reverse('authentication:login', external=True),
}
message = render_to_string('users/_msg_user_created.html', context)
message = custom_render_to_string(self.template_name, context)
return {
'subject': mail_context['subject'],
'message': message
@@ -46,15 +60,25 @@ class UserCreatedMsg(UserMessage):
class ResetPasswordMsg(UserMessage):
subject = _('Reset password')
template_name = 'authentication/_msg_reset_password.html'
contexts = [
{"name": "email", "label": _('Email'), "default": "123456@qq.com"},
{"name": "rest_password_url", "label": _('Reset password url'),
"default": "https://example.com/reset-password"},
{"name": "rest_password_token", "label": _('Reset password token'), "default": "abcdefg1234567"},
{"name": "forget_password_url", "label": _('Login url'), "default": "https://example.com/forget-password"},
{"name": "login_url", "label": _('Login url'), "default": "https://example.com/login"},
]
def __init__(self, user):
super().__init__(user)
self.reset_passwd_token = user.generate_reset_token()
def get_html_msg(self) -> dict:
user = self.user
subject = _('Reset password')
context = {
'user': user,
'email': user.email,
'rest_password_url': reverse('authentication:reset-password', external=True),
'rest_password_token': self.reset_passwd_token,
'forget_password_url': reverse('authentication:forgot-password', external=True),
@@ -62,7 +86,7 @@ class ResetPasswordMsg(UserMessage):
}
message = render_to_string('authentication/_msg_reset_password.html', context)
return {
'subject': subject,
'subject': self.subject,
'message': message
}
@@ -74,6 +98,14 @@ class ResetPasswordMsg(UserMessage):
class ResetPasswordSuccessMsg(UserMessage):
subject = _('Reset password success')
template_name = 'authentication/_msg_rest_password_success.html'
contexts = [
{"name": "name", "label": _('Name'), "default": "张三"},
{"name": "ip_address", "label": _('IP address'), "default": "192.168.1.1"},
{"name": "browser", "label": _('Browser'), "default": "Mozilla/firefox"}
]
def __init__(self, user, request):
super().__init__(user)
self.ip_address = get_request_ip_or_data(request)
@@ -82,15 +114,14 @@ class ResetPasswordSuccessMsg(UserMessage):
def get_html_msg(self) -> dict:
user = self.user
subject = _('Reset password success')
context = {
'name': user.name,
'ip_address': self.ip_address,
'browser': self.browser,
}
message = render_to_string('authentication/_msg_rest_password_success.html', context)
message = custom_render_to_string(self.template_name, context)
return {
'subject': subject,
'subject': self.subject,
'message': message
}
@@ -106,6 +137,14 @@ class ResetPasswordSuccessMsg(UserMessage):
class ResetPublicKeySuccessMsg(UserMessage):
subject = _('Reset public key success')
template_name = 'authentication/_msg_rest_public_key_success.html'
contexts = [
{"name": "name", "label": _('Name'), "default": "张三"},
{"name": "ip_address", "label": _('IP address'), "default": "192.168.1.1"},
{"name": "browser", "label": _('Browser'), "default": "Mozilla/firefox"}
]
def __init__(self, user, request):
super().__init__(user)
self.ip_address = get_request_ip_or_data(request)
@@ -114,15 +153,14 @@ class ResetPublicKeySuccessMsg(UserMessage):
def get_html_msg(self) -> dict:
user = self.user
subject = _('Reset public key success')
context = {
'name': user.name,
'ip_address': self.ip_address,
'browser': self.browser,
}
message = render_to_string('authentication/_msg_rest_public_key_success.html', context)
message = custom_render_to_string(self.template_name, context)
return {
'subject': subject,
'subject': self.subject,
'message': message
}
@@ -138,9 +176,20 @@ class ResetPublicKeySuccessMsg(UserMessage):
class PasswordExpirationReminderMsg(UserMessage):
subject = _('Password is about expire')
template_name = 'users/_msg_password_expire_reminder.html'
contexts = [
{"name": "name", "label": _('Name'), "default": "张三"},
{"name": "date_password_expired", "label": _('Password expiration date'), "default": "2025-01-01 12:00:00"},
{"name": "update_password_url", "label": _('Update password url'),
"default": "https://example.com/update-password"},
{"name": "forget_password_url", "label": _('Login url'), "default": "https://example.com/forget-password"},
{"name": "email", "label": _('Email'), "default": "123456@qq.com"},
{"name": "login_url", "label": _('Login url'), "default": "https://example.com/login"},
]
def get_html_msg(self) -> dict:
user = self.user
subject = _('Password is about expire')
date_password_expired_local = timezone.localtime(user.date_password_expired)
update_password_url = urljoin(settings.SITE_URL, '/ui/#/profile/index')
@@ -153,9 +202,9 @@ class PasswordExpirationReminderMsg(UserMessage):
'email': user.email,
'login_url': reverse('authentication:login', external=True),
}
message = render_to_string('users/_msg_password_expire_reminder.html', context)
message = custom_render_to_string(self.template_name, context)
return {
'subject': subject,
'subject': self.subject,
'message': message
}
@@ -167,17 +216,23 @@ class PasswordExpirationReminderMsg(UserMessage):
class UserExpirationReminderMsg(UserMessage):
def get_html_msg(self) -> dict:
subject = _('Account is about expire')
template_name = 'users/_msg_account_expire_reminder.html'
contexts = [
{"name": "name", "label": _('Name'), "default": "张三"},
{"name": "date_expired", "label": _('Expiration date'), "default": "2025-01-01 12:00:00"}
]
def get_html_msg(self) -> dict:
date_expired_local = timezone.localtime(self.user.date_expired)
date_expired = date_expired_local.strftime('%Y-%m-%d %H:%M:%S')
context = {
'name': self.user.name,
'date_expired': date_expired
}
message = render_to_string('users/_msg_account_expire_reminder.html', context)
message = render_to_string(self.template_name, context)
return {
'subject': subject,
'subject': self.subject,
'message': message
}
@@ -189,16 +244,22 @@ class UserExpirationReminderMsg(UserMessage):
class ResetSSHKeyMsg(UserMessage):
def get_html_msg(self) -> dict:
subject = _('Reset SSH Key')
template_name = 'users/_msg_reset_ssh_key.html'
contexts = [
{"name": "name", "label": _('Name'), "default": "张三"},
{"name": "url", "label": _('Update SSH Key url'), "default": "https://example.com/profile/password-and-ssh-key"}
]
def get_html_msg(self) -> dict:
update_url = urljoin(settings.SITE_URL, '/ui/#/profile/password-and-ssh-key/?tab=SSHKey')
context = {
'name': self.user.name,
'url': update_url,
}
message = render_to_string('users/_msg_reset_ssh_key.html', context)
message = custom_render_to_string(self.template_name, context)
return {
'subject': subject,
'subject': self.subject,
'message': message
}
@@ -210,15 +271,21 @@ class ResetSSHKeyMsg(UserMessage):
class ResetMFAMsg(UserMessage):
def get_html_msg(self) -> dict:
subject = _('Reset MFA')
template_name = 'users/_msg_reset_mfa.html'
contexts = [
{"name": "name", "label": _('Name'), "default": "张三"},
{"name": "url", "label": _('Reset MFA url'), "default": "https://example.com/profile/mfa"}
]
def get_html_msg(self) -> dict:
context = {
'name': self.user.name,
'url': reverse('authentication:user-otp-enable-start', external=True),
}
message = render_to_string('users/_msg_reset_mfa.html', context)
message = custom_render_to_string('users/_msg_reset_mfa.html', context)
return {
'subject': subject,
'subject': self.subject,
'message': message
}

View File

@@ -1,7 +1,7 @@
{% load i18n %}
<p>
{{ honorific }} {{ user }},
{{ honorific }} {{ name }},
</p>
<div>
@@ -9,7 +9,7 @@
{{ content | safe }}
</p>
<p>
{% trans 'Username' %}: {{ user.username }} <br />
{% trans 'Username' %}: {{ username }} <br/>
{% trans 'Password' %}:
<a href="{{ rest_password_url }}?token={{ rest_password_token }}">
{% trans 'click here to set your password' %}
@@ -18,6 +18,6 @@
<br>
<p>
{% trans 'This link is valid for 1 hour. After it expires' %}
<a href="{{ forget_password_url }}?email={{ user.email }}">{% trans 'request new one' %}</a>
<a href="{{ forget_password_url }}?email={{ email }}">{% trans 'request new one' %}</a>
</p>
</div>