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>
This commit is contained in:
2026-08-05 23:58:13 -06:00
parent fda7515263
commit 625f2466e5
5 changed files with 24 additions and 29 deletions

View File

@@ -22,17 +22,12 @@ from ..serializers import (
) )
# ks251XXX 或者 ks2510XX 返回 251 或者 2510 # ks251XXX 或者 ks2510XX 返回 251 或者 2510,其他返回 None。
# 其他返回 None # 班级号限定 3~4 位,与前端 ButtonWithSearch 的 /^ks\d{3,4}/ 保持一致;
# 原来的 \d+ 会贪婪吃掉后面的数字ks251001 会返回 251001 而不是 251
def get_class_name(username): def get_class_name(username):
if username.startswith("ks"): result = re.match(r"ks(\d{3,4})", username)
result = re.search(r"ks\d+", username) return result.group(1) if result else None
if result:
return result.group(0)[2:]
else:
return None
else:
return None
class UserAdminAPI(APIView): class UserAdminAPI(APIView):

View File

@@ -19,7 +19,7 @@ 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 from utils.shortcuts import get_env, strip_class_prefix
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
@@ -239,8 +239,5 @@ class ClassUsernamesAPI(AsyncAPIView):
classroom = request.GET.get("classroom", "") classroom = request.GET.get("classroom", "")
if not classroom: if not classroom:
return self.error("需要班级号") return self.error("需要班级号")
prefix = f"ks{classroom}" names = [strip_class_prefix(user.username, classroom) async for user in User.objects.filter(class_name=classroom).order_by("-create_time")]
names = [
user.username[len(prefix) :] if user.username.startswith(prefix) else user.username async for user in User.objects.filter(class_name=classroom).order_by("-create_time")
]
return self.success(names) return self.success(names)

View File

@@ -8,6 +8,7 @@ from account.decorators import teacher_admin_required
from account.models import AdminType, User from account.models import AdminType, User
from problem.models import Problem from problem.models import Problem
from utils.api import APIView from utils.api import APIView
from utils.shortcuts import strip_class_prefix
from ..models import FlowchartSubmission, FlowchartSubmissionStatus from ..models import FlowchartSubmission, FlowchartSubmissionStatus
@@ -64,12 +65,6 @@ for _w in CUSTOM_WORDS:
jieba.add_word(_w, freq=9999) jieba.add_word(_w, freq=9999)
def get_real_name(username, class_name):
if class_name and username.startswith("ks"):
return username[len(f"ks{class_name}") :]
return username
class FlowchartStatisticsAPI(APIView): class FlowchartStatisticsAPI(APIView):
@teacher_admin_required @teacher_admin_required
def get(self, request): def get(self, request):
@@ -169,7 +164,7 @@ class FlowchartStatisticsAPI(APIView):
if all_users_dict: if all_users_dict:
for uname in set(all_users_dict.keys()) - submitted_users: for uname in set(all_users_dict.keys()) - submitted_users:
class_name = all_users_dict[uname] class_name = all_users_dict[uname]
real_name = get_real_name(uname, class_name) real_name = strip_class_prefix(uname, class_name)
unaccepted.append({"username": uname, "real_name": real_name}) unaccepted.append({"username": uname, "real_name": real_name})
# 5. Word cloud from feedback + suggestions + criteria comments # 5. Word cloud from feedback + suggestions + criteria comments

View File

@@ -5,16 +5,11 @@ from account.models import AdminType, User
from judge.tasks import judge_task from judge.tasks import judge_task
from problem.models import Problem from problem.models import Problem
from utils.api import APIView from utils.api import APIView
from utils.shortcuts import strip_class_prefix
from ..models import JudgeStatus, Submission from ..models import JudgeStatus, Submission
def get_real_name(username, class_name):
if class_name and username.startswith("ks"):
return username[len(f"ks{class_name}") :]
return username
class SubmissionRejudgeAPI(APIView): class SubmissionRejudgeAPI(APIView):
@super_admin_required @super_admin_required
def get(self, request): def get(self, request):
@@ -132,7 +127,7 @@ class SubmissionStatisticsAPI(APIView):
unaccepted_usernames = set(all_users_dict.keys()) - submitted_usernames unaccepted_usernames = set(all_users_dict.keys()) - submitted_usernames
for username in unaccepted_usernames: for username in unaccepted_usernames:
class_name = all_users_dict[username] class_name = all_users_dict[username]
real_name = get_real_name(username, class_name) real_name = strip_class_prefix(username, class_name)
unaccepted.append({"username": username, "real_name": real_name}) unaccepted.append({"username": username, "real_name": real_name})
# 计算人数完成率 # 计算人数完成率

View File

@@ -36,6 +36,19 @@ def build_query_string(kv_data, ignore_none=True):
return query_string return query_string
def strip_class_prefix(username, class_name):
"""
去掉用户名里的 ks<班级号> 前缀,得到学生本人那一段。
用户名形如 ks251张三class_name 为 251 时返回 张三。
用 removeprefix 而不是按长度切片:前缀对不上时原样返回,
不会从中间截出乱码。
"""
if not class_name:
return username
return username.removeprefix(f"ks{class_name}")
def datetime2str(value, format="iso-8601"): def datetime2str(value, format="iso-8601"):
if format.lower() == "iso-8601": if format.lower() == "iso-8601":
value = value.isoformat() value = value.isoformat()