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:
@@ -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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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})
|
||||||
|
|
||||||
# 计算人数完成率
|
# 计算人数完成率
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user