chore: 删掉 17 个确认无人调用的端点(净 -472 行)

阶段 0 盘点时判为 CUT 的 17 条,当时只在代码里标了 DEPRECATED(2026-05-26),
没删。这次删掉:15 个视图类整体移除,2 条只删 URL。

## 为什么有两条只能删 URL

ProblemSetProblemAPI 和 ProblemSetProgressAPI **各有一条在用的路径**:

  ProblemSetProblemAPI    /problemset/<id>/problems          在用
                          /problemset/<id>/problems/<pid>    废弃
  ProblemSetProgressAPI   /problemset/progress               在用
                          /problemset/<id>/progress          废弃

删类会直接搞挂"看题单题目"和"加入题单"。清单里记的是**路径**不是视图类,
这个区别不看一眼就动手会出事。

## 核实过程

不敢只信清单 —— 今天已经因为"读过文档还是漏了盲点"栽过一次(前端 /api2 前缀
漏改 5 处)。所以:

1. 用 ast(不是正则)建「视图类 → 它的所有 URL」映射,判定哪些类的全部 URL
   都废弃。正则版把 B 组算成 0 个,而我明知至少有 2 个 —— 那种"看起来干净"的
   结果最危险。
2. DEPRECATED 检测最初往前多看一行,把上一条的行内注释算到了下一条头上,
   于是 /hitokoto、/profile、/reset_password 被误判成废弃。这三个明显在用,
   才发现范围错了。
3. 按端点字符串精确搜整个 ojnext(覆盖 http.get、原生 fetch、独立 axios、
   模板插值),16/16 确认无调用。**并且用 5 个明确在用的端点反测了检测器本身** ——
   未经验证的检测器报"全部无调用"是没有意义的。

## 验证

起本地 Django + PostgreSQL 实跑 33 条断言,全过:
- 保留的端点还能用(13 条,含那两个"类保留"的路径,加入题单确认真的成功)
- 删掉的端点确实 404(17 条)
- reverse() 也解析不到了(3 条)

`ruff check .` 全绿(顺手清掉 44 个因此变成未使用的 import)。

## 一个差点造成的意外

脚本无条件重写所有 urls.py,把 class_pk/ 和 message/ 下三个 **CRLF** 文件
静默转成了 LF —— 内容没变但整文件显示为改动。已原样恢复,最终 diff 是
纯删除,零意外改动。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 06:59:00 -06:00
parent 4ebcecfab4
commit eed0e630e6
18 changed files with 11 additions and 483 deletions

View File

@@ -1,9 +1,8 @@
from django.urls import path from django.urls import path
from ..views.admin import GenerateUserAPI, ResetUserPasswordAPI, UserAdminAPI from ..views.admin import ResetUserPasswordAPI, UserAdminAPI
urlpatterns = [ urlpatterns = [
path("user", UserAdminAPI.as_view()), path("user", UserAdminAPI.as_view()),
path("generate_user", GenerateUserAPI.as_view()), # DEPRECATED: 前端未调用
path("reset_password", ResetUserPasswordAPI.as_view()), path("reset_password", ResetUserPasswordAPI.as_view()),
] ]

View File

