精简 Problem 模型:移除 rule_type / total_score / io_mode

系统只用 ACM 模式且题目固定标准 IO,这三列已无实际用途:
- rule_type / total_score:OI 专用,删除后 judge/dispatcher 的 OI 分支全部塌缩为 ACM
- io_mode:判题改为固定发送 Standard IO 常量;languages.py 的 seccomp_rule
  保留 dict 形态但改用字面量 key,不改判题机 wire 契约
- prompt 字段保留(预留给未来 AI 分析)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 11:40:38 -06:00
parent 4743b0398a
commit 9535b64a4b
11 changed files with 58 additions and 140 deletions

View File

@@ -13,7 +13,7 @@ from account.models import User
from conf.models import JudgeServer from conf.models import JudgeServer
from contest.models import ACMContestRank from contest.models import ACMContestRank
from options.options import SysOptions from options.options import SysOptions
from problem.models import Problem, ProblemRuleType from problem.models import Problem
from problem.utils import parse_problem_template from problem.utils import parse_problem_template
from submission.models import JudgeStatus, Submission, is_accepted from submission.models import JudgeStatus, Submission, is_accepted
from utils.cache import cache from utils.cache import cache
@@ -114,22 +114,6 @@ class JudgeDispatcher(DispatcherBase):
self.submission.statistic_info["time_cost"] = max([x["cpu_time"] for x in resp_data]) self.submission.statistic_info["time_cost"] = max([x["cpu_time"] for x in resp_data])
self.submission.statistic_info["memory_cost"] = max([x["memory"] for x in resp_data]) self.submission.statistic_info["memory_cost"] = max([x["memory"] for x in resp_data])
# sum up the score in OI mode
if self.problem.rule_type == ProblemRuleType.OI:
score = 0
try:
for i in range(len(resp_data)):
if resp_data[i]["result"] == JudgeStatus.ACCEPTED:
resp_data[i]["score"] = self.problem.test_case_score[i]["score"]
score += resp_data[i]["score"]
else:
resp_data[i]["score"] = 0
except IndexError:
logger.error(f"Index Error raised when summing up the score in problem {self.problem.id}")
self.submission.statistic_info["score"] = 0
return
self.submission.statistic_info["score"] = score
def judge(self): def judge(self):
language = self.submission.language language = self.submission.language
sub_config = list(filter(lambda item: language == item["name"], SysOptions.languages))[0] sub_config = list(filter(lambda item: language == item["name"], SysOptions.languages))[0]
@@ -147,7 +131,7 @@ class JudgeDispatcher(DispatcherBase):
"max_memory": 1024 * 1024 * self.problem.memory_limit, "max_memory": 1024 * 1024 * self.problem.memory_limit,
"test_case_id": self.problem.test_case_id, "test_case_id": self.problem.test_case_id,
"output": False, "output": False,
"io_mode": self.problem.io_mode, "io_mode": {"io_mode": "Standard IO", "input": "input.txt", "output": "output.txt"},
} }
with ChooseJudgeServer() as server: with ChooseJudgeServer() as server:
@@ -188,14 +172,11 @@ class JudgeDispatcher(DispatcherBase):
self.submission.info = resp self.submission.info = resp
self._compute_statistic_info(resp["data"]) self._compute_statistic_info(resp["data"])
error_test_case = list(filter(lambda case: case["result"] != 0, resp["data"])) error_test_case = list(filter(lambda case: case["result"] != 0, resp["data"]))
# ACM模式下,多个测试点全部正确则AC否则取第一个错误的测试点的状态 # 多个测试点全部正确则AC否则取第一个错误的测试点的状态
# OI模式下, 若多个测试点全部正确则AC 若全部错误则取第一个错误测试点状态,否则为部分正确
if not error_test_case: if not error_test_case:
self.submission.result = JudgeStatus.ACCEPTED self.submission.result = JudgeStatus.ACCEPTED
elif self.problem.rule_type == ProblemRuleType.ACM or len(error_test_case) == len(resp["data"]):
self.submission.result = error_test_case[0]["result"]
else: else:
self.submission.result = JudgeStatus.PARTIALLY_ACCEPTED self.submission.result = error_test_case[0]["result"]
if self.submission.result == JudgeStatus.ACCEPTED: if self.submission.result == JudgeStatus.ACCEPTED:
ast_rules = self.problem.ast_rules ast_rules = self.problem.ast_rules
@@ -252,7 +233,6 @@ class JudgeDispatcher(DispatcherBase):
problem.save(update_fields=["accepted_number", "statistic_info"]) problem.save(update_fields=["accepted_number", "statistic_info"])
profile = User.objects.select_for_update().get(id=self.submission.user_id).userprofile profile = User.objects.select_for_update().get(id=self.submission.user_id).userprofile
if problem.rule_type == ProblemRuleType.ACM:
acm_problems_status = profile.acm_problems_status.get("problems", {}) acm_problems_status = profile.acm_problems_status.get("problems", {})
if not is_accepted(acm_problems_status[problem_id]["status"]): if not is_accepted(acm_problems_status[problem_id]["status"]):
acm_problems_status[problem_id]["status"] = JudgeStatus.ACCEPTED if is_accepted(self.submission.result) else self.submission.result acm_problems_status[problem_id]["status"] = JudgeStatus.ACCEPTED if is_accepted(self.submission.result) else self.submission.result
@@ -261,19 +241,6 @@ class JudgeDispatcher(DispatcherBase):
profile.acm_problems_status["problems"] = acm_problems_status profile.acm_problems_status["problems"] = acm_problems_status
profile.save(update_fields=["accepted_number", "acm_problems_status"]) profile.save(update_fields=["accepted_number", "acm_problems_status"])
else:
oi_problems_status = profile.oi_problems_status.get("problems", {})
score = self.submission.statistic_info["score"]
if not is_accepted(oi_problems_status[problem_id]["status"]):
# minus last time score, add this tim score
profile.add_score(this_time_score=score, last_time_score=oi_problems_status[problem_id]["score"])
oi_problems_status[problem_id]["score"] = score
oi_problems_status[problem_id]["status"] = JudgeStatus.ACCEPTED if is_accepted(self.submission.result) else self.submission.result
if is_accepted(self.submission.result):
profile.accepted_number += 1
profile.oi_problems_status["problems"] = oi_problems_status
profile.save(update_fields=["accepted_number", "oi_problems_status"])
def update_problem_status(self): def update_problem_status(self):
result = str(self.submission.result) result = str(self.submission.result)
problem_id = str(self.problem.id) problem_id = str(self.problem.id)
@@ -292,7 +259,6 @@ class JudgeDispatcher(DispatcherBase):
user_profile = user.userprofile user_profile = user.userprofile
user_profile.submission_number = F("submission_number") + 1 user_profile.submission_number = F("submission_number") + 1
profile_status = JudgeStatus.ACCEPTED if is_accepted(self.submission.result) else self.submission.result profile_status = JudgeStatus.ACCEPTED if is_accepted(self.submission.result) else self.submission.result
if problem.rule_type == ProblemRuleType.ACM:
acm_problems_status = user_profile.acm_problems_status.get("problems", {}) acm_problems_status = user_profile.acm_problems_status.get("problems", {})
if problem_id not in acm_problems_status: if problem_id not in acm_problems_status:
acm_problems_status[problem_id] = {"status": profile_status, "_id": self.problem._id} acm_problems_status[problem_id] = {"status": profile_status, "_id": self.problem._id}
@@ -305,24 +271,6 @@ class JudgeDispatcher(DispatcherBase):
user_profile.acm_problems_status["problems"] = acm_problems_status user_profile.acm_problems_status["problems"] = acm_problems_status
user_profile.save(update_fields=["submission_number", "accepted_number", "acm_problems_status"]) user_profile.save(update_fields=["submission_number", "accepted_number", "acm_problems_status"])
else:
oi_problems_status = user_profile.oi_problems_status.get("problems", {})
score = self.submission.statistic_info["score"]
if problem_id not in oi_problems_status:
user_profile.add_score(score)
oi_problems_status[problem_id] = {"status": profile_status, "_id": self.problem._id, "score": score}
if is_accepted(self.submission.result):
user_profile.accepted_number += 1
elif not is_accepted(oi_problems_status[problem_id]["status"]):
# minus last time score, add this time score
user_profile.add_score(this_time_score=score, last_time_score=oi_problems_status[problem_id]["score"])
oi_problems_status[problem_id]["score"] = score
oi_problems_status[problem_id]["status"] = profile_status
if is_accepted(self.submission.result):
user_profile.accepted_number += 1
user_profile.oi_problems_status["problems"] = oi_problems_status
user_profile.save(update_fields=["submission_number", "accepted_number", "oi_problems_status"])
def update_contest_problem_status(self): def update_contest_problem_status(self):
with transaction.atomic(): with transaction.atomic():
user = User.objects.select_for_update().get(id=self.submission.user_id) user = User.objects.select_for_update().get(id=self.submission.user_id)

