fix: 修掉重写期间在旧后端发现的 6 个问题
这些是做 Bun 重写时逐行对照发现的,旧后端现在还在线上跑,学生今天就在踩。 每条都在本地起 Django + postgres 实跑验证过,**正反两面都验**:越权要挡住、 正常操作要还能用。合计 14 条断言全过。 ## 越权(跨教师) **ACMContestHelper.put** — 只按 pk 取 rank,任何老师都能改**别人比赛**里的 检查标记。而 `contest_id` 明明在 ACMContesHelperSerializer 里、客户端一直在传, 视图完全没用它。改成按 (pk, contest_id) 取 + ensure_created_by。 同类的 get 本来就有 ensure_created_by,是 put 漏了。 **AddContestProblemAPI.post** — 一个权限装饰器都没有,导致两件事: - 少 problem_permission_required:problem_permission=None 的学生管理员也能建题 - 少 ensure_created_by:任何管理员都能往**别人的比赛**里塞题 另外源题只按 id 取,能把别人比赛里的赛题(或别人未公开的草稿)拷进自己的比赛, 连 answers 参考答案一起拿到。改成源题必须是公开题库题,不可见的要求归属。 (中间件 AdminRoleRequiredMiddleware 只保证"是管理员身份",挡不住这些。) ## 会 500 **ProblemVisibleAPI.put** — `self.error(...)` 漏写 return,题不存在时继续执行到 未赋值的 problem,抛 UnboundLocalError → 500。 ## 数据正确性 **ProblemSetAdminAPI.get** — 后台列表写死 filter(visible=True),而列表里就有 可见性开关:一旦关掉,题单从后台消失,教师再也找不到、也无法在界面上打开回来。 后台列表不该过滤 visible,前台该。 **增删题单题目不重算进度** — 题目数是进度的分母。不重算的话学生进度百分比全是 虚高的,原本标"已完成"的人会一直是已完成。更麻烦的是那个手动同步接口 (ProblemSetSyncAPI)前端没有调用点,等于**界面上根本没办法修**。 改成增删之后直接 sync_all_progress_for_problemset。 **移除用户留下孤儿奖章** — 只删了 ProblemSetProgress,UserBadge 还在。学生个人页 会一直挂着一个自己已不在的题单的奖章,而且重新加入时因唯一约束拿不到第二次。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -484,15 +484,26 @@ class MakeContestProblemPublicAPIView(APIView):
|
||||
|
||||
|
||||
class AddContestProblemAPI(APIView):
|
||||
@problem_permission_required
|
||||
@validate_serializer(AddContestProblemSerializer)
|
||||
def post(self, request):
|
||||
data = request.data
|
||||
try:
|
||||
contest = Contest.objects.get(id=data["contest_id"])
|
||||
problem = Problem.objects.get(id=data["problem_id"])
|
||||
# 源题必须是**公开题库**里的题。原来只按 id 取,于是能把别人比赛里的赛题
|
||||
# (或别人尚未公开的草稿)拷进自己的比赛,连带 answers 参考答案一起拿到。
|
||||
problem = Problem.objects.get(id=data["problem_id"], contest__isnull=True)
|
||||
except (Contest.DoesNotExist, Problem.DoesNotExist):
|
||||
return self.error("Contest or Problem does not exist")
|
||||
|
||||
# 原来这里一个权限装饰器都没有:
|
||||
# - 少了 problem_permission_required,problem_permission=None 的学生管理员也能建题
|
||||
# - 少了 ensure_created_by,任何管理员都能往别人的比赛里塞题
|
||||
# 中间件只保证了"是管理员身份",挡不住这两件事。
|
||||
ensure_created_by(contest, request.user)
|
||||
if not problem.visible:
|
||||
ensure_created_by(problem, request.user)
|
||||
|
||||
if contest.status == ContestStatus.CONTEST_ENDED:
|
||||
return self.error("Contest has ended")
|
||||
if Problem.objects.filter(contest=contest, _id=data["display_id"]).exists():
|
||||
@@ -604,7 +615,9 @@ class ProblemVisibleAPI(APIView):
|
||||
try:
|
||||
problem = Problem.objects.get(id=data["id"])
|
||||
except Problem.DoesNotExist:
|
||||
self.error("problem does not exists")
|
||||
# 这里原来漏了 return,会继续往下走到未赋值的 problem,
|
||||
# 抛 UnboundLocalError → 500。题不存在时应该是一条正常的错误响应。
|
||||
return self.error("problem does not exists")
|
||||
problem.visible = not problem.visible
|
||||
problem.save()
|
||||
return self.success()
|
||||
|
||||
Reference in New Issue
Block a user