utils/tasks.py 整个文件(delete_files) utils/async_helpers.py async_cache_delete problem/utils.py build_problem_template utils/shortcuts.py build_query_string achievement/checker.py get_or_create_stat problem/serializers.py TemplateSerializer delete_files 的唯一调用者是上一个 commit 删掉的 DownloadContestSubmissions (`delete_files.send_with_options(args=(zip_path,), delay=300_000)`,生成 zip 后 延时清理)。django_dramatiq 按约定自动发现各 app 的 tasks.py,缺一个文件不报错, 已实跑确认 judge / achievement / flowchart 三处的 actor 都还在。 ## 扫出来但**不能删**的,记在这里免得下次又查一遍 **achievement/metrics.py 的 18 个指标类**:静态扫描报"零引用",因为类名确实 从不被引用 —— 它们靠 `@metric(key, name)` 装饰器在**定义时**注册进 METRIC_REGISTRY。删掉会静默丢掉成就维度,而且不报错。运行时确认注册表里 18 个一个不少。 **async_cache_get / async_cache_set**:各 6 处引用,只有 delete 那个是死的。 **parse_problem_template**:5 处引用,和被删的 build_problem_template 只是名字像。 **User 模型上的 open_api / open_api_appkey / two_factor_auth / session_keys**: 删字段要迁移,会改生产库结构 —— 而新后端正是靠"库结构与旧后端逐列一致"才能 回滚(阶段 5 演练验证过)。切换完成之前不动。 ## 验证 manage.py check 通过;起本地 Django + PostgreSQL 实跑 14 个端点全部 200 (前台 10 个 + 后台 4 个);ruff 全绿;行尾未被改动。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
64 lines
2.4 KiB
Python
64 lines
2.4 KiB
Python
import json
|
||
import os
|
||
import re
|
||
from functools import lru_cache
|
||
|
||
from django.conf import settings
|
||
|
||
from judge.sql_runner import SQLCaseError, build_display
|
||
from utils.shortcuts import natural_sort_key
|
||
|
||
TEMPLATE_BASE = """//PREPEND BEGIN
|
||
{}
|
||
//PREPEND END
|
||
|
||
//TEMPLATE BEGIN
|
||
{}
|
||
//TEMPLATE END
|
||
|
||
//APPEND BEGIN
|
||
{}
|
||
//APPEND END"""
|
||
|
||
|
||
@lru_cache(maxsize=100)
|
||
def parse_problem_template(template_str):
|
||
prepend = re.findall(r"//PREPEND BEGIN\n([\s\S]+?)//PREPEND END", template_str)
|
||
template = re.findall(r"//TEMPLATE BEGIN\n([\s\S]+?)//TEMPLATE END", template_str)
|
||
append = re.findall(r"//APPEND BEGIN\n([\s\S]+?)//APPEND END", template_str)
|
||
return {"prepend": prepend[0] if prepend else "", "template": template[0] if template else "", "append": append[0] if append else ""}
|
||
|
||
|
||
def generate_sql_display(test_case_id, answers, sql_config):
|
||
"""SQL 题:用测试点1的初始化脚本 + 标准答案生成题目页展示数据。
|
||
|
||
返回 (sql_display, error):成功时 error 为 None,失败时 sql_display 为 None、error 为中文提示。
|
||
"""
|
||
test_case_dir = os.path.join(settings.TEST_CASE_DIR, test_case_id)
|
||
try:
|
||
with open(os.path.join(test_case_dir, "info"), encoding="utf-8") as f:
|
||
info = json.load(f)
|
||
except (OSError, json.JSONDecodeError):
|
||
return None, "测试点信息读取失败,请重新上传测试点"
|
||
if not info.get("sql"):
|
||
return None, "测试点不是 SQL 类型,请重新上传 SQL 测试点压缩包"
|
||
try:
|
||
keys = sorted(info["test_cases"].keys(), key=natural_sort_key)
|
||
if not keys:
|
||
return None, "题目没有任何测试点"
|
||
input_name = info["test_cases"][keys[0]]["input_name"]
|
||
except (KeyError, AttributeError, TypeError):
|
||
return None, "测试点信息损坏,请重新上传测试点"
|
||
try:
|
||
with open(os.path.join(test_case_dir, input_name), encoding="utf-8") as f:
|
||
init_sql = f.read()
|
||
except OSError:
|
||
return None, f"测试点脚本 {input_name} 读取失败"
|
||
ref_sql = next((item["code"] for item in answers or [] if item.get("language") == "SQL" and item.get("code", "").strip()), None)
|
||
if ref_sql is None:
|
||
return None, "题目缺少 SQL 标准答案"
|
||
try:
|
||
return build_display(init_sql, ref_sql, sql_config["mode"]), None
|
||
except SQLCaseError as e:
|
||
return None, f"SQL 展示数据生成失败: {e.message}"
|