View File

@@ -1,5 +1,3 @@
from problem.models import ProblemIOMode
default_env = ["LANG=en_US.UTF-8", "LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8"] default_env = ["LANG=en_US.UTF-8", "LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8"]
_c_lang_config = { _c_lang_config = {
@@ -27,7 +25,7 @@ int main() {
"max_memory": 256 * 1024 * 1024, "max_memory": 256 * 1024 * 1024,
"compile_command": "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c17 {src_path} -lm -o {exe_path}", "compile_command": "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c17 {src_path} -lm -o {exe_path}",
}, },
"run": {"command": "{exe_path}", "seccomp_rule": {ProblemIOMode.STANDARD: "c_cpp", ProblemIOMode.FILE: "c_cpp_file_io"}, "env": default_env}, "run": {"command": "{exe_path}", "seccomp_rule": {"Standard IO": "c_cpp", "File IO": "c_cpp_file_io"}, "env": default_env},
} }
@@ -56,7 +54,7 @@ int main() {
"max_memory": 1024 * 1024 * 1024, "max_memory": 1024 * 1024 * 1024,
"compile_command": "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++20 {src_path} -lm -o {exe_path}", "compile_command": "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++20 {src_path} -lm -o {exe_path}",
}, },
"run": {"command": "{exe_path}", "seccomp_rule": {ProblemIOMode.STANDARD: "c_cpp", ProblemIOMode.FILE: "c_cpp_file_io"}, "env": default_env}, "run": {"command": "{exe_path}", "seccomp_rule": {"Standard IO": "c_cpp", "File IO": "c_cpp_file_io"}, "env": default_env},
} }

