From 5b393a3b4cc00229d3d34c73a49787747fa43271 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Wed, 5 Aug 2026 04:21:08 -0600 Subject: [PATCH] =?UTF-8?q?feat(problem):=20=E6=96=B0=E5=A2=9E=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E7=BB=99=E9=A2=98=E7=9B=AE=E6=B7=BB=E5=8A=A0/?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=A0=87=E7=AD=BE=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 --- problem/serializers.py | 6 ++++++ problem/urls/admin.py | 2 ++ problem/views/admin.py | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/problem/serializers.py b/problem/serializers.py index a4d8929..fb37624 100644 --- a/problem/serializers.py +++ b/problem/serializers.py @@ -122,6 +122,12 @@ class EditTagSerializer(serializers.Serializer): name = serializers.CharField(max_length=32) +class BatchProblemTagSerializer(serializers.Serializer): + problem_ids = serializers.ListField(child=serializers.IntegerField(), allow_empty=False) + tag_names = serializers.ListField(child=serializers.CharField(max_length=32), allow_empty=False) + action = serializers.ChoiceField(choices=["add", "remove"]) + + class BaseProblemSerializer(serializers.ModelSerializer): tags = serializers.SlugRelatedField(many=True, slug_field="name", read_only=True) created_by = UsernameSerializer() diff --git a/problem/urls/admin.py b/problem/urls/admin.py index 56dff4e..b0b65b4 100644 --- a/problem/urls/admin.py +++ b/problem/urls/admin.py @@ -2,6 +2,7 @@ from django.urls import path from ..views.admin import ( AddContestProblemAPI, + BatchProblemTagAPI, ContestProblemAPI, MakeContestProblemPublicAPIView, ProblemAPI, @@ -27,6 +28,7 @@ urlpatterns = [ path("problem/top_ac_trend", TopACTrendAPI.as_view()), path("problem/flowchart", ProblemFlowchartAIGen.as_view()), path("problem/tag", TagAdminAPI.as_view()), + path("problem/batch_tag", BatchProblemTagAPI.as_view()), path("contest/problem", ContestProblemAPI.as_view()), path("contest_problem/make_public", MakeContestProblemPublicAPIView.as_view()), path("contest/add_problem_from_public", AddContestProblemAPI.as_view()), diff --git a/problem/views/admin.py b/problem/views/admin.py index c8e6a69..0e9121e 100644 --- a/problem/views/admin.py +++ b/problem/views/admin.py @@ -22,6 +22,7 @@ from utils.shortcuts import natural_sort_key, rand_str from ..models import Problem, ProblemTag from ..serializers import ( AddContestProblemSerializer, + BatchProblemTagSerializer, ContestProblemMakePublicSerializer, CreateContestProblemSerializer, CreateProblemSerializer, @@ -34,7 +35,7 @@ from ..serializers import ( TagAdminSerializer, TestCaseUploadForm, ) -from ..services import clear_tag_cache, resolve_tags +from ..services import clear_tag_cache, find_tags, resolve_tags from ..utils import generate_sql_display @@ -554,6 +555,37 @@ class TagAdminAPI(APIView): return self.success() +class BatchProblemTagAPI(APIView): + @problem_permission_required + @validate_serializer(BatchProblemTagSerializer) + def post(self, request): + data = request.data + problems = Problem.objects.filter(id__in=data["problem_ids"], contest_id__isnull=True) + if not request.user.can_mgmt_all_problem(): + problems = problems.filter(created_by=request.user) + problems = list(problems) + if not problems: + return self.error("没有可操作的题目") + + # 添加时按需新建标签,移除时只认已有标签 + if data["action"] == "add": + tags = resolve_tags(data["tag_names"]) + else: + tags = find_tags(data["tag_names"]) + if not tags: + return self.error("没有匹配的标签") + + for problem in problems: + if data["action"] == "add": + problem.tags.add(*tags) + else: + problem.tags.remove(*tags) + + # 题目数变化会影响前台标签列表(只展示 problem_count > 0 的) + clear_tag_cache() + return self.success({"problem_count": len(problems), "tag_count": len(tags)}) + + class ProblemVisibleAPI(APIView): @problem_permission_required def put(self, request):