Files
OnlineJudge/submission/views/admin.py
yuetsh 625f2466e5 refactor: 统一用户名的 ks 班级前缀处理
同一个剥前缀函数在 submission 和 flowchart 的管理端各有一份逐字相同的
拷贝,conf 里还有一份内联写法,合并到 utils/shortcuts.strip_class_prefix。

改名是因为原名 get_real_name 和 UserProfile.real_name 字段、以及三个
serializer 里的同名方法都容易混。

行为上修了两处:

- 剥前缀改用 removeprefix,不再按长度硬切。班级号对不上时原样返回,
  旧写法会从中间截出乱码(ks999王五 配 class_name=251 会切成「王五」)
- get_class_name 的正则从 \d+ 收紧到 \d{3,4},与前端
  ButtonWithSearch 的 /^ks\d{3,4}/ 对齐。旧的贪婪匹配在姓名部分是纯
  数字时会吃掉整串(ks251001 返回 251001 而不是 251)。顺带 re.search
  换成 re.match,外层的 startswith 判断并进正则

正常数据(ks251张三、ks2510李四)新旧结果一致,已逐例对拍。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 23:58:13 -06:00

153 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.db.models import Count, Q
from account.decorators import super_admin_required, teacher_admin_required
from account.models import AdminType, User
from judge.tasks import judge_task
from problem.models import Problem
from utils.api import APIView
from utils.shortcuts import strip_class_prefix
from ..models import JudgeStatus, Submission
class SubmissionRejudgeAPI(APIView):
@super_admin_required
def get(self, request):
id = request.GET.get("id")
if not id:
return self.error("Parameter error, id is required")
try:
submission = Submission.objects.select_related("problem").get(id=id, contest_id__isnull=True)
except Submission.DoesNotExist:
return self.error("Submission does not exists")
submission.statistic_info = {}
submission.save()
judge_task.send(submission.id, submission.problem.id)
return self.success()
class SubmissionStatisticsAPI(APIView):
@teacher_admin_required
def get(self, request):
start = request.GET.get("start")
end = request.GET.get("end")
if not end:
return self.error("end is required")
filters = {"contest_id__isnull": True, "create_time__lte": end}
if start:
filters["create_time__gte"] = start
submissions = Submission.objects.filter(**filters).select_related("problem__created_by")
problem_id = request.GET.get("problem_id")
if problem_id:
try:
problem = Problem.objects.get(_id__iexact=problem_id, contest_id__isnull=True, visible=True)
except Problem.DoesNotExist:
return self.error("Problem doesn't exist")
submissions = submissions.filter(problem=problem)
username = request.GET.get("username")
all_users_dict = {}
if username:
submissions = submissions.filter(username__icontains=username)
all_users_dict = {
user["username"]: user["class_name"]
for user in User.objects.filter(
username__icontains=username,
is_disabled=False,
admin_type=AdminType.REGULAR_USER,
).values("username", "class_name")
}
# 优化:一次性获取所有统计数据
submission_stats = submissions.aggregate(
total_count=Count("id"),
accepted_count=Count("id", filter=Q(result__in=[JudgeStatus.ACCEPTED, JudgeStatus.AST_CHECK_FAILED])),
)
submission_count = submission_stats["total_count"]
accepted_count = submission_stats["accepted_count"]
correct_rate = round(accepted_count / submission_count * 100, 2) if submission_count else 0
# 优化:获取用户提交统计
user_submissions = (
submissions.values("username")
.annotate(
submission_count=Count("id"),
accepted_count=Count("id", filter=Q(result__in=[JudgeStatus.ACCEPTED, JudgeStatus.AST_CHECK_FAILED])),
)
.order_by("-submission_count")
)
# 获取所有有提交记录的用户的class_name信息
submitted_usernames = {item["username"] for item in user_submissions}
if submitted_usernames:
submitted_users_dict = {user["username"]: user["class_name"] for user in User.objects.filter(username__in=submitted_usernames).values("username", "class_name")}
else:
submitted_users_dict = {}
# 预先收集每个用户的提交ID和结果按时间倒序
submission_items_by_user = {}
for submission in submissions.values("username", "id", "result").order_by("-create_time"):
username_key = submission["username"]
submission_id = str(submission["id"])
submission_items_by_user.setdefault(username_key, []).append(
{
"id": submission_id,
"result": submission["result"],
}
)
# 处理有提交记录的用户
accepted = []
for item in user_submissions:
username_key = item["username"]
if item["accepted_count"] > 0:
rate = round(item["accepted_count"] / item["submission_count"] * 100, 2)
accepted.append(
{
"username": username_key,
"class_name": submitted_users_dict.get(username_key),
"submission_count": item["submission_count"],
"accepted_count": item["accepted_count"],
"correct_rate": f"{rate}%",
"submission_items": submission_items_by_user.get(username_key, []),
}
)
# 处理无提交记录的用户,只返回姓名列表
unaccepted = []
if all_users_dict:
unaccepted_usernames = set(all_users_dict.keys()) - submitted_usernames
for username in unaccepted_usernames:
class_name = all_users_dict[username]
real_name = strip_class_prefix(username, class_name)
unaccepted.append({"username": username, "real_name": real_name})
# 计算人数完成率
person_count = len(all_users_dict) if all_users_dict else 0
person_rate = 0
if person_count:
person_rate = min(100, round(len(accepted) / person_count * 100, 2))
# 处理已删除用户但提交记录仍存在的情况
if person_count < len(accepted):
person_count = len(accepted)
return self.success(
{
"submission_count": submission_count,
"accepted_count": accepted_count,
"correct_rate": f"{correct_rate}%",
"person_count": person_count,
"person_rate": f"{person_rate}%",
"data": accepted,
"data_unaccepted": unaccepted,
}
)