View File

@@ -88,7 +88,7 @@ class SQLJudgeDispatcher(JudgeDispatcher):
raise SQLProblemConfigError("测试点不是 SQL 类型,请重新上传 SQL 测试点压缩包") raise SQLProblemConfigError("测试点不是 SQL 类型,请重新上传 SQL 测试点压缩包")
init_scripts = [] init_scripts = []
# 按 "1","2",… 自然序遍历,保证与 test_case_score 的下标对应OI 计分依赖顺序) # 按 "1","2",… 自然序遍历,保证测试点顺序稳定
for key in sorted(info["test_cases"].keys(), key=natural_sort_key): for key in sorted(info["test_cases"].keys(), key=natural_sort_key):
input_name = info["test_cases"][key]["input_name"] input_name = info["test_cases"][key]["input_name"]
try: try:

View File

@@ -1,7 +1,6 @@
# Generated by Django 5.2.3 on 2025-06-14 08:51 # Generated by Django 5.2.3 on 2025-06-14 08:51
import django.db.models.deletion import django.db.models.deletion
import problem.models
import utils.models import utils.models
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@@ -47,7 +46,7 @@ class Migration(migrations.Migration):
('last_update_time', models.DateTimeField(auto_now=True, null=True)), ('last_update_time', models.DateTimeField(auto_now=True, null=True)),
('time_limit', models.IntegerField()), ('time_limit', models.IntegerField()),
('memory_limit', models.IntegerField()), ('memory_limit', models.IntegerField()),
('io_mode', models.JSONField(default=problem.models._default_io_mode)), ('io_mode', models.JSONField(default=dict)),
('spj', models.BooleanField(default=False)), ('spj', models.BooleanField(default=False)),
('spj_language', models.TextField(null=True)), ('spj_language', models.TextField(null=True)),
('spj_code', models.TextField(null=True)), ('spj_code', models.TextField(null=True)),

View File

@@ -0,0 +1,25 @@
# Generated by Django 6.0.4 on 2026-07-04 17:27
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('problem', '0012_problem_sql_display'),
]
operations = [
migrations.RemoveField(
model_name='problem',
name='io_mode',
),
migrations.RemoveField(
model_name='problem',
name='rule_type',
),
migrations.RemoveField(
model_name='problem',
name='total_score',
),
]

View File

