diff --git a/contest/views/admin.py b/contest/views/admin.py index 4085edc..ab695f8 100644 --- a/contest/views/admin.py +++ b/contest/views/admin.py @@ -201,10 +201,14 @@ class ACMContestHelper(APIView): @validate_serializer(ACMContesHelperSerializer) def put(self, request): data = request.data + # 原来只按 pk 取,contest_id 明明在序列化器里、客户端一直在传,却完全没用上。 + # 于是任何老师都能改别人比赛里的检查标记 —— teacher_admin_required 只保证 + # "是老师"。同类的 get 是有 ensure_created_by 的,这里补齐。 try: - rank = ACMContestRank.objects.get(pk=data["rank_id"]) + rank = ACMContestRank.objects.get(pk=data["rank_id"], contest_id=data["contest_id"]) except ACMContestRank.DoesNotExist: return self.error("Rank id does not exist") + ensure_created_by(rank.contest, request.user) problem_rank_status = rank.submission_info.get(data["problem_id"]) if not problem_rank_status: return self.error("Problem id does not exist") diff --git a/problem/views/admin.py b/problem/views/admin.py index 1e56409..24bd905 100644 --- a/problem/views/admin.py +++ b/problem/views/admin.py @@ -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() diff --git a/problemset/views/admin.py b/problemset/views/admin.py index 655cc7c..a193380 100644 --- a/problemset/views/admin.py +++ b/problemset/views/admin.py @@ -7,6 +7,7 @@ from problemset.models import ( ProblemSetBadge, ProblemSetProblem, ProblemSetProgress, + UserBadge, ) from problemset.serializers import ( AddProblemToSetSerializer, @@ -32,7 +33,10 @@ class ProblemSetAdminAPI(APIView): @teacher_admin_required def get(self, request): """获取题单列表(管理员)""" - problem_sets = ProblemSet.objects.filter(visible=True).annotate(problems_count=Count("problemsetproblem", distinct=True)).order_by("-create_time") + # 后台列表**不能**过滤 visible:题单列表里有可见性开关,一旦关掉, + # 题单就从这个列表里消失,教师再也找不到它、也就无法在界面上打开回来。 + # 前台列表(views/oj.py)该过滤,后台不该。 + problem_sets = ProblemSet.objects.annotate(problems_count=Count("problemsetproblem", distinct=True)).order_by("-create_time") if not request.user.is_super_admin(): problem_sets = problem_sets.filter(created_by=request.user) @@ -162,6 +166,10 @@ class ProblemSetProblemAdminAPI(APIView): score=data.get("score", 0), hint=data.get("hint", ""), ) + # 题目数就是进度的分母,加完必须重算:不重算的话已加入的学生进度百分比 + # 全是虚高的,而且原本标成"已完成"的人会一直是已完成。 + # 手动同步接口(ProblemSetSyncAPI)前端没有调用点,所以只能在这里做。 + ProblemSetProgress.sync_all_progress_for_problemset(problem_set) return self.success("题目已添加到题单") @@ -207,6 +215,8 @@ class ProblemSetProblemAdminAPI(APIView): try: problem_set_problem = ProblemSetProblem.objects.get(id=problem_set_problem_id, problemset=problem_set) problem_set_problem.delete() + # 同上:分母变了,进度要重算 + ProblemSetProgress.sync_all_progress_for_problemset(problem_set) return self.success("题目已从题单中移除") except ProblemSetProblem.DoesNotExist: return self.error("题目不在该题单中") @@ -329,6 +339,9 @@ class ProblemSetProgressAdminAPI(APIView): try: progress = ProblemSetProgress.objects.get(problemset=problem_set, user_id=user_id) progress.delete() + # 奖章是靠这个题单挣的,人移出去了奖章却留着 —— 学生个人页上会一直挂着 + # 一个自己已经不在的题单的奖章,而且重新加入时会因唯一约束拿不到第二次。 + UserBadge.objects.filter(user_id=user_id, badge__problemset=problem_set).delete() return self.success("用户已从题单中移除") except ProblemSetProgress.DoesNotExist: return self.error("用户未加入该题单")