@@ -1,18 +1,12 @@
from django.urls import path from django.urls import path
from ..views.oj import ( from ..views.oj import (
SSOAPI,
AvatarUploadAPI, AvatarUploadAPI,
Metrics, Metrics,
OpenAPIAppkeyAPI,
ProfileProblemDisplayIDRefreshAPI, ProfileProblemDisplayIDRefreshAPI,
SessionManagementAPI,
UserActivityRankAPI, UserActivityRankAPI,
UserChangeEmailAPI,
UserChangePasswordAPI,
UserLoginAPI, UserLoginAPI,
UserLogoutAPI, UserLogoutAPI,
UsernameOrEmailCheck,
UserProblemRankAPI, UserProblemRankAPI,
UserProfileAPI, UserProfileAPI,
UserRankAPI, UserRankAPI,
@@ -23,9 +17,6 @@ urlpatterns = [
path("login", UserLoginAPI.as_view()), path("login", UserLoginAPI.as_view()),
path("logout", UserLogoutAPI.as_view()), path("logout", UserLogoutAPI.as_view()),
path("register", UserRegisterAPI.as_view()), path("register", UserRegisterAPI.as_view()),
path("change_password", UserChangePasswordAPI.as_view()), # DEPRECATED: 前端未调用
path("change_email", UserChangeEmailAPI.as_view()), # DEPRECATED: 前端未调用
path("check_username_or_email", UsernameOrEmailCheck.as_view()), # DEPRECATED: 前端未调用
path("profile", UserProfileAPI.as_view(), name="user_profile_api"), path("profile", UserProfileAPI.as_view(), name="user_profile_api"),
path("profile/fresh_display_id", ProfileProblemDisplayIDRefreshAPI.as_view()), path("profile/fresh_display_id", ProfileProblemDisplayIDRefreshAPI.as_view()),
path("metrics", Metrics.as_view()), path("metrics", Metrics.as_view()),
@@ -33,10 +24,4 @@ urlpatterns = [
path("user_rank", UserRankAPI.as_view()), path("user_rank", UserRankAPI.as_view()),
path("user_activity_rank", UserActivityRankAPI.as_view()), path("user_activity_rank", UserActivityRankAPI.as_view()),
path("user_problem_rank", UserProblemRankAPI.as_view()), path("user_problem_rank", UserProblemRankAPI.as_view()),
path("sessions", SessionManagementAPI.as_view()), # DEPRECATED: 前端未调用
path(
"open_api_appkey", # DEPRECATED: 前端未调用
OpenAPIAppkeyAPI.as_view(),
),
path("sso", SSOAPI.as_view()), # DEPRECATED: 前端未调用
] ]

View File

@@ -1,11 +1,8 @@
import os
import re import re
import xlsxwriter
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.db.models import F, Q from django.db.models import F, Q
from django.http import HttpResponse
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from submission.models import Submission from submission.models import Submission
@@ -16,7 +13,6 @@ from ..decorators import super_admin_required
from ..models import AdminType, ProblemPermission, User, UserProfile from ..models import AdminType, ProblemPermission, User, UserProfile
from ..serializers import ( from ..serializers import (
EditUserSerializer, EditUserSerializer,
GenerateUserSerializer,
ImportUserSerializer, ImportUserSerializer,
UserAdminSerializer, UserAdminSerializer,
) )
@@ -168,78 +164,6 @@ class UserAdminAPI(APIView):
return self.success() return self.success()
# DEPRECATED: 前端未调用 (2026-05-26)
class GenerateUserAPI(APIView):
@super_admin_required
def get(self, request):
"""
download users excel
"""
file_id = request.GET.get("file_id")
if not file_id:
return self.error("Invalid Parameter, file_id is required")
if not re.match(r"^[a-zA-Z0-9]+$", file_id):
return self.error("Illegal file_id")
file_path = f"/tmp/{file_id}.xlsx"
if not os.path.isfile(file_path):
return self.error("File does not exist")
with open(file_path, "rb") as f:
raw_data = f.read()
os.remove(file_path)
response = HttpResponse(raw_data)
response["Content-Disposition"] = "attachment; filename=users.xlsx"
response["Content-Type"] = "application/xlsx"
return response
@validate_serializer(GenerateUserSerializer)
@super_admin_required
def post(self, request):
"""
Generate User
"""
data = request.data
number_max_length = max(len(str(data["number_from"])), len(str(data["number_to"])))
if number_max_length + len(data["prefix"]) + len(data["suffix"]) > 32:
return self.error("Username should not more than 32 characters")
if data["number_from"] > data["number_to"]:
return self.error("Start number must be lower than end number")
file_id = rand_str(8)
filename = f"/tmp/{file_id}.xlsx"
workbook = xlsxwriter.Workbook(filename)
worksheet = workbook.add_worksheet()
worksheet.set_column("A:B", 20)
worksheet.write("A1", "Username")
worksheet.write("B1", "Password")
i = 1
user_list = []
for number in range(data["number_from"], data["number_to"] + 1):
raw_password = rand_str(data["password_length"])
user = User(
username=f"{data['prefix']}{number}{data['suffix']}",
password=make_password(raw_password),
)
user.raw_password = raw_password
user_list.append(user)
try:
with transaction.atomic():
ret = User.objects.bulk_create(user_list)
UserProfile.objects.bulk_create([UserProfile(user=user) for user in ret])
for item in user_list:
worksheet.write_string(i, 0, item.username)
worksheet.write_string(i, 1, item.raw_password)
i += 1
workbook.close()
return self.success({"file_id": file_id})
except IntegrityError as e:
# Extract detail from exception message
# duplicate key value violates unique constraint "user_username_key"
# DETAIL: Key (username)=(root11) already exists.
return self.error(str(e).split("\n")[1])
class ResetUserPasswordAPI(APIView): class ResetUserPasswordAPI(APIView):
@super_admin_required @super_admin_required
def post(self, request): def post(self, request):

View File

@@ -1,18 +1,17 @@
import asyncio import asyncio
import os import os
from importlib import import_module
from django.conf import settings from django.conf import settings
from django.contrib import auth from django.contrib import auth
from django.db.models import Count, Q from django.db.models import Count, Q
from django.utils import timezone from django.utils import timezone
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from options.options import SysOptions from options.options import SysOptions
from problem.models import Problem from problem.models import Problem
from submission.models import JudgeStatus, Submission from submission.models import JudgeStatus, Submission
from utils.api import APIView, AsyncAPIView, CSRFExemptAPIView, validate_serializer from utils.api import AsyncAPIView, validate_serializer
from utils.async_helpers import async_cache_get, async_cache_set from utils.async_helpers import async_cache_get, async_cache_set
from utils.constants import CacheKey from utils.constants import CacheKey
from utils.shortcuts import datetime2str, rand_str from utils.shortcuts import datetime2str, rand_str
@@ -23,11 +22,7 @@ from ..serializers import (
EditUserProfileSerializer, EditUserProfileSerializer,
ImageUploadForm, ImageUploadForm,
RankInfoSerializer, RankInfoSerializer,
SSOSerializer,
UserChangeEmailSerializer,
UserChangePasswordSerializer,
UserLoginSerializer, UserLoginSerializer,
UsernameOrEmailCheckSerializer,
UserProfileSerializer, UserProfileSerializer,
UserRegisterSerializer, UserRegisterSerializer,
) )
@@ -132,23 +127,6 @@ class UserLogoutAPI(AsyncAPIView):
return self.success() return self.success()
# DEPRECATED: 前端未调用 (2026-05-26)
class UsernameOrEmailCheck(APIView):
@validate_serializer(UsernameOrEmailCheckSerializer)
def post(self, request):
"""
check username or email is duplicate
"""
data = request.data
# True means already exist.
result = {"username": False, "email": False}
if data.get("username"):
result["username"] = User.objects.filter(username=data["username"].lower()).exists()
if data.get("email"):
result["email"] = User.objects.filter(email=data["email"].lower()).exists()
return self.success(result)
class UserRegisterAPI(AsyncAPIView): class UserRegisterAPI(AsyncAPIView):
@validate_serializer(UserRegisterSerializer) @validate_serializer(UserRegisterSerializer)
async def post(self, request): async def post(self, request):
@@ -169,87 +147,6 @@ class UserRegisterAPI(AsyncAPIView):
return self.success("Succeeded") return self.success("Succeeded")
# DEPRECATED: 前端未调用 (2026-05-26)
class UserChangeEmailAPI(APIView):
@validate_serializer(UserChangeEmailSerializer)
@login_required
def post(self, request):
data = request.data
user = auth.authenticate(username=request.user.username, password=data["password"])
if user:
data["new_email"] = data["new_email"].lower()
if User.objects.filter(email=data["new_email"]).exists():
return self.error("The email is owned by other account")
user.email = data["new_email"]
user.save()
return self.success("Succeeded")
else:
return self.error("Wrong password")
# DEPRECATED: 前端未调用 (2026-05-26)
class UserChangePasswordAPI(APIView):
@validate_serializer(UserChangePasswordSerializer)
@login_required
def post(self, request):
"""
User change password api
"""
data = request.data
username = request.user.username
user = auth.authenticate(username=username, password=data["old_password"])
if user:
user.set_password(data["new_password"])
user.save()
return self.success("Succeeded")
else:
return self.error("Invalid old password")
# DEPRECATED: 前端未调用 (2026-05-26)
class SessionManagementAPI(APIView):
@login_required
def get(self, request):
engine = import_module(settings.SESSION_ENGINE)
session_store = engine.SessionStore
current_session = request.session.session_key
session_keys = request.user.session_keys
result = []
modified = False
for key in session_keys[:]:
session = session_store(key)
# session does not exist or is expiry
if not session._session:
session_keys.remove(key)
modified = True
continue
s = {}
if current_session == key:
s["current_session"] = True
s["ip"] = session["ip"]
s["user_agent"] = session["user_agent"]
s["last_activity"] = datetime2str(session["last_activity"])
s["session_key"] = key
result.append(s)
if modified:
request.user.save()
return self.success(result)
@login_required
def delete(self, request):
session_key = request.GET.get("session_key")
if not session_key:
return self.error("Parameter Error")
request.session.delete(session_key)
if session_key in request.user.session_keys:
request.user.session_keys.remove(session_key)
request.user.save()
return self.success("Succeeded")
else:
return self.error("Invalid session_key")
class UserRankAPI(AsyncAPIView): class UserRankAPI(AsyncAPIView):
async def get(self, request): async def get(self, request):
username = request.GET.get("username", "") username = request.GET.get("username", "")
@@ -354,40 +251,3 @@ class ProfileProblemDisplayIDRefreshAPI(AsyncAPIView):
await profile.asave(update_fields=["acm_problems_status"]) await profile.asave(update_fields=["acm_problems_status"])
return self.success() return self.success()
# DEPRECATED: 前端未调用 (2026-05-26)
class OpenAPIAppkeyAPI(APIView):
@login_required
def post(self, request):
user = request.user
if not user.open_api:
return self.error("OpenAPI function is truned off for you")
api_appkey = rand_str()
user.open_api_appkey = api_appkey
user.save()
return self.success({"appkey": api_appkey})
# DEPRECATED: 前端未调用 (2026-05-26)
class SSOAPI(CSRFExemptAPIView):
@login_required
def get(self, request):
token = rand_str()
request.user.auth_token = token
request.user.save()
return self.success({"token": token})
@method_decorator(csrf_exempt)
@validate_serializer(SSOSerializer)
def post(self, request):
try:
user = User.objects.get(auth_token=request.data["token"])
except User.DoesNotExist:
return self.error("User does not exist")
return self.success(
{
"username": user.username,
"avatar": user.userprofile.avatar,
"admin_type": user.admin_type,
}
)

View File

@@ -4,7 +4,6 @@ from ..views import (
ClassUsernamesAPI, ClassUsernamesAPI,
HitokotoAPI, HitokotoAPI,
JudgeServerHeartbeatAPI, JudgeServerHeartbeatAPI,
LanguagesAPI,
WebsiteConfigAPI, WebsiteConfigAPI,
) )
@@ -12,7 +11,6 @@ urlpatterns = [
path("website", WebsiteConfigAPI.as_view()), path("website", WebsiteConfigAPI.as_view()),
# 这里必须要有 / # 这里必须要有 /
path("judge_server_heartbeat/", JudgeServerHeartbeatAPI.as_view()), path("judge_server_heartbeat/", JudgeServerHeartbeatAPI.as_view()),
path("languages", LanguagesAPI.as_view()), # DEPRECATED: 前端未调用
path("hitokoto", HitokotoAPI.as_view()), path("hitokoto", HitokotoAPI.as_view()),
path("class_usernames", ClassUsernamesAPI.as_view()), path("class_usernames", ClassUsernamesAPI.as_view()),
] ]