@@ -13,24 +13,6 @@ class ProblemTag(models.Model):
db_table = "problem_tag" db_table = "problem_tag"
class ProblemRuleType(models.TextChoices):
ACM = "ACM", "ACM"
OI = "OI", "OI"
class ProblemIOMode(models.TextChoices):
STANDARD = "Standard IO", "Standard IO"
FILE = "File IO", "File IO"
def _default_io_mode():
return {
"io_mode": ProblemIOMode.STANDARD,
"input": "input.txt",
"output": "output.txt",
}
class Problem(models.Model): class Problem(models.Model):
# display ID # display ID
_id = models.TextField(db_index=True) _id = models.TextField(db_index=True)
@@ -58,18 +40,14 @@ class Problem(models.Model):
time_limit = models.IntegerField() time_limit = models.IntegerField()
# MB # MB
memory_limit = models.IntegerField() memory_limit = models.IntegerField()
# io mode
io_mode = models.JSONField(default=_default_io_mode)
rule_type = models.TextField(choices=ProblemRuleType.choices)
visible = models.BooleanField(default=True, db_default=True) visible = models.BooleanField(default=True, db_default=True)
difficulty = models.TextField(choices=Difficulty.choices) difficulty = models.TextField(choices=Difficulty.choices)
tags = models.ManyToManyField(ProblemTag) tags = models.ManyToManyField(ProblemTag)
source = models.TextField(null=True) source = models.TextField(null=True)
# 预留:题目考察知识点,供未来 AI 分析使用(当前未接线)
prompt = models.TextField(null=True) prompt = models.TextField(null=True)
# [{language: "python", code: "..."}] # [{language: "python", code: "..."}]
answers = models.JSONField(null=True) answers = models.JSONField(null=True)
# for OI mode
total_score = models.IntegerField(default=0, db_default=0)
submission_number = models.BigIntegerField(default=0, db_default=0) submission_number = models.BigIntegerField(default=0, db_default=0)
accepted_number = models.BigIntegerField(default=0, db_default=0) accepted_number = models.BigIntegerField(default=0, db_default=0)
# {JudgeStatus.ACCEPTED: 3, JudgeStatus.WRONG_ANSWER: 11}, the number means count # {JudgeStatus.ACCEPTED: 3, JudgeStatus.WRONG_ANSWER: 11}, the number means count

View File

@@ -1,5 +1,3 @@
import re
from django import forms from django import forms
from utils.api import UsernameSerializer, serializers from utils.api import UsernameSerializer, serializers
@@ -9,7 +7,7 @@ from utils.serializers import (
LanguageNameMultiChoiceField, LanguageNameMultiChoiceField,
) )
from .models import Problem, ProblemIOMode, ProblemRuleType, ProblemTag from .models import Problem, ProblemTag
from .utils import parse_problem_template from .utils import parse_problem_template
@@ -48,20 +46,6 @@ class SQLTestCasePreviewSerializer(serializers.Serializer):
mode = serializers.ChoiceField(choices=["query", "modify"]) mode = serializers.ChoiceField(choices=["query", "modify"])
class ProblemIOModeSerializer(serializers.Serializer):
io_mode = serializers.ChoiceField(choices=ProblemIOMode.choices)
input = serializers.CharField()
output = serializers.CharField()
def validate(self, attrs):
if attrs["input"] == attrs["output"]:
raise serializers.ValidationError("Invalid io mode")
for item in (attrs["input"], attrs["output"]):
if not re.match("^[a-zA-Z0-9.]+$", item):
raise serializers.ValidationError("Invalid io file name format")
return attrs
class CreateOrEditProblemSerializer(serializers.Serializer): class CreateOrEditProblemSerializer(serializers.Serializer):
_id = serializers.CharField(max_length=32, allow_blank=True, allow_null=True) _id = serializers.CharField(max_length=32, allow_blank=True, allow_null=True)
title = serializers.CharField(max_length=1024) title = serializers.CharField(max_length=1024)
@@ -75,8 +59,6 @@ class CreateOrEditProblemSerializer(serializers.Serializer):
memory_limit = serializers.IntegerField(min_value=1, max_value=1024) memory_limit = serializers.IntegerField(min_value=1, max_value=1024)
languages = LanguageNameMultiChoiceField() languages = LanguageNameMultiChoiceField()
template = serializers.DictField(child=serializers.CharField(min_length=1)) template = serializers.DictField(child=serializers.CharField(min_length=1))
rule_type = serializers.ChoiceField(choices=ProblemRuleType.choices)
io_mode = ProblemIOModeSerializer()
visible = serializers.BooleanField() visible = serializers.BooleanField()
difficulty = serializers.ChoiceField(choices=Difficulty.choices) difficulty = serializers.ChoiceField(choices=Difficulty.choices)
tags = serializers.ListField(child=serializers.CharField(max_length=32), allow_empty=False) tags = serializers.ListField(child=serializers.CharField(max_length=32), allow_empty=False)

