diff --git a/contest/decorators.py b/contest/decorators.py index a0160326..d08783c0 100644 --- a/contest/decorators.py +++ b/contest/decorators.py @@ -37,6 +37,8 @@ def check_user_contest_permission(func): contest_id = kwargs["contest_id"] elif "contest_id" in request.data: contest_id = request.data["contest_id"] + elif "contest_id" in request.GET: + contest_id = request.GET["contest_id"] else: if request.is_ajax(): return error_response(u"参数错误") diff --git a/contest/tests.py b/contest/tests.py index 88759979..51fca4ff 100644 --- a/contest/tests.py +++ b/contest/tests.py @@ -8,8 +8,7 @@ from rest_framework.test import APITestCase, APIClient from account.models import User from group.models import Group from contest.models import Contest, ContestProblem -from .models import ContestSubmission -from .models import GROUP_CONTEST, PASSWORD_PROTECTED_CONTEST +from .models import GROUP_CONTEST, PASSWORD_PROTECTED_CONTEST, PUBLIC_CONTEST from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN @@ -582,5 +581,39 @@ class ContestListPageTest(TestCase): self.assertEqual(response.status_code, 200) +class ContestProblemMySubmissionListTest(TestCase): + # 以下是我比赛单个题目的提交列表的测试 + def setUp(self): + self.client = Client() + self.user1 = User.objects.create(username="test1", admin_type=SUPER_ADMIN) + self.user1.set_password("testaa") + self.user1.save() + self.user2 = User.objects.create(username="test2", admin_type=REGULAR_USER) + self.user2.set_password("testbb") + self.user2.save() + self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1, + contest_type=PUBLIC_CONTEST, show_rank=True, + show_user_submission=True, + start_time="2015-08-15T10:00:00.000Z", + end_time="2015-08-30T12:00:00.000Z", + created_by=User.objects.get(username="test1")) + self.contest_problem = ContestProblem.objects.create(title="titlex", + description="descriptionx", + input_description="input1_description", + output_description="output1_description", + test_case_id="1", + samples=json.dumps([{"input": "1 1", "output": "2"}]), + time_limit=100, + memory_limit=1000, + hint="hint1", + created_by=self.user1, + contest=self.global_contest, + sort_index="a") + + def test_contestsList_page_not_exist(self): + self.client.login(username="test1", password="testaa") + response = self.client.get('/contest/1/submissions/999/') + self.assertTemplateUsed(response, "utils/error.html") + diff --git a/contest/views.py b/contest/views.py index 191e54a0..d9eee7b3 100644 --- a/contest/views.py +++ b/contest/views.py @@ -19,6 +19,7 @@ from account.models import SUPER_ADMIN, User from account.decorators import login_required from group.models import Group from utils.cache import get_cache_redis +from submission.models import Submission from .models import (Contest, ContestProblem, CONTEST_ENDED, CONTEST_NOT_START, CONTEST_UNDERWAY, ContestRank) from .models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST @@ -444,4 +445,94 @@ class ContestTimeAPIView(APIView): return error_response(u"比赛不存在") return success_response({"start": int((contest.start_time - now()).total_seconds() * 1000), "end": int((contest.end_time - now()).total_seconds() * 1000), - "status": contest.status}) \ No newline at end of file + "status": contest.status}) + + +@login_required +def contest_problem_my_submissions_list_page(request, contest_id, contest_problem_id): + """ + 我比赛单个题目的所有提交列表 + """ + try: + Contest.objects.get(id=contest_id) + except Contest.DoesNotExist: + return error_page(request, u"比赛不存在") + try: + contest_problem = ContestProblem.objects.get(id=contest_problem_id, visible=True) + except ContestProblem.DoesNotExist: + return error_page(request, u"比赛问题不存在") + submissions = Submission.objects.filter(user_id=request.user.id, problem_id=contest_problem.id).\ + order_by("-create_time").\ + values("id", "result", "create_time", "accepted_answer_time", "language") + return render(request, "oj/submission/problem_my_submissions_list.html", + {"submissions": submissions, "problem": contest_problem}) + + +@check_user_contest_permission +def contest_problem_submissions_list_page(request, contest_id, page=1): + """ + 单个比赛中的所有提交(包含自己和别人,自己可查提交结果,其他人不可查) + """ + try: + contest = Contest.objects.get(id=contest_id) + except Contest.DoesNotExist: + return error_page(request, u"比赛不存在") + + submissions = Submission.objects.filter(contest_id=contest_id).\ + values("id", "contest_id", "problem_id", "result", "create_time", + "accepted_answer_time", "language", "user_id").order_by("-create_time") + + user_id = request.GET.get("user_id", None) + if user_id: + submissions = submissions.filter(user_id=request.GET.get("user_id")) + + # 封榜的时候只能看到自己的提交 + if not contest.real_time_rank: + if not (request.user.admin_type == SUPER_ADMIN or request.user == contest.created_by): + submissions = submissions.filter(user_id=request.user.id) + + language = request.GET.get("language", None) + filter = None + if language: + submissions = submissions.filter(language=int(language)) + filter = {"name": "language", "content": language} + result = request.GET.get("result", None) + if result: + submissions = submissions.filter(result=int(result)) + filter = {"name": "result", "content": result} + paginator = Paginator(submissions, 20) + + # 为查询题目标题创建新字典 + title = {} + contest_problems = ContestProblem.objects.filter(contest=contest) + for item in contest_problems: + title[item.id] = item.title + for item in submissions: + item['title'] = title[item['problem_id']] + + try: + current_page = paginator.page(int(page)) + except Exception: + return error_page(request, u"不存在的页码") + previous_page = next_page = None + try: + previous_page = current_page.previous_page_number() + except Exception: + pass + try: + next_page = current_page.next_page_number() + except Exception: + pass + + for item in current_page: + # 自己提交的 管理员和创建比赛的可以看到所有的提交链接 + if item["user_id"] == request.user.id or request.user.admin_type == SUPER_ADMIN or \ + request.user == contest.created_by: + item["show_link"] = True + else: + item["show_link"] = False + + return render(request, "oj/contest/submissions_list.html", + {"submissions": current_page, "page": int(page), + "previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20, + "contest": contest, "filter": filter, "user_id": user_id}) \ No newline at end of file diff --git a/oj/settings.py b/oj/settings.py index dd437a91..2e86e755 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -50,7 +50,6 @@ INSTALLED_APPS = ( 'submission', 'mq', 'contest', - 'contest_submission', 'mail', 'django_extensions', @@ -59,7 +58,7 @@ INSTALLED_APPS = ( if DEBUG: INSTALLED_APPS += ( - 'debug_toolbar', + # 'debug_toolbar', 'rest_framework_swagger', ) diff --git a/oj/urls.py b/oj/urls.py index 59db3b4f..f95fa2bb 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -19,13 +19,12 @@ from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView, from admin.views import AdminTemplateView from problem.views import TestCaseUploadAPIView, ProblemTagAdminAPIView, ProblemAdminAPIView -from submission.views import (SubmissionAPIView, SubmissionAdminAPIView, - SubmissionShareAPIView, SubmissionRejudgeAdminAPIView) -from contest_submission.views import ContestSubmissionAPIView, ContestSubmissionAdminAPIView +from submission.views import (SubmissionAPIView, SubmissionAdminAPIView, ContestSubmissionAPIView, + SubmissionShareAPIView, SubmissionRejudgeAdminAPIView, + ContestSubmissionAdminAPIView) from monitor.views import QueueLengthMonitorAPIView from utils.views import SimditorImageUploadAPIView -from contest_submission.views import contest_problem_my_submissions_list_page urlpatterns = [ @@ -78,16 +77,16 @@ urlpatterns = [ url(r'^contest/(?P\d+)/problem/(?P\d+)/$', "contest.views.contest_problem_page", name="contest_problem_page"), url(r'^contest/(?P\d+)/problem/(?P\d+)/submissions/$', - "contest_submission.views.contest_problem_my_submissions_list_page", + "contest.views.contest_problem_my_submissions_list_page", name="contest_problem_my_submissions_list_page"), url(r'^contest/(?P\d+)/$', "contest.views.contest_page", name="contest_page"), url(r'^contest/(?P\d+)/problems/$', "contest.views.contest_problems_list_page", name="contest_problems_list_page"), - url(r'^contest/(?P\d+)/submissions/$', "contest_submission.views.contest_problem_submissions_list_page", + url(r'^contest/(?P\d+)/submissions/$', "contest.views.contest_problem_submissions_list_page", name="contest_problem_submissions_list_page"), url(r'^contest/(?P\d+)/submissions/(?P\d+)/$', - "contest_submission.views.contest_problem_submissions_list_page", name="contest_problem_submissions_list_page"), + "contest.views.contest_problem_submissions_list_page", name="contest_problem_submissions_list_page"), url(r'^contests/$', "contest.views.contest_list_page", name="contest_list_page"), url(r'^contests/(?P\d+)/$', "contest.views.contest_list_page", name="contest_list_page"), @@ -128,6 +127,7 @@ urlpatterns = [ url(r'^account/settings/$', TemplateView.as_view(template_name="oj/account/settings.html"), name="account_setting_page"), url(r'^account/settings/avatar/$', TemplateView.as_view(template_name="oj/account/avatar.html"), name="avatar_settings_page"), + url(r'^account/auth/$', "account.views.auth_page", name="auth_login_page"), ] diff --git a/submission/serializers.py b/submission/serializers.py index 3a5308cd..dcaf7014 100644 --- a/submission/serializers.py +++ b/submission/serializers.py @@ -30,3 +30,10 @@ class SubmissionRejudgeSerializer(serializers.Serializer): submission_id = serializers.CharField(max_length=40) +class CreateContestSubmissionSerializer(serializers.Serializer): + contest_id = serializers.IntegerField() + problem_id = serializers.IntegerField() + language = serializers.IntegerField() + code = serializers.CharField(max_length=3000) + + diff --git a/submission/tests.py b/submission/tests.py index 96d06f07..a6342dad 100644 --- a/submission/tests.py +++ b/submission/tests.py @@ -4,8 +4,8 @@ from django.test import TestCase, Client from django.core.urlresolvers import reverse from account.models import User, REGULAR_USER, ADMIN, SUPER_ADMIN from problem.models import Problem -from contest.models import Contest -from contest.models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PUBLIC_CONTEST +from contest.models import Contest, ContestProblem +from contest.models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST from submission.models import Submission from rest_framework.test import APITestCase, APIClient @@ -193,7 +193,7 @@ class SubmissionPageTest(TestCase): hint="hint1", created_by=User.objects.get(username="test1")) self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1, - contest_type=PASSWORD_PUBLIC_CONTEST, show_rank=True, + contest_type=PUBLIC_CONTEST, show_rank=True, show_user_submission=True, start_time="2015-08-15T10:00:00.000Z", end_time="2015-08-15T12:00:00.000Z", @@ -203,3 +203,129 @@ class SubmissionPageTest(TestCase): language=1, code='#include "stdio.h"\nint main(){\n\treturn 0;\n}', problem_id=self.problem.id) + + +class ContestSubmissionAPITest(APITestCase): + def setUp(self): + self.client = APIClient() + self.url = reverse('contest_submission_api') + self.user1 = User.objects.create(username="test1", admin_type=SUPER_ADMIN) + self.user1.set_password("testaa") + self.user1.save() + self.user2 = User.objects.create(username="test2", admin_type=REGULAR_USER) + self.user2.set_password("testbb") + self.user2.save() + self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1, + contest_type=PUBLIC_CONTEST, show_rank=True, + show_user_submission=True, + start_time="2015-08-15T10:00:00.000Z", + end_time="2015-08-30T12:00:00.000Z", + created_by=User.objects.get(username="test1")) + self.contest_problem = ContestProblem.objects.create(title="titlex", + description="descriptionx", + input_description="input1_description", + output_description="output1_description", + test_case_id="1", + samples=json.dumps([{"input": "1 1", "output": "2"}]), + time_limit=100, + memory_limit=1000, + hint="hint1", + created_by=User.objects.get(username="test1"), + contest=Contest.objects.get(title="titlex"), + sort_index="a") + + # 以下是创建比赛的提交 + def test_invalid_format(self): + self.client.login(username="test1", password="testaa") + data = {"contest_id": self.global_contest.id, "language": 1} + response = self.client.post(self.url, data=data) + self.assertEqual(response.data["code"], 1) + + def test_contest_submission_successfully(self): + self.client.login(username="test1", password="testaa") + data = {"contest_id": self.global_contest.id, "problem_id": self.contest_problem.id, + "language": 1, "code": '#include "stdio.h"\nint main(){\n\treturn 0;\n}'} + response = self.client.post(self.url, data=data) + self.assertEqual(response.data["code"], 0) + + def test_contest_problem_does_not_exist(self): + self.client.login(username="test1", password="testaa") + data = {"contest_id": self.global_contest.id, "problem_id": self.contest_problem.id + 10, + "language": 1, "code": '#include "stdio.h"\nint main(){\n\treturn 0;\n}'} + response = self.client.post(self.url, data=data) + self.assertEqual(response.data, {"code": 1, "data": u"题目不存在"}) + + +class ContestSubmissionAdminAPITest(APITestCase): + def setUp(self): + self.client = APIClient() + self.url = reverse('contest_submission_admin_api_view') + self.userA = User.objects.create(username="test1", admin_type=ADMIN) + self.userA.set_password("testaa") + self.userA.save() + self.userS = User.objects.create(username="test2", admin_type=SUPER_ADMIN) + self.userS.set_password("testbb") + self.userS.save() + self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1, + contest_type=PASSWORD_PROTECTED_CONTEST, show_rank=True, + show_user_submission=True, + start_time="2015-08-15T10:00:00.000Z", + end_time="2015-08-15T12:00:00.000Z", + password="aacc", created_by=self.userS + ) + self.problem = ContestProblem.objects.create(title="title1", + description="description1", + input_description="input1_description", + output_description="output1_description", + test_case_id="1", + sort_index="1", + samples=json.dumps([{"input": "1 1", "output": "2"}]), + time_limit=100, + memory_limit=1000, + hint="hint1", + contest=self.global_contest, + created_by=self.userS) + self.submission = Submission.objects.create(user_id=self.userA.id, + language=1, + code='#include "stdio.h"\nint main(){\n\treturn 0;\n}', + problem_id=self.problem.id) + self.submissionS = Submission.objects.create(user_id=self.userS.id, + language=2, + code='#include "stdio.h"\nint main(){\n\treturn 0;\n}', + problem_id=self.problem.id) + + def test_submission_contest_does_not_exist(self): + self.client.login(username="test2", password="testbb") + response = self.client.get(self.url + "?contest_id=99") + self.assertEqual(response.data["code"], 1) + + def test_submission_contest_parameter_error(self): + self.client.login(username="test2", password="testbb") + response = self.client.get(self.url) + self.assertEqual(response.data["code"], 1) + + def test_submission_access_denied(self): + self.client.login(username="test1", password="testaa") + response = self.client.get(self.url + "?problem_id=" + str(self.problem.id)) + self.assertEqual(response.data["code"], 1) + + def test_submission_access_denied_with_contest_id(self): + self.client.login(username="test1", password="testaa") + response = self.client.get(self.url + "?contest_id=" + str(self.global_contest.id)) + self.assertEqual(response.data["code"], 1) + + def test_get_submission_successfully(self): + self.client.login(username="test2", password="testbb") + response = self.client.get( + self.url + "?contest_id=" + str(self.global_contest.id) + "&problem_id=" + str(self.problem.id)) + self.assertEqual(response.data["code"], 0) + + def test_get_submission_successfully_problem(self): + self.client.login(username="test2", password="testbb") + response = self.client.get(self.url + "?problem_id=" + str(self.problem.id)) + self.assertEqual(response.data["code"], 0) + + def test_get_submission_problem_do_not_exist(self): + self.client.login(username="test2", password="testbb") + response = self.client.get(self.url + "?problem_id=9999") + self.assertEqual(response.data["code"], 1) \ No newline at end of file diff --git a/submission/views.py b/submission/views.py index 100c5f45..e9eaa44e 100644 --- a/submission/views.py +++ b/submission/views.py @@ -5,27 +5,29 @@ import logging import redis from django.shortcuts import render from django.core.paginator import Paginator - from rest_framework.views import APIView from judge.judger_controller.tasks import judge -from judge.judger_controller.settings import redis_config from account.decorators import login_required, super_admin_required -from account.models import SUPER_ADMIN, User, REGULAR_USER - +from account.models import SUPER_ADMIN, User from problem.models import Problem from contest.models import ContestProblem, Contest -from announcement.models import Announcement +from contest.decorators import check_user_contest_permission from utils.shortcuts import serializer_invalid_response, error_response, success_response, error_page, paginate - +from utils.cache import get_cache_redis from .models import Submission from .serializers import (CreateSubmissionSerializer, SubmissionSerializer, - SubmissionhareSerializer, SubmissionRejudgeSerializer) - + SubmissionhareSerializer, SubmissionRejudgeSerializer, + CreateContestSubmissionSerializer) logger = logging.getLogger("app_info") +def _judge(submission_id, time_limit, memory_limit, test_case_id): + judge.delay(submission_id, time_limit, memory_limit, test_case_id) + get_cache_redis().incr("judge_queue_length") + + class SubmissionAPIView(APIView): @login_required def post(self, request): @@ -41,17 +43,16 @@ class SubmissionAPIView(APIView): problem = Problem.objects.get(id=data["problem_id"]) except Problem.DoesNotExist: return error_response(u"题目不存在") - submission = Submission.objects.create(user_id=request.user.id, language=int(data["language"]), - code=data["code"], problem_id=problem.id) + submission = Submission.objects.create(user_id=request.user.id, + language=int(data["language"]), + code=data["code"], + problem_id=problem.id) try: - judge.delay(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id) + _judge(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id) except Exception as e: logger.error(e) return error_response(u"提交判题任务失败") - r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"]) - r.incr("judge_queue_length") - return success_response({"submission_id": submission.id}) else: return serializer_invalid_response(serializer) @@ -71,6 +72,37 @@ class SubmissionAPIView(APIView): return success_response(response_data) +class ContestSubmissionAPIView(APIView): + @check_user_contest_permission + def post(self, request): + """ + 创建比赛的提交 + --- + request_serializer: CreateContestSubmissionSerializer + """ + serializer = CreateContestSubmissionSerializer(data=request.data) + if serializer.is_valid(): + data = serializer.data + contest = Contest.objects.get(id=data["contest_id"]) + try: + problem = ContestProblem.objects.get(contest=contest, id=data["problem_id"]) + except ContestProblem.DoesNotExist: + return error_response(u"题目不存在") + submission = Submission.objects.create(user_id=request.user.id, + language=int(data["language"]), + contest_id=contest.id, + code=data["code"], + problem_id=problem.id) + try: + _judge(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id) + except Exception as e: + logger.error(e) + return error_response(u"提交判题任务失败") + return success_response({"submission_id": submission.id}) + else: + return serializer_invalid_response(serializer) + + @login_required def problem_my_submissions_list_page(request, problem_id): """ @@ -81,8 +113,8 @@ def problem_my_submissions_list_page(request, problem_id): except Problem.DoesNotExist: return error_page(request, u"问题不存在") - submissions = Submission.objects.filter(user_id=request.user.id, problem_id=problem.id, - contest_id__isnull=True).order_by("-create_time"). \ + submissions = Submission.objects.filter(user_id=request.user.id, problem_id=problem.id,contest_id__isnull=True).\ + order_by("-create_time"). \ values("id", "result", "create_time", "accepted_answer_time", "language") return render(request, "oj/submission/problem_my_submissions_list.html", @@ -119,17 +151,13 @@ def my_submission(request, submission_id): except Submission.DoesNotExist: return error_page(request, u"提交不存在") - if submission.contest_id: - try: - problem = ContestProblem.objects.get(id=submission.problem_id, - visible=True) - except ContestProblem.DoesNotExist: - return error_page(request, u"提交不存在") - else: - try: + try: + if submission.contest_id: + problem = ContestProblem.objects.get(id=submission.problem_id, visible=True) + else: problem = Problem.objects.get(id=submission.problem_id, visible=True) - except Problem.DoesNotExist: - return error_page(request, u"提交不存在") + except Exception: + return error_page(request, u"提交不存在") if submission.info: try: @@ -145,6 +173,7 @@ def my_submission(request, submission_id): class SubmissionAdminAPIView(APIView): + @super_admin_required def get(self, request): problem_id = request.GET.get("problem_id", None) if not problem_id: @@ -164,7 +193,8 @@ def my_submission_list_page(request, page=1): submissions = Submission.objects.filter(contest_id__isnull=True) else: submissions = Submission.objects.filter(user_id=request.user.id, contest_id__isnull=True) - submissions = submissions.values("id", "user_id", "problem_id", "result", "create_time", "accepted_answer_time", "language").order_by("-create_time") + submissions = submissions.values("id", "user_id", "problem_id", "result", "create_time", "accepted_answer_time", + "language").order_by("-create_time") language = request.GET.get("language", None) filter = None @@ -238,24 +268,42 @@ class SubmissionRejudgeAdminAPIView(APIView): serializer = SubmissionRejudgeSerializer(data=request.data) if serializer.is_valid(): submission_id = serializer.data["submission_id"] + # 目前只考虑前台公开题目的重新判题 try: - submission = Submission.objects.get(id=submission_id) + submission = Submission.objects.get(id=submission_id, contest_id__isnull=True) except Submission.DoesNotExist: return error_response(u"提交不存在") - # 目前只考虑前台公开题目的重新判题 + try: problem = Problem.objects.get(id=submission.problem_id) except Problem.DoesNotExist: return error_response(u"题目不存在") try: - judge.delay(submission_id, problem.time_limit, problem.memory_limit, problem.test_case_id) + _judge(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id) except Exception as e: logger.error(e) return error_response(u"提交判题任务失败") - - # 增加redis 中判题队列长度的计数器 - r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"]) - r.incr("judge_queue_length") return success_response(u"任务提交成功") else: - return serializer_invalid_response(serializer) \ No newline at end of file + return serializer_invalid_response(serializer) + + +class ContestSubmissionAdminAPIView(APIView): + @check_user_contest_permission + def get(self, request): + """ + 查询比赛提交,单个比赛题目提交的adminAPI + --- + response_serializer: SubmissionSerializer + """ + problem_id = request.GET.get("problem_id", None) + contest_id = request.GET.get("contest_id", None) + + # 需要 problem_id 和 contest_id 两个参数 否则会在check_user_contest_permission 的时候被拦截 + if problem_id: + submissions = Submission.objects.filter(contest_id=contest_id, problem_id=problem_id).order_by("-create_time") + # 需要 contest_id 参数 + else: + submissions = Submission.objects.filter(contest_id=contest_id).order_by("-create_time") + + return paginate(request, submissions, SubmissionSerializer) diff --git a/template/src/oj/account/oauth.html b/template/src/oj/account/oauth.html new file mode 100644 index 00000000..2713d19c --- /dev/null +++ b/template/src/oj/account/oauth.html @@ -0,0 +1,24 @@ +{% extends "oj_base.html" %} +{% block title %} + 授权登录 +{% endblock %} +{% block body %} +
+
+ {% if request.user.is_authenticated %} +

3秒钟后将跳转到{{ callback }}

+ + {% else %} + + {% endif %} + + +
+
+{% endblock %} +{% block js_block %} + +{% endblock %} \ No newline at end of file diff --git a/template/src/utils/reset_password_email.html b/template/src/utils/reset_password_email.html index 3852adb2..f4c8b936 100644 --- a/template/src/utils/reset_password_email.html +++ b/template/src/utils/reset_password_email.html @@ -8,7 +8,7 @@ - Online Judge + {{ website_name }} 密码找回邮件 @@ -32,7 +32,7 @@ - 您刚刚在 青岛大学在线评测系统 使用了找回密码功能。 + 您刚刚在 {{ website_name }} 使用了找回密码功能。