View File

@@ -134,16 +134,6 @@ class JudgeServerHeartbeatAPI(CSRFExemptAPIView):
return self.success() return self.success()
# DEPRECATED: 前端未调用 (2026-05-26)
class LanguagesAPI(APIView):
def get(self, request):
return self.success(
{
"languages": SysOptions.languages,
}
)
class TestCasePruneAPI(APIView): class TestCasePruneAPI(APIView):
@super_admin_required @super_admin_required
def get(self, request): def get(self, request):

View File

@@ -1,11 +1,9 @@
from django.urls import path from django.urls import path
from ..views.admin import ACMContestHelper, ContestAnnouncementAPI, ContestAPI, ContestCloneAPI, DownloadContestSubmissions from ..views.admin import ACMContestHelper, ContestAPI, ContestCloneAPI
urlpatterns = [ urlpatterns = [
path("contest", ContestAPI.as_view()), path("contest", ContestAPI.as_view()),
path("contest/clone", ContestCloneAPI.as_view()), path("contest/clone", ContestCloneAPI.as_view()),
path("contest/announcement", ContestAnnouncementAPI.as_view()), # DEPRECATED: 前端未调用
path("contest/acm_helper", ACMContestHelper.as_view()), path("contest/acm_helper", ACMContestHelper.as_view()),
path("download_submissions", DownloadContestSubmissions.as_view()), # DEPRECATED: 前端未调用
] ]