View File

@@ -19,7 +19,7 @@ from utils.api import APIError, APIView, CSRFExemptAPIView, validate_serializer
from utils.openai import get_ai_client from utils.openai import get_ai_client
from utils.shortcuts import natural_sort_key, rand_str from utils.shortcuts import natural_sort_key, rand_str
from ..models import Problem, ProblemRuleType, ProblemTag from ..models import Problem, ProblemTag
from ..serializers import ( from ..serializers import (
AddContestProblemSerializer, AddContestProblemSerializer,
ContestProblemMakePublicSerializer, ContestProblemMakePublicSerializer,
@@ -195,14 +195,6 @@ class TestCaseAPI(CSRFExemptAPIView, TestCaseZipProcessor):
class ProblemBase(APIView): class ProblemBase(APIView):
def common_checks(self, request): def common_checks(self, request):
data = request.data data = request.data
if data["rule_type"] == ProblemRuleType.OI:
total_score = 0
for item in data["test_case_score"]:
if item["score"] <= 0:
return "Invalid score"
else:
total_score += item["score"]
data["total_score"] = total_score
data["languages"] = list(data["languages"]) data["languages"] = list(data["languages"])
# SQL 题校验:.sql 测试点与 .in/.out 沙箱判题互斥SQL 必须是唯一语言 # SQL 题校验:.sql 测试点与 .in/.out 沙箱判题互斥SQL 必须是唯一语言

View File

@@ -57,7 +57,7 @@ class SubmissionModelSerializer(serializers.ModelSerializer):
fields = "__all__" fields = "__all__"
# 不显示submission info的serializer, 用于ACM rule_type # 不显示submission info的serializer, 用于非管理员查看他人提交
class SubmissionSafeModelSerializer(serializers.ModelSerializer): class SubmissionSafeModelSerializer(serializers.ModelSerializer):
problem = serializers.SlugRelatedField(read_only=True, slug_field="_id") problem = serializers.SlugRelatedField(read_only=True, slug_field="_id")

View File

@@ -11,7 +11,7 @@ from judge.tasks import judge_task
from options.options import SysOptions from options.options import SysOptions
# from judge.dispatcher import JudgeDispatcher # from judge.dispatcher import JudgeDispatcher
from problem.models import Problem, ProblemRuleType from problem.models import Problem
from utils.api import APIView, AsyncAPIView, validate_serializer from utils.api import APIView, AsyncAPIView, validate_serializer
from utils.cache import cache from utils.cache import cache
from utils.captcha import Captcha from utils.captcha import Captcha
@@ -116,10 +116,7 @@ class SubmissionAPI(AsyncAPIView):
if not submission.check_user_permission(request.user): if not submission.check_user_permission(request.user):
return self.error("No permission for this submission") return self.error("No permission for this submission")
if ( if request.user.is_admin_role():
submission.problem.rule_type == ProblemRuleType.OI
or request.user.is_admin_role()
):
submission_data = await self.async_serialize_data(SubmissionModelSerializer, submission) submission_data = await self.async_serialize_data(SubmissionModelSerializer, submission)
else: else:
submission_data = await self.async_serialize_data(SubmissionSafeModelSerializer, submission) submission_data = await self.async_serialize_data(SubmissionSafeModelSerializer, submission)

View File

@@ -12,7 +12,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oj.settings")
django.setup() django.setup()
from django.conf import settings from django.conf import settings
from account.models import User, UserProfile, AdminType, ProblemPermission from account.models import User, UserProfile, AdminType, ProblemPermission
from problem.models import Problem, ProblemTag, ProblemRuleType from problem.models import Problem, ProblemTag
from utils.constants import Difficulty from utils.constants import Difficulty
admin_type_map = {0: AdminType.REGULAR_USER, 1: AdminType.STUDENT_ADMIN, 2: AdminType.SUPER_ADMIN} admin_type_map = {0: AdminType.REGULAR_USER, 1: AdminType.STUDENT_ADMIN, 2: AdminType.SUPER_ADMIN}
@@ -154,7 +154,6 @@ def import_problems():
print("%s test_case files don't exist, omitted" % data["title"]) print("%s test_case files don't exist, omitted" % data["title"])
continue continue
data["test_case_score"] = test_case_score data["test_case_score"] = test_case_score
data["rule_type"] = ProblemRuleType.ACM
data["template"] = {} data["template"] = {}
data.pop("total_submit_number") data.pop("total_submit_number")
data.pop("total_accepted_number") data.pop("total_accepted_number")