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 acls.models import LoginACL, LoginAssetACL
from assets.models import Asset from assets.models import Asset
from audits.models import UserLoginLog from audits.models import UserLoginLog
from common.views.template import custom_render_to_string
from notifications.notifications import UserMessage from notifications.notifications import UserMessage
from users.models import User from users.models import User
class UserLoginReminderMsg(UserMessage): class UserLoginReminderMsg(UserMessage):
subject = _('User login reminder') 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): def __init__(self, user, user_log: UserLoginLog, acl: LoginACL):
self.user_log = user_log self.user_log = user_log
@@ -23,11 +34,12 @@ class UserLoginReminderMsg(UserMessage):
'ip': user_log.ip, 'ip': user_log.ip,
'city': user_log.city, 'city': user_log.city,
'username': user_log.username, 'username': user_log.username,
'recipient': self.user,
'acl_name': self.acl_name, 'acl_name': self.acl_name,
'recipient_name': self.user.name,
'recipient_username': self.user.username,
'user_agent': user_log.user_agent, '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 { return {
'subject': str(self.subject), 'subject': str(self.subject),
@@ -43,6 +55,18 @@ class UserLoginReminderMsg(UserMessage):
class AssetLoginReminderMsg(UserMessage): class AssetLoginReminderMsg(UserMessage):
subject = _('User login alert for asset') 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__( def __init__(
self, user, asset: Asset, login_user: User, self, user, asset: Asset, login_user: User,
@@ -51,6 +75,7 @@ class AssetLoginReminderMsg(UserMessage):
): ):
self.ip = ip self.ip = ip
self.asset = asset self.asset = asset
self.login_user = login_user
self.account = account self.account = account
self.acl_name = str(acl) self.acl_name = str(acl)
self.login_user = login_user self.login_user = login_user
@@ -60,7 +85,8 @@ class AssetLoginReminderMsg(UserMessage):
def get_html_msg(self) -> dict: def get_html_msg(self) -> dict:
context = { context = {
'ip': self.ip, 'ip': self.ip,
'recipient': self.user, 'recipient_name': self.user.name,
'recipient_username': self.user.username,
'username': self.login_user.username, 'username': self.login_user.username,
'name': self.login_user.name, 'name': self.login_user.name,
'asset': str(self.asset), 'asset': str(self.asset),
@@ -68,7 +94,7 @@ class AssetLoginReminderMsg(UserMessage):
'account_name': self.account.name, 'account_name': self.account.name,
'acl_name': self.acl_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 { return {
'subject': str(self.subject), 'subject': str(self.subject),

View File

@@ -1,6 +1,6 @@
{% load i18n %} {% load i18n %}
<h3>{% trans 'Dear' %}: {{ recipient.name }}[{{ recipient.username }}]</h3> <h3>{% trans 'Dear' %}: {{ recipient_name }}[{{ recipient_username }}]</h3>
<hr> <hr>
<p>{% trans 'We would like to inform you that a user has recently logged:' %}<p> <p>{% trans 'We would like to inform you that a user has recently logged:' %}<p>
<p><strong>{% trans 'User details' %}:</strong></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 django.utils.translation import gettext as _
from common.utils import get_logger from common.utils import get_logger
from common.utils.timezone import local_now_display from common.utils.timezone import local_now_display
from common.views.template import custom_render_to_string
from notifications.notifications import UserMessage from notifications.notifications import UserMessage
logger = get_logger(__file__) logger = get_logger(__file__)
class DifferentCityLoginMessage(UserMessage): 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): def __init__(self, user, ip, city):
self.ip = ip self.ip = ip
self.city = city self.city = city
@@ -16,18 +26,16 @@ class DifferentCityLoginMessage(UserMessage):
def get_html_msg(self) -> dict: def get_html_msg(self) -> dict:
now = local_now_display() now = local_now_display()
subject = _('Different city login reminder')
context = dict( context = dict(
subject=subject,
name=self.user.name, name=self.user.name,
username=self.user.username, username=self.user.username,
ip=self.ip, ip=self.ip,
time=now, time=now,
city=self.city, city=self.city,
) )
message = render_to_string('authentication/_msg_different_city.html', context) message = custom_render_to_string(self.template_name, context)
return { return {
'subject': subject, 'subject': self.subject,
'message': message 'message': message
} }
@@ -41,6 +49,16 @@ class DifferentCityLoginMessage(UserMessage):
class OAuthBindMessage(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): def __init__(self, user, ip, oauth_name, oauth_id):
super().__init__(user) super().__init__(user)
self.ip = ip self.ip = ip
@@ -51,7 +69,6 @@ class OAuthBindMessage(UserMessage):
now = local_now_display() now = local_now_display()
subject = self.oauth_name + ' ' + _('binding reminder') subject = self.oauth_name + ' ' + _('binding reminder')
context = dict( context = dict(
subject=subject,
name=self.user.name, name=self.user.name,
username=self.user.username, username=self.user.username,
ip=self.ip, ip=self.ip,
@@ -59,7 +76,7 @@ class OAuthBindMessage(UserMessage):
oauth_name=self.oauth_name, oauth_name=self.oauth_name,
oauth_id=self.oauth_id 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 { return {
'subject': subject, 'subject': subject,
'message': message 'message': message

View File

@@ -6,12 +6,12 @@
{% trans 'Please click the link below to reset your password, if not your request, concern your account security' %} {% trans 'Please click the link below to reset your password, if not your request, concern your account security' %}
<br> <br>
<br> <br>
<a href="{{ rest_password_url }}?token={{ rest_password_token}}" class='showLink' target="_blank"> <a href="{{ rest_password_url }}?token={{ rest_password_token }}" class='showLink' target="_blank">
{% trans 'Click here reset password' %} {% trans 'Click here reset password' %}
</a> </a>
</p> </p>
<br> <br>
<p> <p>
{% trans 'This link is valid for 1 hour. After it expires' %} {% 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> </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.mixins import ListModelMixin, UpdateModelMixin, RetrieveModelMixin
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from common.api import JMSGenericViewSet 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.backends import BACKEND
from notifications.models import SystemMsgSubscription, UserMsgSubscription from notifications.models import SystemMsgSubscription, UserMsgSubscription
from notifications.notifications import CustomMsgTemplateBase
from notifications.notifications import system_msgs from notifications.notifications import system_msgs
from notifications.serializers import ( from notifications.serializers import (
SystemMsgSubscriptionSerializer, SystemMsgSubscriptionByCategorySerializer, SystemMsgSubscriptionSerializer, SystemMsgSubscriptionByCategorySerializer,
UserMsgSubscriptionSerializer, UserMsgSubscriptionSerializer,
) )
from notifications.serializers import TemplateEditSerializer
__all__ = ( __all__ = (
'BackendListView', 'SystemMsgSubscriptionViewSet', 'BackendListView', 'SystemMsgSubscriptionViewSet',
'UserMsgSubscriptionViewSet', 'get_all_test_messages' 'UserMsgSubscriptionViewSet', 'get_all_test_messages', 'TemplateViewSet',
) )
@@ -130,3 +138,72 @@ def get_all_test_messages(request):
<hr /> <hr />
""").format(msg_cls.__name__, msg_text) """).format(msg_cls.__name__, msg_text)
return HttpResponse(html_data + text_data) 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) 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 rest_framework import serializers
from common.serializers import BulkModelSerializer from common.serializers import BulkModelSerializer
@@ -40,3 +41,16 @@ class UserMsgSubscriptionSerializer(BulkModelSerializer):
class Meta: class Meta:
model = UserMsgSubscription model = UserMsgSubscription
fields = ('user_id', 'receive_backends',) 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('system-msg-subscription', api.SystemMsgSubscriptionViewSet, 'system-msg-subscription')
router.register('user-msg-subscription', api.UserMsgSubscriptionViewSet, 'user-msg-subscription') router.register('user-msg-subscription', api.UserMsgSubscriptionViewSet, 'user-msg-subscription')
router.register('site-messages', api.SiteMessageViewSet, 'site-message') router.register('site-messages', api.SiteMessageViewSet, 'site-message')
router.register('templates', api.TemplateViewSet, 'template')
urlpatterns = [ urlpatterns = [
path('backends/', api.BackendListView.as_view(), name='backends') path('backends/', api.BackendListView.as_view(), name='backends'),
] ]
urlpatterns += router.urls urlpatterns += router.urls

View File

@@ -8,7 +8,7 @@ from common.serializers.fields import EncryptedField
__all__ = [ __all__ = [
'MailTestSerializer', 'EmailSettingSerializer', 'MailTestSerializer', 'EmailSettingSerializer',
'EmailContentSettingSerializer', 'SMSBackendSerializer', 'EmailContentSettingSerializer', 'SMSBackendSerializer'
] ]
@@ -35,7 +35,8 @@ class EmailSettingSerializer(serializers.Serializer):
) )
EMAIL_HOST_PASSWORD = EncryptedField( EMAIL_HOST_PASSWORD = EncryptedField(
max_length=1024, required=False, label=_("Password"), 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( EMAIL_FROM = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('Sender'), 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 get_logger, reverse
from common.utils import lazyproperty from common.utils import lazyproperty
from common.utils.timezone import local_now_display from common.utils.timezone import local_now_display
from common.views.template import custom_render_to_string
from notifications.backends import BACKEND from notifications.backends import BACKEND
from notifications.models import SystemMsgSubscription from notifications.models import SystemMsgSubscription
from notifications.notifications import SystemMessage, UserMessage from notifications.notifications import SystemMessage, UserMessage
@@ -280,7 +281,17 @@ class StorageConnectivityMessage(SystemMessage):
class SessionSharingMessage(UserMessage): class SessionSharingMessage(UserMessage):
subject = _('Session sharing')
message_type_label = _('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): def __init__(self, user, instance):
super().__init__(user) super().__init__(user)
@@ -289,14 +300,14 @@ class SessionSharingMessage(UserMessage):
def get_html_msg(self) -> dict: def get_html_msg(self) -> dict:
instance = self.instance instance = self.instance
context = { context = {
'asset': instance.session.asset, 'asset': str(instance.session.asset),
'created_by': instance.created_by, 'created_by': instance.created_by,
'account': instance.session.account, 'account': str(instance.session.account),
'url': instance.url, 'url': instance.url,
'verify_code': instance.verify_code, 'verify_code': instance.verify_code,
'org': instance.org_name, 'org': instance.org_name,
} }
message = render_to_string('terminal/_msg_session_sharing.html', context) message = custom_render_to_string(self.template_name, context)
return { return {
'subject': self.message_type_label + ' ' + self.instance.created_by, 'subject': self.message_type_label + ' ' + self.instance.created_by,
'message': message 'message': message

View File

@@ -7,10 +7,25 @@ from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from common.utils import reverse, get_request_ip_or_data, get_request_user_agent 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 from notifications.notifications import UserMessage
class UserCreatedMsg(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: def get_html_msg(self) -> dict:
user = self.user user = self.user
@@ -27,13 +42,12 @@ class UserCreatedMsg(UserMessage):
context = { context = {
**mail_context, **mail_context,
'user': user, **user_info,
'rest_password_url': reverse('authentication:reset-password', external=True), 'rest_password_url': reverse('authentication:reset-password', external=True),
'rest_password_token': user.generate_reset_token(), 'rest_password_token': user.generate_reset_token(),
'forget_password_url': reverse('authentication:forgot-password', external=True), 'forget_password_url': reverse('authentication:login', external=True),
'login_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 { return {
'subject': mail_context['subject'], 'subject': mail_context['subject'],
'message': message 'message': message
@@ -46,15 +60,25 @@ class UserCreatedMsg(UserMessage):
class ResetPasswordMsg(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): def __init__(self, user):
super().__init__(user) super().__init__(user)
self.reset_passwd_token = user.generate_reset_token() self.reset_passwd_token = user.generate_reset_token()
def get_html_msg(self) -> dict: def get_html_msg(self) -> dict:
user = self.user user = self.user
subject = _('Reset password')
context = { context = {
'user': user, 'email': user.email,
'rest_password_url': reverse('authentication:reset-password', external=True), 'rest_password_url': reverse('authentication:reset-password', external=True),
'rest_password_token': self.reset_passwd_token, 'rest_password_token': self.reset_passwd_token,
'forget_password_url': reverse('authentication:forgot-password', external=True), '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) message = render_to_string('authentication/_msg_reset_password.html', context)
return { return {
'subject': subject, 'subject': self.subject,
'message': message 'message': message
} }
@@ -74,6 +98,14 @@ class ResetPasswordMsg(UserMessage):
class ResetPasswordSuccessMsg(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): def __init__(self, user, request):
super().__init__(user) super().__init__(user)
self.ip_address = get_request_ip_or_data(request) self.ip_address = get_request_ip_or_data(request)
@@ -82,15 +114,14 @@ class ResetPasswordSuccessMsg(UserMessage):
def get_html_msg(self) -> dict: def get_html_msg(self) -> dict:
user = self.user user = self.user
subject = _('Reset password success')
context = { context = {
'name': user.name, 'name': user.name,
'ip_address': self.ip_address, 'ip_address': self.ip_address,
'browser': self.browser, 'browser': self.browser,
} }
message = render_to_string('authentication/_msg_rest_password_success.html', context) message = custom_render_to_string(self.template_name, context)
return { return {
'subject': subject, 'subject': self.subject,
'message': message 'message': message
} }
@@ -106,6 +137,14 @@ class ResetPasswordSuccessMsg(UserMessage):
class ResetPublicKeySuccessMsg(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): def __init__(self, user, request):
super().__init__(user) super().__init__(user)
self.ip_address = get_request_ip_or_data(request) self.ip_address = get_request_ip_or_data(request)
@@ -114,15 +153,14 @@ class ResetPublicKeySuccessMsg(UserMessage):
def get_html_msg(self) -> dict: def get_html_msg(self) -> dict:
user = self.user user = self.user
subject = _('Reset public key success')
context = { context = {
'name': user.name, 'name': user.name,
'ip_address': self.ip_address, 'ip_address': self.ip_address,
'browser': self.browser, '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 { return {
'subject': subject, 'subject': self.subject,
'message': message 'message': message
} }
@@ -138,9 +176,20 @@ class ResetPublicKeySuccessMsg(UserMessage):
class PasswordExpirationReminderMsg(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: def get_html_msg(self) -> dict:
user = self.user user = self.user
subject = _('Password is about expire')
date_password_expired_local = timezone.localtime(user.date_password_expired) date_password_expired_local = timezone.localtime(user.date_password_expired)
update_password_url = urljoin(settings.SITE_URL, '/ui/#/profile/index') update_password_url = urljoin(settings.SITE_URL, '/ui/#/profile/index')
@@ -153,9 +202,9 @@ class PasswordExpirationReminderMsg(UserMessage):
'email': user.email, 'email': user.email,
'login_url': reverse('authentication:login', external=True), '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 { return {
'subject': subject, 'subject': self.subject,
'message': message 'message': message
} }
@@ -167,17 +216,23 @@ class PasswordExpirationReminderMsg(UserMessage):
class UserExpirationReminderMsg(UserMessage): class UserExpirationReminderMsg(UserMessage):
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: def get_html_msg(self) -> dict:
subject = _('Account is about expire')
date_expired_local = timezone.localtime(self.user.date_expired) date_expired_local = timezone.localtime(self.user.date_expired)
date_expired = date_expired_local.strftime('%Y-%m-%d %H:%M:%S') date_expired = date_expired_local.strftime('%Y-%m-%d %H:%M:%S')
context = { context = {
'name': self.user.name, 'name': self.user.name,
'date_expired': date_expired 'date_expired': date_expired
} }
message = render_to_string('users/_msg_account_expire_reminder.html', context) message = render_to_string(self.template_name, context)
return { return {
'subject': subject, 'subject': self.subject,
'message': message 'message': message
} }
@@ -189,16 +244,22 @@ class UserExpirationReminderMsg(UserMessage):
class ResetSSHKeyMsg(UserMessage): class ResetSSHKeyMsg(UserMessage):
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: def get_html_msg(self) -> dict:
subject = _('Reset SSH Key')
update_url = urljoin(settings.SITE_URL, '/ui/#/profile/password-and-ssh-key/?tab=SSHKey') update_url = urljoin(settings.SITE_URL, '/ui/#/profile/password-and-ssh-key/?tab=SSHKey')
context = { context = {
'name': self.user.name, 'name': self.user.name,
'url': update_url, 'url': update_url,
} }
message = render_to_string('users/_msg_reset_ssh_key.html', context) message = custom_render_to_string(self.template_name, context)
return { return {
'subject': subject, 'subject': self.subject,
'message': message 'message': message
} }
@@ -210,15 +271,21 @@ class ResetSSHKeyMsg(UserMessage):
class ResetMFAMsg(UserMessage): class ResetMFAMsg(UserMessage):
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: def get_html_msg(self) -> dict:
subject = _('Reset MFA')
context = { context = {
'name': self.user.name, 'name': self.user.name,
'url': reverse('authentication:user-otp-enable-start', external=True), '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 { return {
'subject': subject, 'subject': self.subject,
'message': message 'message': message
} }

View File

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