View File

@@ -1,12 +1,11 @@
from django.urls import path from django.urls import path
from ..views.oj import ContestAccessAPI, ContestAnnouncementListAPI, ContestAPI, ContestListAPI, ContestPasswordVerifyAPI, ContestRankAPI from ..views.oj import ContestAccessAPI, ContestAPI, ContestListAPI, ContestPasswordVerifyAPI, ContestRankAPI
urlpatterns = [ urlpatterns = [
path("contests", ContestListAPI.as_view()), path("contests", ContestListAPI.as_view()),
path("contest", ContestAPI.as_view()), path("contest", ContestAPI.as_view()),
path("contest/password", ContestPasswordVerifyAPI.as_view()), path("contest/password", ContestPasswordVerifyAPI.as_view()),
path("contest/announcement", ContestAnnouncementListAPI.as_view()), # DEPRECATED: 前端未调用
path("contest/access", ContestAccessAPI.as_view()), path("contest/access", ContestAccessAPI.as_view()),
path("contest_rank", ContestRankAPI.as_view()), path("contest_rank", ContestRankAPI.as_view()),
] ]

View File

@@ -1,30 +1,19 @@
import copy
import os
import zipfile
from datetime import datetime, timedelta from datetime import datetime, timedelta
from ipaddress import ip_network from ipaddress import ip_network
from django.http import FileResponse
from django.utils.timezone import now from django.utils.timezone import now
from account.decorators import ensure_created_by, super_admin_required, teacher_admin_required from account.decorators import ensure_created_by, teacher_admin_required
from account.models import User
from problem.models import Problem from problem.models import Problem
from submission.models import JudgeStatus, Submission
from utils.api import APIView, validate_serializer from utils.api import APIView, validate_serializer
from utils.shortcuts import rand_str
from utils.tasks import delete_files
from ..models import ACMContestRank, Contest, ContestAnnouncement from ..models import ACMContestRank, Contest
from ..serializers import ( from ..serializers import (
ACMContesHelperSerializer, ACMContesHelperSerializer,
ContestAdminSerializer, ContestAdminSerializer,
ContestAnnouncementSerializer,
ContestCloneSerializer, ContestCloneSerializer,
CreateConetestSeriaizer, CreateConetestSeriaizer,
CreateContestAnnouncementSerializer,
EditConetestSeriaizer, EditConetestSeriaizer,
EditContestAnnouncementSerializer,
) )
@@ -96,73 +85,6 @@ class ContestAPI(APIView):
return self.success(self.paginate_data(request, contests, ContestAdminSerializer)) return self.success(self.paginate_data(request, contests, ContestAdminSerializer))
# DEPRECATED: 前端未调用 (2026-05-26)
class ContestAnnouncementAPI(APIView):
@validate_serializer(CreateContestAnnouncementSerializer)
@super_admin_required
def post(self, request):
"""
Create one contest_announcement.
"""
data = request.data
try:
contest = Contest.objects.get(id=data.pop("contest_id"))
data["contest"] = contest
data["created_by"] = request.user
except Contest.DoesNotExist:
return self.error("Contest does not exist")
announcement = ContestAnnouncement.objects.create(**data)
return self.success(ContestAnnouncementSerializer(announcement).data)
@validate_serializer(EditContestAnnouncementSerializer)
@super_admin_required
def put(self, request):
"""
update contest_announcement
"""
data = request.data
try:
contest_announcement = ContestAnnouncement.objects.get(id=data.pop("id"))
except ContestAnnouncement.DoesNotExist:
return self.error("Contest announcement does not exist")
for k, v in data.items():
setattr(contest_announcement, k, v)
contest_announcement.save()
return self.success()
@super_admin_required
def delete(self, request):
"""
Delete one contest_announcement.
"""
contest_announcement_id = request.GET.get("id")
if contest_announcement_id:
ContestAnnouncement.objects.filter(id=contest_announcement_id).delete()
return self.success()
@super_admin_required
def get(self, request):
"""
Get one contest_announcement or contest_announcement list.
"""
contest_announcement_id = request.GET.get("id")
if contest_announcement_id:
try:
contest_announcement = ContestAnnouncement.objects.get(id=contest_announcement_id)
return self.success(ContestAnnouncementSerializer(contest_announcement).data)
except ContestAnnouncement.DoesNotExist:
return self.error("Contest announcement does not exist")
contest_id = request.GET.get("contest_id")
if not contest_id:
return self.error("Parameter error")
contest_announcements = ContestAnnouncement.objects.filter(contest_id=contest_id)
keyword = request.GET.get("keyword")
if keyword:
contest_announcements = contest_announcements.filter(title__contains=keyword)
return self.success(ContestAnnouncementSerializer(contest_announcements, many=True).data)
class ACMContestHelper(APIView): class ACMContestHelper(APIView):
@teacher_admin_required @teacher_admin_required
def get(self, request): def get(self, request):
@@ -217,55 +139,6 @@ class ACMContestHelper(APIView):
return self.success() return self.success()
# DEPRECATED: 前端未调用 (2026-05-26)
class DownloadContestSubmissions(APIView):
def _dump_submissions(self, contest, exclude_admin=True):
problem_ids = contest.problem_set.all().values_list("id", "_id")
id2display_id = {k[0]: k[1] for k in problem_ids}
ac_map = {k[0]: False for k in problem_ids}
submissions = Submission.objects.filter(contest=contest, result__in=[JudgeStatus.ACCEPTED, JudgeStatus.AST_CHECK_FAILED]).order_by("-create_time")
user_ids = submissions.values_list("user_id", flat=True)
users = User.objects.filter(id__in=user_ids)
path = f"/tmp/{rand_str()}.zip"
with zipfile.ZipFile(path, "w") as zip_file:
for user in users:
if user.is_admin_role() and exclude_admin:
continue
user_ac_map = copy.deepcopy(ac_map)
user_submissions = submissions.filter(user_id=user.id)
for submission in user_submissions:
problem_id = submission.problem_id
if user_ac_map[problem_id]:
continue
file_name = f"{user.username}_{id2display_id[submission.problem_id]}.txt"
compression = zipfile.ZIP_DEFLATED
zip_file.writestr(
zinfo_or_arcname=f"{file_name}",
data=submission.code,
compress_type=compression,
)
user_ac_map[problem_id] = True
return path
@super_admin_required
def get(self, request):
contest_id = request.GET.get("contest_id")
if not contest_id:
return self.error("Parameter error")
try:
contest = Contest.objects.get(id=contest_id)
except Contest.DoesNotExist:
return self.error("Contest does not exist")
exclude_admin = request.GET.get("exclude_admin") == "1"
zip_path = self._dump_submissions(contest, exclude_admin)
delete_files.send_with_options(args=(zip_path,), delay=300_000)
resp = FileResponse(open(zip_path, "rb"))
resp["Content-Type"] = "application/zip"
resp["Content-Disposition"] = f"attachment;filename={os.path.basename(zip_path)}"
return resp
class ContestCloneAPI(APIView): class ContestCloneAPI(APIView):
@validate_serializer(ContestCloneSerializer) @validate_serializer(ContestCloneSerializer)
@teacher_admin_required @teacher_admin_required

