refactor: 移除邮件功能
发信能力整体下线,唯一的调用方(忘记密码)前端也从来没接过。 - utils/shortcuts.py 删 send_email,连带四个 django.core.mail 相关 import - account/tasks.py 整个删除,里面只有 send_email_async 一个 actor - 删 ApplyResetPasswordAPI / ResetPasswordAPI 及其路由、serializer, User.reset_password_token 和 reset_password_token_expire_time 两个 字段(account/0010) - 删 SMTPAPI / SMTPTestAPI 及其路由、三个 SMTP serializer、 SysOptions.smtp_config - account/templates/reset_password_email.html options/0003 数据迁移删掉库里的 smtp_config 行:_init_option 会为每个 OptionKey 建行,key 从代码里去掉后它会变成孤儿,而且配过 SMTP 的话 value 里存着明文密码。 保留 ResetUserPasswordAPI(管理端直接重置密码,不发信,前端在用)和 User.email(注册要填)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 6.0.4 on 2026-08-06 05:15
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0009_remove_user_tfa_token_remove_user_two_factor_auth'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='user',
|
||||||
|
name='reset_password_token',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='user',
|
||||||
|
name='reset_password_token_expire_time',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -36,8 +36,6 @@ class User(AbstractBaseUser):
|
|||||||
# One of UserType
|
# One of UserType
|
||||||
admin_type = models.TextField(default=AdminType.REGULAR_USER, choices=AdminType.choices)
|
admin_type = models.TextField(default=AdminType.REGULAR_USER, choices=AdminType.choices)
|
||||||
problem_permission = models.TextField(default=ProblemPermission.NONE, choices=ProblemPermission.choices)
|
problem_permission = models.TextField(default=ProblemPermission.NONE, choices=ProblemPermission.choices)
|
||||||
reset_password_token = models.TextField(null=True)
|
|
||||||
reset_password_token_expire_time = models.DateTimeField(null=True)
|
|
||||||
# SSO auth token
|
# SSO auth token
|
||||||
auth_token = models.TextField(null=True)
|
auth_token = models.TextField(null=True)
|
||||||
session_keys = JSONField(default=list, db_default=models.Value([], output_field=models.JSONField()))
|
session_keys = JSONField(default=list, db_default=models.Value([], output_field=models.JSONField()))
|
||||||
|
|||||||
@@ -131,17 +131,6 @@ class EditUserProfileSerializer(serializers.Serializer):
|
|||||||
language = serializers.CharField(max_length=32, allow_blank=True, required=False)
|
language = serializers.CharField(max_length=32, allow_blank=True, required=False)
|
||||||
|
|
||||||
|
|
||||||
class ApplyResetPasswordSerializer(serializers.Serializer):
|
|
||||||
email = serializers.EmailField()
|
|
||||||
captcha = serializers.CharField()
|
|
||||||
|
|
||||||
|
|
||||||
class ResetPasswordSerializer(serializers.Serializer):
|
|
||||||
token = serializers.CharField()
|
|
||||||
password = serializers.CharField(min_length=6)
|
|
||||||
captcha = serializers.CharField()
|
|
||||||
|
|
||||||
|
|
||||||
class SSOSerializer(serializers.Serializer):
|
class SSOSerializer(serializers.Serializer):
|
||||||
token = serializers.CharField()
|
token = serializers.CharField()
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
import dramatiq
|
|
||||||
|
|
||||||
from options.options import SysOptions
|
|
||||||
from utils.shortcuts import DRAMATIQ_WORKER_ARGS, send_email
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@dramatiq.actor(**DRAMATIQ_WORKER_ARGS(max_retries=3))
|
|
||||||
def send_email_async(from_name, to_email, to_name, subject, content):
|
|
||||||
if not SysOptions.smtp_config:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
send_email(smtp_config=SysOptions.smtp_config, from_name=from_name, to_email=to_email, to_name=to_name, subject=subject, content=content)
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(e)
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<div>
|
|
||||||
<table cellpadding="0" align="center"
|
|
||||||
style="overflow:hidden;background:#fff;margin:0 auto;text-align:left;position:relative;font-size:14px; font-family:'lucida Grande',Verdana;line-height:1.5;box-shadow:0 0 3px #ccc;border:1px solid #ccc;border-radius:5px;border-collapse:collapse;">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th valign="middle"
|
|
||||||
style="height:38px;color:#fff; font-size:14px;line-height:38px; font-weight:bold;text-align:left;padding:10px 24px 6px; border-bottom:1px solid #467ec3;background:#518bcb;border-radius:5px 5px 0 0;">
|
|
||||||
{{ website_name }}</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div style="padding:20px 35px 40px;">
|
|
||||||
<h2 style="font-weight:bold;margin-bottom:5px;font-size:14px;">Hello, {{ username }}:</h2>
|
|
||||||
<p style="margin-top:20px">
|
|
||||||
Please click <a href="{{ link }}">{{ link }}</a> to reset your password in 20 minutes.
|
|
||||||
</p>
|
|
||||||
<p style="margin-top:20px">
|
|
||||||
To protect your account, please do not use simple passwords.
|
|
||||||
</p>
|
|
||||||
<p style="margin-top:20px">
|
|
||||||
If you still have any questions, please contract system administrator.
|
|
||||||
</p>
|
|
||||||
<p style="margin-left:2em;"></p>
|
|
||||||
<p style="text-indent:0;text-align:right;">{{ website_name }}</p>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
@@ -4,12 +4,10 @@ from utils.captcha.views import CaptchaAPIView
|
|||||||
|
|
||||||
from ..views.oj import (
|
from ..views.oj import (
|
||||||
SSOAPI,
|
SSOAPI,
|
||||||
ApplyResetPasswordAPI,
|
|
||||||
AvatarUploadAPI,
|
AvatarUploadAPI,
|
||||||
Metrics,
|
Metrics,
|
||||||
OpenAPIAppkeyAPI,
|
OpenAPIAppkeyAPI,
|
||||||
ProfileProblemDisplayIDRefreshAPI,
|
ProfileProblemDisplayIDRefreshAPI,
|
||||||
ResetPasswordAPI,
|
|
||||||
SessionManagementAPI,
|
SessionManagementAPI,
|
||||||
UserActivityRankAPI,
|
UserActivityRankAPI,
|
||||||
UserChangeEmailAPI,
|
UserChangeEmailAPI,
|
||||||
@@ -29,8 +27,6 @@ urlpatterns = [
|
|||||||
path("register", UserRegisterAPI.as_view()),
|
path("register", UserRegisterAPI.as_view()),
|
||||||
path("change_password", UserChangePasswordAPI.as_view()), # DEPRECATED: 前端未调用
|
path("change_password", UserChangePasswordAPI.as_view()), # DEPRECATED: 前端未调用
|
||||||
path("change_email", UserChangeEmailAPI.as_view()), # DEPRECATED: 前端未调用
|
path("change_email", UserChangeEmailAPI.as_view()), # DEPRECATED: 前端未调用
|
||||||
path("apply_reset_password", ApplyResetPasswordAPI.as_view()), # DEPRECATED: 前端未调用
|
|
||||||
path("reset_password", ResetPasswordAPI.as_view()), # DEPRECATED: 前端未调用
|
|
||||||
path("captcha", CaptchaAPIView.as_view()),
|
path("captcha", CaptchaAPIView.as_view()),
|
||||||
path("check_username_or_email", UsernameOrEmailCheck.as_view()), # DEPRECATED: 前端未调用
|
path("check_username_or_email", UsernameOrEmailCheck.as_view()), # DEPRECATED: 前端未调用
|
||||||
path("profile", UserProfileAPI.as_view(), name="user_profile_api"),
|
path("profile", UserProfileAPI.as_view(), name="user_profile_api"),
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
from datetime import timedelta
|
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, Q
|
||||||
from django.template.loader import render_to_string
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.timezone import now
|
|
||||||
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
|
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
|
||||||
|
|
||||||
from options.options import SysOptions
|
from options.options import SysOptions
|
||||||
@@ -24,11 +21,9 @@ from utils.shortcuts import datetime2str, rand_str
|
|||||||
from ..decorators import login_required
|
from ..decorators import login_required
|
||||||
from ..models import AdminType, User, UserProfile
|
from ..models import AdminType, User, UserProfile
|
||||||
from ..serializers import (
|
from ..serializers import (
|
||||||
ApplyResetPasswordSerializer,
|
|
||||||
EditUserProfileSerializer,
|
EditUserProfileSerializer,
|
||||||
ImageUploadForm,
|
ImageUploadForm,
|
||||||
RankInfoSerializer,
|
RankInfoSerializer,
|
||||||
ResetPasswordSerializer,
|
|
||||||
SSOSerializer,
|
SSOSerializer,
|
||||||
UserChangeEmailSerializer,
|
UserChangeEmailSerializer,
|
||||||
UserChangePasswordSerializer,
|
UserChangePasswordSerializer,
|
||||||
@@ -37,7 +32,6 @@ from ..serializers import (
|
|||||||
UserProfileSerializer,
|
UserProfileSerializer,
|
||||||
UserRegisterSerializer,
|
UserRegisterSerializer,
|
||||||
)
|
)
|
||||||
from ..tasks import send_email_async
|
|
||||||
|
|
||||||
|
|
||||||
class UserProfileAPI(AsyncAPIView):
|
class UserProfileAPI(AsyncAPIView):
|
||||||
@@ -216,61 +210,6 @@ class UserChangePasswordAPI(APIView):
|
|||||||
return self.error("Invalid old password")
|
return self.error("Invalid old password")
|
||||||
|
|
||||||
|
|
||||||
# DEPRECATED: 前端未调用 (2026-05-26)
|
|
||||||
class ApplyResetPasswordAPI(APIView):
|
|
||||||
@validate_serializer(ApplyResetPasswordSerializer)
|
|
||||||
def post(self, request):
|
|
||||||
if request.user.is_authenticated:
|
|
||||||
return self.error("You have already logged in, are you kidding me? ")
|
|
||||||
data = request.data
|
|
||||||
captcha = Captcha(request)
|
|
||||||
if not captcha.check(data["captcha"]):
|
|
||||||
return self.error("Invalid captcha")
|
|
||||||
try:
|
|
||||||
user = User.objects.get(email__iexact=data["email"])
|
|
||||||
except User.DoesNotExist:
|
|
||||||
return self.error("User does not exist")
|
|
||||||
if user.reset_password_token_expire_time and 0 < int((user.reset_password_token_expire_time - now()).total_seconds()) < 20 * 60:
|
|
||||||
return self.error("You can only reset password once per 20 minutes")
|
|
||||||
user.reset_password_token = rand_str()
|
|
||||||
user.reset_password_token_expire_time = now() + timedelta(minutes=20)
|
|
||||||
user.save()
|
|
||||||
render_data = {
|
|
||||||
"username": user.username,
|
|
||||||
"website_name": SysOptions.website_name,
|
|
||||||
"link": f"{SysOptions.website_base_url}/reset-password/{user.reset_password_token}",
|
|
||||||
}
|
|
||||||
email_html = render_to_string("reset_password_email.html", render_data)
|
|
||||||
send_email_async.send(
|
|
||||||
from_name=SysOptions.website_name_shortcut,
|
|
||||||
to_email=user.email,
|
|
||||||
to_name=user.username,
|
|
||||||
subject="Reset your password",
|
|
||||||
content=email_html,
|
|
||||||
)
|
|
||||||
return self.success("Succeeded")
|
|
||||||
|
|
||||||
|
|
||||||
# DEPRECATED: 前端未调用 (2026-05-26)
|
|
||||||
class ResetPasswordAPI(APIView):
|
|
||||||
@validate_serializer(ResetPasswordSerializer)
|
|
||||||
def post(self, request):
|
|
||||||
data = request.data
|
|
||||||
captcha = Captcha(request)
|
|
||||||
if not captcha.check(data["captcha"]):
|
|
||||||
return self.error("Invalid captcha")
|
|
||||||
try:
|
|
||||||
user = User.objects.get(reset_password_token=data["token"])
|
|
||||||
except User.DoesNotExist:
|
|
||||||
return self.error("Token does not exist")
|
|
||||||
if user.reset_password_token_expire_time < now():
|
|
||||||
return self.error("Token has expired")
|
|
||||||
user.reset_password_token = None
|
|
||||||
user.set_password(data["password"])
|
|
||||||
user.save()
|
|
||||||
return self.success("Succeeded")
|
|
||||||
|
|
||||||
|
|
||||||
# DEPRECATED: 前端未调用 (2026-05-26)
|
# DEPRECATED: 前端未调用 (2026-05-26)
|
||||||
class SessionManagementAPI(APIView):
|
class SessionManagementAPI(APIView):
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
@@ -3,22 +3,6 @@ from utils.api import serializers
|
|||||||
from .models import JudgeServer
|
from .models import JudgeServer
|
||||||
|
|
||||||
|
|
||||||
class EditSMTPConfigSerializer(serializers.Serializer):
|
|
||||||
server = serializers.CharField(max_length=128)
|
|
||||||
port = serializers.IntegerField(default=25)
|
|
||||||
email = serializers.CharField(max_length=256)
|
|
||||||
password = serializers.CharField(max_length=128, required=False, allow_null=True, allow_blank=True)
|
|
||||||
tls = serializers.BooleanField()
|
|
||||||
|
|
||||||
|
|
||||||
class CreateSMTPConfigSerializer(EditSMTPConfigSerializer):
|
|
||||||
password = serializers.CharField(max_length=128)
|
|
||||||
|
|
||||||
|
|
||||||
class TestSMTPConfigSerializer(serializers.Serializer):
|
|
||||||
email = serializers.EmailField()
|
|
||||||
|
|
||||||
|
|
||||||
class CreateEditWebsiteConfigSerializer(serializers.Serializer):
|
class CreateEditWebsiteConfigSerializer(serializers.Serializer):
|
||||||
website_base_url = serializers.CharField(max_length=128)
|
website_base_url = serializers.CharField(max_length=128)
|
||||||
website_name = serializers.CharField(max_length=64)
|
website_name = serializers.CharField(max_length=64)
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from ..views import (
|
from ..views import (
|
||||||
SMTPAPI,
|
|
||||||
DashboardInfoAPI,
|
DashboardInfoAPI,
|
||||||
JudgeServerAPI,
|
JudgeServerAPI,
|
||||||
RandomUsernameAPI,
|
RandomUsernameAPI,
|
||||||
SMTPTestAPI,
|
|
||||||
TestCasePruneAPI,
|
TestCasePruneAPI,
|
||||||
WebsiteConfigAPI,
|
WebsiteConfigAPI,
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("smtp", SMTPAPI.as_view()), # DEPRECATED: 前端未调用
|
|
||||||
path("smtp_test", SMTPTestAPI.as_view()), # DEPRECATED: 前端未调用
|
|
||||||
path("website", WebsiteConfigAPI.as_view()),
|
path("website", WebsiteConfigAPI.as_view()),
|
||||||
path("random_user", RandomUsernameAPI.as_view()),
|
path("random_user", RandomUsernameAPI.as_view()),
|
||||||
path("judge_server", JudgeServerAPI.as_view()),
|
path("judge_server", JudgeServerAPI.as_view()),
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import os
|
|||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import smtplib
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
@@ -20,83 +19,19 @@ from problem.models import Problem
|
|||||||
from submission.models import Submission
|
from submission.models import Submission
|
||||||
from utils.api import APIView, AsyncAPIView, CSRFExemptAPIView, validate_serializer
|
from utils.api import APIView, AsyncAPIView, CSRFExemptAPIView, validate_serializer
|
||||||
from utils.cache import JsonDataLoader
|
from utils.cache import JsonDataLoader
|
||||||
from utils.shortcuts import get_env, send_email
|
from utils.shortcuts import get_env
|
||||||
from utils.websocket import push_config_update
|
from utils.websocket import push_config_update
|
||||||
from utils.xss_filter import XSSHtml
|
from utils.xss_filter import XSSHtml
|
||||||
|
|
||||||
from .models import JudgeServer
|
from .models import JudgeServer
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
CreateEditWebsiteConfigSerializer,
|
CreateEditWebsiteConfigSerializer,
|
||||||
CreateSMTPConfigSerializer,
|
|
||||||
EditJudgeServerSerializer,
|
EditJudgeServerSerializer,
|
||||||
EditSMTPConfigSerializer,
|
|
||||||
JudgeServerHeartbeatSerializer,
|
JudgeServerHeartbeatSerializer,
|
||||||
JudgeServerSerializer,
|
JudgeServerSerializer,
|
||||||
TestSMTPConfigSerializer,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# DEPRECATED: 前端未调用 (2026-05-26)
|
|
||||||
class SMTPAPI(APIView):
|
|
||||||
@super_admin_required
|
|
||||||
def get(self, request):
|
|
||||||
smtp = SysOptions.smtp_config
|
|
||||||
if not smtp:
|
|
||||||
return self.success(None)
|
|
||||||
smtp.pop("password")
|
|
||||||
return self.success(smtp)
|
|
||||||
|
|
||||||
@super_admin_required
|
|
||||||
@validate_serializer(CreateSMTPConfigSerializer)
|
|
||||||
def post(self, request):
|
|
||||||
SysOptions.smtp_config = request.data
|
|
||||||
return self.success()
|
|
||||||
|
|
||||||
@super_admin_required
|
|
||||||
@validate_serializer(EditSMTPConfigSerializer)
|
|
||||||
def put(self, request):
|
|
||||||
smtp = SysOptions.smtp_config
|
|
||||||
data = request.data
|
|
||||||
for item in ["server", "port", "email", "tls"]:
|
|
||||||
smtp[item] = data[item]
|
|
||||||
if "password" in data:
|
|
||||||
smtp["password"] = data["password"]
|
|
||||||
SysOptions.smtp_config = smtp
|
|
||||||
return self.success()
|
|
||||||
|
|
||||||
|
|
||||||
# DEPRECATED: 前端未调用 (2026-05-26)
|
|
||||||
class SMTPTestAPI(APIView):
|
|
||||||
@super_admin_required
|
|
||||||
@validate_serializer(TestSMTPConfigSerializer)
|
|
||||||
def post(self, request):
|
|
||||||
if not SysOptions.smtp_config:
|
|
||||||
return self.error("Please setup SMTP config at first")
|
|
||||||
try:
|
|
||||||
send_email(
|
|
||||||
smtp_config=SysOptions.smtp_config,
|
|
||||||
from_name=SysOptions.website_name_shortcut,
|
|
||||||
to_name=request.user.username,
|
|
||||||
to_email=request.data["email"],
|
|
||||||
subject="You have successfully configured SMTP",
|
|
||||||
content="You have successfully configured SMTP",
|
|
||||||
)
|
|
||||||
except smtplib.SMTPResponseException as e:
|
|
||||||
# guess error message encoding
|
|
||||||
msg = b"Failed to send email"
|
|
||||||
try:
|
|
||||||
msg = e.smtp_error
|
|
||||||
# qq mail
|
|
||||||
msg = msg.decode("gbk")
|
|
||||||
except Exception:
|
|
||||||
msg = msg.decode("utf-8", "ignore")
|
|
||||||
return self.error(msg)
|
|
||||||
except Exception as e:
|
|
||||||
msg = str(e)
|
|
||||||
return self.error(msg)
|
|
||||||
return self.success()
|
|
||||||
|
|
||||||
|
|
||||||
class WebsiteConfigAPI(AsyncAPIView):
|
class WebsiteConfigAPI(AsyncAPIView):
|
||||||
async def get(self, request):
|
async def get(self, request):
|
||||||
ret = await SysOptions.aget_many(
|
ret = await SysOptions.aget_many(
|
||||||
|
|||||||
24
options/migrations/0003_remove_smtp_config.py
Normal file
24
options/migrations/0003_remove_smtp_config.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# 邮件功能已整体移除,smtp_config 不再有读写方。_init_option 建过的那一行会留在
|
||||||
|
# 库里成为孤儿,且里面存着明文 SMTP 密码,这里一并删掉。
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def remove_smtp_config(apps, schema_editor):
|
||||||
|
SysOptions = apps.get_model("options", "SysOptions")
|
||||||
|
SysOptions.objects.filter(key="smtp_config").delete()
|
||||||
|
|
||||||
|
|
||||||
|
def restore_smtp_config(apps, schema_editor):
|
||||||
|
SysOptions = apps.get_model("options", "SysOptions")
|
||||||
|
SysOptions.objects.get_or_create(key="smtp_config", defaults={"value": {}})
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("options", "0002_add_sql_language"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(remove_smtp_config, restore_smtp_config),
|
||||||
|
]
|
||||||
@@ -102,7 +102,6 @@ class OptionKeys:
|
|||||||
allow_register = "allow_register"
|
allow_register = "allow_register"
|
||||||
submission_list_show_all = "submission_list_show_all"
|
submission_list_show_all = "submission_list_show_all"
|
||||||
class_list = "class_list"
|
class_list = "class_list"
|
||||||
smtp_config = "smtp_config"
|
|
||||||
judge_server_token = "judge_server_token"
|
judge_server_token = "judge_server_token"
|
||||||
throttling = "throttling"
|
throttling = "throttling"
|
||||||
languages = "languages"
|
languages = "languages"
|
||||||
@@ -117,7 +116,6 @@ class OptionDefaultValue:
|
|||||||
allow_register = True
|
allow_register = True
|
||||||
submission_list_show_all = True
|
submission_list_show_all = True
|
||||||
class_list = []
|
class_list = []
|
||||||
smtp_config = {}
|
|
||||||
judge_server_token = default_token
|
judge_server_token = default_token
|
||||||
throttling = {"ip": {"capacity": 100, "fill_rate": 0.1, "default_capacity": 50}, "user": {"capacity": 20, "fill_rate": 0.03, "default_capacity": 10}}
|
throttling = {"ip": {"capacity": 100, "fill_rate": 0.1, "default_capacity": 50}, "user": {"capacity": 20, "fill_rate": 0.03, "default_capacity": 10}}
|
||||||
languages = languages
|
languages = languages
|
||||||
@@ -242,14 +240,6 @@ class _SysOptionsMeta(type):
|
|||||||
def class_list(cls, value):
|
def class_list(cls, value):
|
||||||
cls._set_option(OptionKeys.class_list, value)
|
cls._set_option(OptionKeys.class_list, value)
|
||||||
|
|
||||||
@my_property
|
|
||||||
def smtp_config(cls):
|
|
||||||
return cls._get_option(OptionKeys.smtp_config)
|
|
||||||
|
|
||||||
@smtp_config.setter
|
|
||||||
def smtp_config(cls, value):
|
|
||||||
cls._set_option(OptionKeys.smtp_config, value)
|
|
||||||
|
|
||||||
@my_property(ttl=DEFAULT_SHORT_TTL)
|
@my_property(ttl=DEFAULT_SHORT_TTL)
|
||||||
def judge_server_token(cls):
|
def judge_server_token(cls):
|
||||||
return cls._get_option(OptionKeys.judge_server_token)
|
return cls._get_option(OptionKeys.judge_server_token)
|
||||||
|
|||||||
@@ -2,12 +2,9 @@ import os
|
|||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from email.utils import formataddr
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.html import strip_tags
|
|
||||||
|
|
||||||
|
|
||||||
def rand_str(length=32, type="lower_hex"):
|
def rand_str(length=32, type="lower_hex"):
|
||||||
@@ -63,25 +60,6 @@ def natural_sort_key(s, _nsre=re.compile(r"(\d+)")):
|
|||||||
return [int(text) if text.isdigit() else text.lower() for text in re.split(_nsre, s)]
|
return [int(text) if text.isdigit() else text.lower() for text in re.split(_nsre, s)]
|
||||||
|
|
||||||
|
|
||||||
def send_email(smtp_config, from_name, to_email, to_name, subject, content):
|
|
||||||
connection = get_connection(
|
|
||||||
host=smtp_config["server"],
|
|
||||||
port=smtp_config["port"],
|
|
||||||
username=smtp_config["email"],
|
|
||||||
password=smtp_config["password"],
|
|
||||||
use_tls=smtp_config["tls"],
|
|
||||||
)
|
|
||||||
message = EmailMultiAlternatives(
|
|
||||||
subject=subject,
|
|
||||||
body=strip_tags(content),
|
|
||||||
from_email=formataddr((from_name, smtp_config["email"])),
|
|
||||||
to=[formataddr((to_name, to_email))],
|
|
||||||
connection=connection,
|
|
||||||
)
|
|
||||||
message.attach_alternative(content, "text/html")
|
|
||||||
return message.send()
|
|
||||||
|
|
||||||
|
|
||||||
def get_env(name, default=""):
|
def get_env(name, default=""):
|
||||||
return os.environ.get(name, default)
|
return os.environ.get(name, default)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user