From 7835cf013a695c548552860627739726ba4da90b Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Fri, 3 Oct 2025 14:30:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E5=8F=B0=E5=87=BA=E9=A2=98=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- problem/views/admin.py | 284 ++++++++++++++++++++++++++--------------- problem/views/oj.py | 16 ++- 2 files changed, 190 insertions(+), 110 deletions(-) diff --git a/problem/views/admin.py b/problem/views/admin.py index 58fff35..414bb8d 100644 --- a/problem/views/admin.py +++ b/problem/views/admin.py @@ -1,6 +1,7 @@ import hashlib import json import os + # import shutil import tempfile import zipfile @@ -11,7 +12,7 @@ from django.db import transaction from django.db.models import Q from django.http import StreamingHttpResponse, FileResponse -from account.decorators import problem_permission_required, ensure_created_by, super_admin_required +from account.decorators import problem_permission_required, ensure_created_by from contest.models import Contest, ContestStatus from fps.parser import FPSHelper, FPSParser from judge.dispatcher import SPJCompiler @@ -22,12 +23,23 @@ from utils.constants import Difficulty from utils.shortcuts import rand_str, natural_sort_key from utils.tasks import delete_files from ..models import Problem, ProblemRuleType, ProblemTag -from ..serializers import (CreateContestProblemSerializer, CompileSPJSerializer, - CreateProblemSerializer, EditProblemSerializer, EditContestProblemSerializer, - ProblemAdminSerializer, ProblemAdminListSerializer, TestCaseUploadForm, - ContestProblemMakePublicSerializer, AddContestProblemSerializer, ExportProblemSerializer, - ExportProblemRequestSerializer, UploadProblemForm, ImportProblemSerializer, - FPSProblemSerializer) +from ..serializers import ( + CreateContestProblemSerializer, + CompileSPJSerializer, + CreateProblemSerializer, + EditProblemSerializer, + EditContestProblemSerializer, + ProblemAdminSerializer, + ProblemAdminListSerializer, + TestCaseUploadForm, + ContestProblemMakePublicSerializer, + AddContestProblemSerializer, + ExportProblemSerializer, + ExportProblemRequestSerializer, + UploadProblemForm, + ImportProblemSerializer, + FPSProblemSerializer, +) from ..utils import TEMPLATE_BASE, build_problem_template @@ -70,11 +82,13 @@ class TestCaseZipProcessor(object): # ["1.in", "1.out", "2.in", "2.out"] => [("1.in", "1.out"), ("2.in", "2.out")] test_case_list = zip(*[test_case_list[i::2] for i in range(2)]) for index, item in enumerate(test_case_list): - data = {"stripped_output_md5": md5_cache[item[1]], - "input_size": size_cache[item[0]], - "output_size": size_cache[item[1]], - "input_name": item[0], - "output_name": item[1]} + data = { + "stripped_output_md5": md5_cache[item[1]], + "input_size": size_cache[item[0]], + "output_size": size_cache[item[1]], + "input_name": item[0], + "output_name": item[1], + } info.append(data) test_case_info["test_cases"][str(index + 1)] = data @@ -137,10 +151,13 @@ class TestCaseAPI(CSRFExemptAPIView, TestCaseZipProcessor): with zipfile.ZipFile(file_name, "w") as file: for test_case in name_list: file.write(f"{test_case_dir}/{test_case}", test_case) - response = StreamingHttpResponse(FileWrapper(open(file_name, "rb")), - content_type="application/octet-stream") + response = StreamingHttpResponse( + FileWrapper(open(file_name, "rb")), content_type="application/octet-stream" + ) - response["Content-Disposition"] = f"attachment; filename=problem_{problem.id}_test_cases.zip" + response["Content-Disposition"] = ( + f"attachment; filename=problem_{problem.id}_test_cases.zip" + ) response["Content-Length"] = os.path.getsize(file_name) return response @@ -165,7 +182,9 @@ class CompileSPJAPI(APIView): def post(self, request): data = request.data spj_version = rand_str(8) - error = SPJCompiler(data["spj_code"], spj_version, data["spj_language"]).compile_spj() + error = SPJCompiler( + data["spj_code"], spj_version, data["spj_language"] + ).compile_spj() if error: return self.error(error) else: @@ -181,7 +200,8 @@ class ProblemBase(APIView): if not data["spj_compile_ok"]: return "SPJ code must be compiled successfully" data["spj_version"] = hashlib.md5( - (data["spj_language"] + ":" + data["spj_code"]).encode("utf-8")).hexdigest() + (data["spj_language"] + ":" + data["spj_code"]).encode("utf-8") + ).hexdigest() else: data["spj_language"] = None data["spj_code"] = None @@ -227,7 +247,6 @@ class ProblemAPI(ProblemBase): @problem_permission_required def get(self, request): problem_id = request.GET.get("id") - rule_type = request.GET.get("rule_type") user = request.user if problem_id: try: @@ -237,19 +256,24 @@ class ProblemAPI(ProblemBase): except Problem.DoesNotExist: return self.error("Problem does not exist") - problems = Problem.objects.filter(contest_id__isnull=True).order_by("-create_time") - if rule_type: - if rule_type not in ProblemRuleType.choices(): - return self.error("Invalid rule_type") - else: - problems = problems.filter(rule_type=rule_type) + problems = Problem.objects.filter(contest_id__isnull=True).order_by( + "-create_time" + ) + + author = request.GET.get("author", "") + if author: + problems = problems.filter(created_by__username=author) keyword = request.GET.get("keyword", "").strip() if keyword: - problems = problems.filter(Q(title__icontains=keyword) | Q(_id__icontains=keyword)) + problems = problems.filter( + Q(title__icontains=keyword) | Q(_id__icontains=keyword) + ) if not user.can_mgmt_all_problem(): problems = problems.filter(created_by=user) - return self.success(self.paginate_data(request, problems, ProblemAdminListSerializer)) + return self.success( + self.paginate_data(request, problems, ProblemAdminListSerializer) + ) @problem_permission_required @validate_serializer(EditProblemSerializer) @@ -266,7 +290,11 @@ class ProblemAPI(ProblemBase): _id = data["_id"] if not _id: return self.error("Display ID is required") - if Problem.objects.exclude(id=problem_id).filter(_id=_id, contest_id__isnull=True).exists(): + if ( + Problem.objects.exclude(id=problem_id) + .filter(_id=_id, contest_id__isnull=True) + .exists() + ): return self.error("Display ID already exists") error_info = self.common_checks(request) @@ -370,7 +398,9 @@ class ContestProblemAPI(ProblemBase): keyword = request.GET.get("keyword") if keyword: problems = problems.filter(title__contains=keyword) - return self.success(self.paginate_data(request, problems, ProblemAdminListSerializer)) + return self.success( + self.paginate_data(request, problems, ProblemAdminListSerializer) + ) @validate_serializer(EditContestProblemSerializer) def put(self, request): @@ -396,7 +426,11 @@ class ContestProblemAPI(ProblemBase): _id = data["_id"] if not _id: return self.error("Display ID is required") - if Problem.objects.exclude(id=problem_id).filter(_id=_id, contest=contest).exists(): + if ( + Problem.objects.exclude(id=problem_id) + .filter(_id=_id, contest=contest) + .exists() + ): return self.error("Display ID already exists") error_info = self.common_checks(request) @@ -500,10 +534,16 @@ class ExportProblemAPI(APIView): def choose_answers(self, user, problem): ret = [] for item in problem.languages: - submission = Submission.objects.filter(problem=problem, - user_id=user.id, - language=item, - result=JudgeStatus.ACCEPTED).order_by("-create_time").first() + submission = ( + Submission.objects.filter( + problem=problem, + user_id=user.id, + language=item, + result=JudgeStatus.ACCEPTED, + ) + .order_by("-create_time") + .first() + ) if submission: ret.append({"language": submission.language, "code": submission.code}) return ret @@ -512,20 +552,28 @@ class ExportProblemAPI(APIView): info = ExportProblemSerializer(problem).data info["answers"] = self.choose_answers(user, problem=problem) compression = zipfile.ZIP_DEFLATED - zip_file.writestr(zinfo_or_arcname=f"{index}/problem.json", - data=json.dumps(info, indent=4), - compress_type=compression) - problem_test_case_dir = os.path.join(settings.TEST_CASE_DIR, problem.test_case_id) + zip_file.writestr( + zinfo_or_arcname=f"{index}/problem.json", + data=json.dumps(info, indent=4), + compress_type=compression, + ) + problem_test_case_dir = os.path.join( + settings.TEST_CASE_DIR, problem.test_case_id + ) with open(os.path.join(problem_test_case_dir, "info")) as f: info = json.load(f) for k, v in info["test_cases"].items(): - zip_file.write(filename=os.path.join(problem_test_case_dir, v["input_name"]), - arcname=f"{index}/testcase/{v['input_name']}", - compress_type=compression) + zip_file.write( + filename=os.path.join(problem_test_case_dir, v["input_name"]), + arcname=f"{index}/testcase/{v['input_name']}", + compress_type=compression, + ) if not info["spj"]: - zip_file.write(filename=os.path.join(problem_test_case_dir, v["output_name"]), - arcname=f"{index}/testcase/{v['output_name']}", - compress_type=compression) + zip_file.write( + filename=os.path.join(problem_test_case_dir, v["output_name"]), + arcname=f"{index}/testcase/{v['output_name']}", + compress_type=compression, + ) @validate_serializer(ExportProblemRequestSerializer) def get(self, request): @@ -538,7 +586,12 @@ class ExportProblemAPI(APIView): path = f"/tmp/{rand_str()}.zip" with zipfile.ZipFile(path, "w") as zip_file: for index, problem in enumerate(problems): - self.process_one_problem(zip_file=zip_file, user=request.user, problem=problem, index=index + 1) + self.process_one_problem( + zip_file=zip_file, + user=request.user, + problem=problem, + index=index + 1, + ) delete_files.send_with_options(args=(path,), delay=300_000) resp = FileResponse(open(path, "rb")) resp["Content-Type"] = "application/zip" @@ -572,7 +625,9 @@ class ImportProblemAPI(CSRFExemptAPIView, TestCaseZipProcessor): problem_info = json.load(f) serializer = ImportProblemSerializer(data=problem_info) if not serializer.is_valid(): - return self.error(f"Invalid problem format, error is {serializer.errors}") + return self.error( + f"Invalid problem format, error is {serializer.errors}" + ) else: problem_info = serializer.data for item in problem_info["template"].keys(): @@ -581,44 +636,52 @@ class ImportProblemAPI(CSRFExemptAPIView, TestCaseZipProcessor): problem_info["display_id"] = problem_info["display_id"][:24] for k, v in problem_info["template"].items(): - problem_info["template"][k] = build_problem_template(v["prepend"], v["template"], - v["append"]) + problem_info["template"][k] = build_problem_template( + v["prepend"], v["template"], v["append"] + ) spj = problem_info["spj"] is not None rule_type = problem_info["rule_type"] test_case_score = problem_info["test_case_score"] # process test case - _, test_case_id = self.process_zip(tmp_file, spj=spj, dir=f"{i}/testcase/") + _, test_case_id = self.process_zip( + tmp_file, spj=spj, dir=f"{i}/testcase/" + ) - problem_obj = Problem.objects.create(_id=problem_info["display_id"], - title=problem_info["title"], - description=problem_info["description"]["value"], - input_description=problem_info["input_description"][ - "value"], - output_description=problem_info["output_description"][ - "value"], - hint=problem_info["hint"]["value"], - test_case_score=test_case_score if test_case_score else [], - time_limit=problem_info["time_limit"], - memory_limit=problem_info["memory_limit"], - samples=problem_info["samples"], - template=problem_info["template"], - rule_type=problem_info["rule_type"], - source=problem_info["source"], - spj=spj, - spj_code=problem_info["spj"]["code"] if spj else None, - spj_language=problem_info["spj"][ - "language"] if spj else None, - spj_version=rand_str(8) if spj else "", - languages=SysOptions.language_names, - created_by=request.user, - visible=False, - difficulty=Difficulty.MID, - total_score=sum(item["score"] for item in test_case_score) - if rule_type == ProblemRuleType.OI else 0, - test_case_id=test_case_id - ) + problem_obj = Problem.objects.create( + _id=problem_info["display_id"], + title=problem_info["title"], + description=problem_info["description"]["value"], + input_description=problem_info["input_description"][ + "value" + ], + output_description=problem_info["output_description"][ + "value" + ], + hint=problem_info["hint"]["value"], + test_case_score=test_case_score if test_case_score else [], + time_limit=problem_info["time_limit"], + memory_limit=problem_info["memory_limit"], + samples=problem_info["samples"], + template=problem_info["template"], + rule_type=problem_info["rule_type"], + source=problem_info["source"], + spj=spj, + spj_code=problem_info["spj"]["code"] if spj else None, + spj_language=problem_info["spj"]["language"] + if spj + else None, + spj_version=rand_str(8) if spj else "", + languages=SysOptions.language_names, + created_by=request.user, + visible=False, + difficulty=Difficulty.MID, + total_score=sum(item["score"] for item in test_case_score) + if rule_type == ProblemRuleType.OI + else 0, + test_case_id=test_case_id, + ) for tag_name in problem_info["tags"]: tag_obj, _ = ProblemTag.objects.get_or_create(name=tag_name) problem_obj.tags.add(tag_obj) @@ -644,30 +707,34 @@ class FPSProblemImport(CSRFExemptAPIView): our_lang = lang = t["language"] if lang == "Python": our_lang = "Python3" - template[our_lang] = TEMPLATE_BASE.format(prepend.get(lang, ""), t["code"], append.get(lang, "")) + template[our_lang] = TEMPLATE_BASE.format( + prepend.get(lang, ""), t["code"], append.get(lang, "") + ) spj = problem_data["spj"] is not None - Problem.objects.create(_id=f"fps-{rand_str(4)}", - title=problem_data["title"], - description=problem_data["description"], - input_description=problem_data["input"], - output_description=problem_data["output"], - hint=problem_data["hint"], - test_case_score=problem_data["test_case_score"], - time_limit=time_limit, - memory_limit=problem_data["memory_limit"]["value"], - samples=problem_data["samples"], - template=template, - rule_type=ProblemRuleType.ACM, - source=problem_data.get("source", ""), - spj=spj, - spj_code=problem_data["spj"]["code"] if spj else None, - spj_language=problem_data["spj"]["language"] if spj else None, - spj_version=rand_str(8) if spj else "", - visible=False, - languages=SysOptions.language_names, - created_by=creator, - difficulty=Difficulty.MID, - test_case_id=problem_data["test_case_id"]) + Problem.objects.create( + _id=f"fps-{rand_str(4)}", + title=problem_data["title"], + description=problem_data["description"], + input_description=problem_data["input"], + output_description=problem_data["output"], + hint=problem_data["hint"], + test_case_score=problem_data["test_case_score"], + time_limit=time_limit, + memory_limit=problem_data["memory_limit"]["value"], + samples=problem_data["samples"], + template=template, + rule_type=ProblemRuleType.ACM, + source=problem_data.get("source", ""), + spj=spj, + spj_code=problem_data["spj"]["code"] if spj else None, + spj_language=problem_data["spj"]["language"] if spj else None, + spj_version=rand_str(8) if spj else "", + visible=False, + languages=SysOptions.language_names, + created_by=creator, + difficulty=Difficulty.MID, + test_case_id=problem_data["test_case_id"], + ) def post(self, request): form = UploadProblemForm(request.POST, request.FILES) @@ -691,10 +758,19 @@ class FPSProblemImport(CSRFExemptAPIView): test_case_dir = os.path.join(settings.TEST_CASE_DIR, test_case_id) os.mkdir(test_case_dir) score = [] - for item in helper.save_test_case(_problem, test_case_dir)["test_cases"].values(): - score.append({"score": 0, "input_name": item["input_name"], - "output_name": item.get("output_name")}) - problem_data = helper.save_image(_problem, settings.UPLOAD_DIR, settings.UPLOAD_PREFIX) + for item in helper.save_test_case(_problem, test_case_dir)[ + "test_cases" + ].values(): + score.append( + { + "score": 0, + "input_name": item["input_name"], + "output_name": item.get("output_name"), + } + ) + problem_data = helper.save_image( + _problem, settings.UPLOAD_DIR, settings.UPLOAD_PREFIX + ) s = FPSProblemSerializer(data=problem_data) if not s.is_valid(): return self.error(f"Parse FPS file error: {s.errors}") @@ -715,4 +791,4 @@ class ProblemVisibleAPI(APIView): self.error("problem does not exists") problem.visible = not problem.visible problem.save() - return self.success() \ No newline at end of file + return self.success() diff --git a/problem/views/oj.py b/problem/views/oj.py index 68d0fdd..caf8a68 100644 --- a/problem/views/oj.py +++ b/problem/views/oj.py @@ -192,15 +192,19 @@ class ProblemSolvedPeopleCount(APIView): class ProblemAuthorAPI(APIView): def get(self, request): - # 统计出题用户 - cached_data = cache.get(CacheKey.problem_authors) + show_all = request.GET.get("all", "0") == "1" + cached_data = cache.get( + f"{CacheKey.problem_authors}{'_all' if show_all else '_only_visible'}" + ) if cached_data: return self.success(cached_data) + problem_filter = {"contest_id__isnull": True, "created_by__is_disabled": False} + if not show_all: + problem_filter["visible"] = True + authors = ( - Problem.objects.filter( - visible=True, contest_id__isnull=True, created_by__is_disabled=False - ) + Problem.objects.filter(**problem_filter) .values("created_by__username") .annotate(problem_count=Count("id")) .order_by("-problem_count") @@ -213,5 +217,5 @@ class ProblemAuthorAPI(APIView): for author in authors ] - cache.set(CacheKey.problem_authors, result, 3600) + cache.set(CacheKey.problem_authors, result, 7200) return self.success(result)