View File

@@ -16,23 +16,8 @@ from utils.api import AsyncAPIView, validate_serializer
from utils.constants import CONTEST_PASSWORD_SESSION_KEY, ContestStatus from utils.constants import CONTEST_PASSWORD_SESSION_KEY, ContestStatus
from utils.shortcuts import check_is_id, datetime2str from utils.shortcuts import check_is_id, datetime2str
from ..models import ACMContestRank, Contest, ContestAnnouncement from ..models import ACMContestRank, Contest
from ..serializers import ACMContestRankSerializer, ContestAnnouncementSerializer, ContestPasswordVerifySerializer, ContestSerializer from ..serializers import ACMContestRankSerializer, ContestPasswordVerifySerializer, ContestSerializer
# DEPRECATED: 前端未调用 (2026-05-26)
class ContestAnnouncementListAPI(AsyncAPIView):
@check_contest_permission(check_type="announcements")
async def get(self, request):
contest_id = request.GET.get("contest_id")
if not contest_id:
return self.error("Invalid parameter, contest_id is required")
qs = ContestAnnouncement.objects.select_related("created_by").filter(contest_id=contest_id, visible=True)
max_id = request.GET.get("max_id")
if max_id:
qs = qs.filter(id__gt=max_id)
data = await self.async_serialize_data(ContestAnnouncementSerializer, [item async for item in qs], many=True)
return self.success(data)
class ContestAPI(AsyncAPIView): class ContestAPI(AsyncAPIView):

View File

@@ -7,7 +7,6 @@ from problemset.views.admin import (
ProblemSetProblemAdminAPI, ProblemSetProblemAdminAPI,
ProblemSetProgressAdminAPI, ProblemSetProgressAdminAPI,
ProblemSetStatusAPI, ProblemSetStatusAPI,
ProblemSetSyncAPI,
ProblemSetVisibleAPI, ProblemSetVisibleAPI,
) )
@@ -63,9 +62,4 @@ urlpatterns = [
name="admin_problemset_progress_detail_api", name="admin_problemset_progress_detail_api",
), ),
# 题单同步管理API # 题单同步管理API
path( # DEPRECATED: 前端未调用
"problemset/<int:problem_set_id>/sync",
ProblemSetSyncAPI.as_view(),
name="admin_problemset_sync_api",
),
] ]

View File

@@ -8,7 +8,6 @@ from problemset.views.oj import (
ProblemSetProgressAPI, ProblemSetProgressAPI,
ProblemSetUserProgressAPI, ProblemSetUserProgressAPI,
UserBadgeAPI, UserBadgeAPI,
UserProgressAPI,
) )
urlpatterns = [ urlpatterns = [
@@ -24,23 +23,12 @@ urlpatterns = [
ProblemSetProblemAPI.as_view(), ProblemSetProblemAPI.as_view(),
name="problemset_problems_api", name="problemset_problems_api",
), ),
path( # DEPRECATED: 前端未调用
"problemset/<int:problem_set_id>/problems/<int:problem_id>",
ProblemSetProblemAPI.as_view(),
name="problemset_problem_detail_api",
),
# 进度相关API # 进度相关API
path( path(
"problemset/progress", "problemset/progress",
ProblemSetProgressAPI.as_view(), ProblemSetProgressAPI.as_view(),
name="problemset_progress_api", name="problemset_progress_api",
), ),
path( # DEPRECATED: 前端未调用
"problemset/<int:problem_set_id>/progress",
ProblemSetProgressAPI.as_view(),
name="problemset_progress_detail_api",
),
path("user/progress", UserProgressAPI.as_view(), name="user_progress_api"), # DEPRECATED: 前端未调用
# 奖章相关API # 奖章相关API
path("user/badges", UserBadgeAPI.as_view(), name="user_badges_api"), path("user/badges", UserBadgeAPI.as_view(), name="user_badges_api"),
path( path(

View File

@@ -347,25 +347,6 @@ class ProblemSetProgressAdminAPI(APIView):
return self.error("用户未加入该题单") return self.error("用户未加入该题单")
# DEPRECATED: 前端未调用 (2026-05-26)
class ProblemSetSyncAPI(APIView):
"""题单同步管理API"""
@teacher_admin_required
def post(self, request, problem_set_id):
"""手动同步题单的所有用户进度(管理员)"""
try:
problem_set = ProblemSet.objects.get(id=problem_set_id)
ensure_created_by(problem_set, request.user)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
# 同步所有用户的进度
synced_count = ProblemSetProgress.sync_all_progress_for_problemset(problem_set)
return self.success(f"已同步 {synced_count} 个用户的进度")
class ProblemSetVisibleAPI(APIView): class ProblemSetVisibleAPI(APIView):
"""题单可见性管理API""" """题单可见性管理API"""

View File

@@ -259,18 +259,6 @@ class ProblemSetProgressAPI(APIView):
notify_badges(progress.user_id, earned) notify_badges(progress.user_id, earned)
# DEPRECATED: 前端未调用 (2026-05-26)
class UserProgressAPI(APIView):
"""用户进度API"""
@login_required
def get(self, request):
"""获取用户的题单进度列表"""
progress_list = ProblemSetProgress.objects.filter(user=request.user).order_by("-join_time")
serializer = ProblemSetProgressSerializer(progress_list, many=True)
return self.success(serializer.data)
class UserBadgeAPI(AsyncAPIView): class UserBadgeAPI(AsyncAPIView):
"""用户奖章API""" """用户奖章API"""

View File

@@ -4,7 +4,6 @@ from ..views.oj import (
ContestSubmissionListAPI, ContestSubmissionListAPI,
FormatCodeAPI, FormatCodeAPI,
SubmissionAPI, SubmissionAPI,
SubmissionExistsAPI,
SubmissionListAPI, SubmissionListAPI,
SubmissionsTodayCount, SubmissionsTodayCount,
) )
@@ -13,7 +12,6 @@ urlpatterns = [
path("submission", SubmissionAPI.as_view()), path("submission", SubmissionAPI.as_view()),
path("submissions", SubmissionListAPI.as_view()), path("submissions", SubmissionListAPI.as_view()),
path("submissions/today_count", SubmissionsTodayCount.as_view()), path("submissions/today_count", SubmissionsTodayCount.as_view()),
path("submission_exists", SubmissionExistsAPI.as_view()), # DEPRECATED: 前端未调用
path("contest_submissions", ContestSubmissionListAPI.as_view()), path("contest_submissions", ContestSubmissionListAPI.as_view()),
path("format_code", FormatCodeAPI.as_view()), path("format_code", FormatCodeAPI.as_view()),
] ]

View File

@@ -219,15 +219,6 @@ class ContestSubmissionListAPI(AsyncAPIView):
return self.success(data) return self.success(data)
# DEPRECATED: 前端未调用 (2026-05-26)
class SubmissionExistsAPI(AsyncAPIView):
async def get(self, request):
if not request.GET.get("problem_id"):
return self.error("Parameter error, problem_id is required")
exists = request.user.is_authenticated and await Submission.objects.filter(problem_id=request.GET["problem_id"], user_id=request.user.id).aexists()
return self.success(exists)
class SubmissionsTodayCount(AsyncAPIView): class SubmissionsTodayCount(AsyncAPIView):
async def get(self, request): async def get(self, request):
now = timezone.now() now = timezone.now()

View File

@@ -1,8 +1,7 @@
from django.urls import path from django.urls import path
from .views import SimditorFileUploadAPIView, SimditorImageUploadAPIView from .views import SimditorImageUploadAPIView
urlpatterns = [ urlpatterns = [
path("upload_image", SimditorImageUploadAPIView.as_view()), path("upload_image", SimditorImageUploadAPIView.as_view()),
path("upload_file", SimditorFileUploadAPIView.as_view()), # DEPRECATED: 前端未调用
] ]

View File

@@ -3,7 +3,7 @@ import os
from django.conf import settings from django.conf import settings
from account.serializers import FileUploadForm, ImageUploadForm from account.serializers import ImageUploadForm
from utils.api import CSRFExemptAPIView from utils.api import CSRFExemptAPIView
from utils.shortcuts import rand_str from utils.shortcuts import rand_str
@@ -33,25 +33,3 @@ class SimditorImageUploadAPIView(CSRFExemptAPIView):
return self.response({"success": False, "msg": "Upload Error", "file_path": ""}) return self.response({"success": False, "msg": "Upload Error", "file_path": ""})
return self.response({"success": True, "msg": "Success", "file_path": f"{settings.UPLOAD_PREFIX}/{img_name}"}) return self.response({"success": True, "msg": "Success", "file_path": f"{settings.UPLOAD_PREFIX}/{img_name}"})
# DEPRECATED: 前端未调用 (2026-05-26)
class SimditorFileUploadAPIView(CSRFExemptAPIView):
request_parsers = ()
def post(self, request):
form = FileUploadForm(request.POST, request.FILES)
if form.is_valid():
file = form.cleaned_data["file"]
else:
return self.response({"success": False, "msg": "Upload failed"})
suffix = os.path.splitext(file.name)[-1].lower()
file_name = rand_str(10) + suffix
try:
with open(os.path.join(settings.UPLOAD_DIR, file_name), "wb") as f:
for chunk in file:
f.write(chunk)
except IOError as e:
logger.error(e)
return self.response({"success": False, "msg": "Upload Error"})
return self.response({"success": True, "msg": "Success", "file_path": f"{settings.UPLOAD_PREFIX}/{file_name}", "file_name": file.name})