diff --git a/problemset/models.py b/problemset/models.py index 7feeeba..2dd3e9f 100644 --- a/problemset/models.py +++ b/problemset/models.py @@ -110,16 +110,20 @@ class ProblemSetBadge(models.Model): from django.db import transaction user_progresses = ProblemSetProgress.objects.filter(problemset=self.problemset) - new_badges = [UserBadge(user=progress.user, badge=self) for progress in user_progresses if self._is_eligible(progress)] + eligible_user_ids = {p.user_id for p in user_progresses if self._is_eligible(p)} with transaction.atomic(): - UserBadge.objects.filter(badge=self).delete() + # 移除不再满足条件的用户徽章 + UserBadge.objects.filter(badge=self).exclude(user_id__in=eligible_user_ids).delete() + # 为新满足条件的用户授予徽章(保留已有记录的 earned_time) + existing_user_ids = set(UserBadge.objects.filter(badge=self).values_list("user_id", flat=True)) + new_badges = [UserBadge(user_id=uid, badge=self) for uid in eligible_user_ids - existing_user_ids] if new_badges: UserBadge.objects.bulk_create(new_badges) def _is_eligible(self, progress): """判断用户进度是否满足该徽章条件(纯逻辑,不查数据库)""" if self.condition_type == BadgeConditionType.ALL_PROBLEMS: - return progress.completed_problems_count == progress.total_problems_count + return progress.total_problems_count > 0 and progress.completed_problems_count == progress.total_problems_count if self.condition_type == BadgeConditionType.PROBLEM_COUNT: return progress.completed_problems_count >= self.condition_value if self.condition_type == BadgeConditionType.SCORE: diff --git a/problemset/serializers.py b/problemset/serializers.py index 8aac2c0..9a48e18 100644 --- a/problemset/serializers.py +++ b/problemset/serializers.py @@ -304,4 +304,17 @@ class UpdateProgressSerializer(serializers.Serializer): problemset_id = serializers.IntegerField() problem_id = serializers.IntegerField() - submission_id = serializers.CharField(required=False) + submission_id = serializers.CharField() + + +class ProblemSetVisibleSerializer(serializers.Serializer): + """切换题单可见性序列化器""" + + id = serializers.IntegerField() + + +class ProblemSetUpdateStatusSerializer(serializers.Serializer): + """更新题单状态序列化器""" + + id = serializers.IntegerField() + status = serializers.ChoiceField(choices=ProblemSetStatus.choices) diff --git a/problemset/urls/admin.py b/problemset/urls/admin.py index 0f60737..4382633 100644 --- a/problemset/urls/admin.py +++ b/problemset/urls/admin.py @@ -14,6 +14,17 @@ from problemset.views.admin import ( urlpatterns = [ # 管理员题单管理API path("problemset", ProblemSetAdminAPI.as_view(), name="admin_problemset_api"), + # 题单状态管理API — 必须在 之前,否则被整数路由遮蔽 + path( + "problemset/visible", + ProblemSetVisibleAPI.as_view(), + name="admin_problemset_visible_api", + ), + path( + "problemset/status", + ProblemSetStatusAPI.as_view(), + name="admin_problemset_status_api", + ), path( "problemset/", ProblemSetDetailAdminAPI.as_view(), @@ -57,15 +68,4 @@ urlpatterns = [ ProblemSetSyncAPI.as_view(), name="admin_problemset_sync_api", ), - # 题单状态管理API - path( - "problemset/visible", - ProblemSetVisibleAPI.as_view(), - name="admin_problemset_visible_api", - ), - path( - "problemset/status", - ProblemSetStatusAPI.as_view(), - name="admin_problemset_status_api", - ), ] diff --git a/problemset/views/admin.py b/problemset/views/admin.py index d7a2ef0..8d2df2f 100644 --- a/problemset/views/admin.py +++ b/problemset/views/admin.py @@ -1,4 +1,4 @@ -from django.db.models import Q +from django.db.models import Count, Q from account.decorators import ensure_created_by, super_admin_required from problem.models import Problem @@ -7,7 +7,6 @@ from problemset.models import ( ProblemSetBadge, ProblemSetProblem, ProblemSetProgress, - ProblemSetStatus, ) from problemset.serializers import ( AddProblemToSetSerializer, @@ -21,6 +20,8 @@ from problemset.serializers import ( ProblemSetProblemSerializer, ProblemSetProgressSerializer, ProblemSetSerializer, + ProblemSetUpdateStatusSerializer, + ProblemSetVisibleSerializer, ) from utils.api import APIView, validate_serializer @@ -31,7 +32,7 @@ class ProblemSetAdminAPI(APIView): @super_admin_required def get(self, request): """获取题单列表(管理员)""" - problem_sets = ProblemSet.objects.all().order_by("-create_time") + problem_sets = ProblemSet.objects.filter(visible=True).annotate(problems_count=Count("problemsetproblem", distinct=True)).order_by("-create_time") # 过滤条件 keyword = request.GET.get("keyword", "").strip() @@ -72,10 +73,8 @@ class ProblemSetAdminAPI(APIView): except ProblemSet.DoesNotExist: return self.error("题单不存在") - # 更新题单信息 for key, value in data.items(): - if key != "id": - setattr(problem_set, key, value) + setattr(problem_set, key, value) problem_set.save() return self.success(ProblemSetSerializer(problem_set).data) @@ -93,10 +92,7 @@ class ProblemSetAdminAPI(APIView): except ProblemSet.DoesNotExist: return self.error("题单不存在") - # 软删除:设置为不可见 - problem_set.visible = False - problem_set.save() - + problem_set.delete() return self.success("题单已删除") @@ -165,9 +161,6 @@ class ProblemSetProblemAdminAPI(APIView): hint=data.get("hint", ""), ) - # 同步所有用户的进度 - ProblemSetProgress.sync_all_progress_for_problemset(problem_set) - return self.success("题目已添加到题单") @super_admin_required @@ -198,9 +191,6 @@ class ProblemSetProblemAdminAPI(APIView): problem_set_problem.save() - # 同步所有用户的进度 - ProblemSetProgress.sync_all_progress_for_problemset(problem_set) - return self.success("题目已更新") @super_admin_required @@ -215,10 +205,6 @@ 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("题目不在该题单中") @@ -289,19 +275,11 @@ class ProblemSetBadgeAdminAPI(APIView): if "condition_value" in data: badge.condition_value = data["condition_value"] condition_changed = True - if "level" in data: - badge.level = data["level"] - badge.save() + badge.save() # post_save 信号自动触发 recalculate_user_badges - # 如果修改了条件,重新计算所有用户的徽章资格 if condition_changed: - try: - badge.recalculate_user_badges() - return self.success("奖章已更新,并重新计算了所有用户的徽章资格") - except Exception as e: - return self.error(f"奖章已更新,但重新计算徽章资格时出错: {str(e)}") - + return self.success("奖章已更新,并重新计算了所有用户的徽章资格") return self.success("奖章已更新") @super_admin_required @@ -376,6 +354,7 @@ class ProblemSetVisibleAPI(APIView): """题单可见性管理API""" @super_admin_required + @validate_serializer(ProblemSetVisibleSerializer) def put(self, request): """切换题单可见性""" data = request.data @@ -394,6 +373,7 @@ class ProblemSetStatusAPI(APIView): """题单状态管理API""" @super_admin_required + @validate_serializer(ProblemSetUpdateStatusSerializer) def put(self, request): """更新题单状态""" data = request.data @@ -403,10 +383,6 @@ class ProblemSetStatusAPI(APIView): except ProblemSet.DoesNotExist: return self.error("题单不存在") - status = data.get("status") - if status not in ProblemSetStatus.values: - return self.error("无效的状态") - - problem_set.status = status + problem_set.status = data["status"] problem_set.save() return self.success() diff --git a/problemset/views/oj.py b/problemset/views/oj.py index 6818607..f1309af 100644 --- a/problemset/views/oj.py +++ b/problemset/views/oj.py @@ -1,6 +1,7 @@ from django.db.models import Avg, Count, Prefetch, Q from django.utils import timezone +from account.decorators import admin_role_required, login_required from account.models import User from problem.models import Problem from problemset.models import ( @@ -23,7 +24,7 @@ from problemset.serializers import ( UpdateProgressSerializer, UserBadgeSerializer, ) -from submission.models import Submission +from submission.models import JudgeStatus, Submission from utils.api import APIView, validate_serializer @@ -125,6 +126,7 @@ class ProblemSetProblemAPI(APIView): class ProblemSetProgressAPI(APIView): """题单进度API""" + @login_required @validate_serializer(JoinProblemSetSerializer) def post(self, request): """加入题单""" @@ -143,6 +145,7 @@ class ProblemSetProgressAPI(APIView): return self.success("成功加入题单") + @login_required def get(self, request, problem_set_id): """获取题单进度""" try: @@ -158,6 +161,7 @@ class ProblemSetProgressAPI(APIView): serializer = ProblemSetProgressSerializer(progress) return self.success(serializer.data) + @login_required @validate_serializer(UpdateProgressSerializer) def put(self, request): """更新进度""" @@ -172,10 +176,20 @@ class ProblemSetProgressAPI(APIView): except ProblemSetProgress.DoesNotExist: return self.error("未加入该题单") - # 更新详细进度 problem_id = str(data["problem_id"]) + submission_id = data.get("submission_id") + + if not submission_id: + return self.error("需要提供提交记录ID") + + try: + submission = Submission.objects.get(id=submission_id, user=request.user) + except Submission.DoesNotExist: + return self.error("提交记录不存在") + + if submission.result != JudgeStatus.ACCEPTED: + return self.error("只有通过的提交才能更新进度") - # 获取该题目在题单中的分值 try: problemset_problem = ProblemSetProblem.objects.get(problemset=problem_set, problem_id=problem_id) problem_score = problemset_problem.score @@ -183,38 +197,24 @@ class ProblemSetProgressAPI(APIView): problem_score = 0 progress.progress_detail[problem_id] = { - "score": problem_score, # 题单中设置的分值 - "submit_time": data.get("submit_time", timezone.now().isoformat()), + "score": problem_score, + "submit_time": timezone.now().isoformat(), } - - # 更新进度 progress.update_progress() - # 只有当提供了submission_id时才创建ProblemSetSubmission记录 - if "submission_id" in data and data["submission_id"]: - try: - submission = Submission.objects.get(id=data["submission_id"]) - problem = Problem.objects.get(id=problem_id) - - has_accepted = ProblemSetSubmission.objects.filter( + try: + problem = Problem.objects.get(id=problem_id) + if not ProblemSetSubmission.objects.filter(problemset=problem_set, user=request.user, problem=problem).exists(): + ProblemSetSubmission.objects.create( problemset=problem_set, user=request.user, + submission=submission, problem=problem, - ).exists() - if not has_accepted: - ProblemSetSubmission.objects.create( - problemset=problem_set, - user=request.user, - submission=submission, - problem=problem, - ) - except Submission.DoesNotExist: - # 如果提交记录不存在,记录错误但不中断流程 - pass + ) + except Problem.DoesNotExist: + pass - # 检查是否获得奖章 self._check_badges(progress) - return self.success("进度已更新") def _check_badges(self, progress): @@ -226,7 +226,7 @@ class ProblemSetProgressAPI(APIView): continue if badge.condition_type == BadgeConditionType.ALL_PROBLEMS: - if progress.completed_problems_count == progress.total_problems_count: + if progress.total_problems_count > 0 and progress.completed_problems_count == progress.total_problems_count: UserBadge.objects.create(user=progress.user, badge=badge) elif badge.condition_type == BadgeConditionType.PROBLEM_COUNT: if progress.completed_problems_count >= badge.condition_value: @@ -239,6 +239,7 @@ class ProblemSetProgressAPI(APIView): class UserProgressAPI(APIView): """用户进度API""" + @login_required def get(self, request): """获取用户的题单进度列表""" progress_list = ProblemSetProgress.objects.filter(user=request.user).order_by("-join_time") @@ -287,6 +288,7 @@ class ProblemSetBadgeAPI(APIView): class ProblemSetUserProgressAPI(APIView): """题单用户进度列表API""" + @admin_role_required def get(self, request, problem_set_id: int): """获取题单的用户进度列表""" try: