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 ..views.admin import GenerateUserAPI, ResetUserPasswordAPI, UserAdminAPI
from ..views.admin import ResetUserPasswordAPI, UserAdminAPI
urlpatterns = [
path("user", UserAdminAPI.as_view()),
path("generate_user", GenerateUserAPI.as_view()), # DEPRECATED: 前端未调用
path("reset_password", ResetUserPasswordAPI.as_view()),
]

View File

@@ -1,18 +1,12 @@
from django.urls import path
from ..views.oj import (
SSOAPI,
AvatarUploadAPI,
Metrics,
OpenAPIAppkeyAPI,
ProfileProblemDisplayIDRefreshAPI,
SessionManagementAPI,
UserActivityRankAPI,
UserChangeEmailAPI,
UserChangePasswordAPI,
UserLoginAPI,
UserLogoutAPI,
UsernameOrEmailCheck,
UserProblemRankAPI,
UserProfileAPI,
UserRankAPI,
@@ -23,9 +17,6 @@ urlpatterns = [
path("login", UserLoginAPI.as_view()),
path("logout", UserLogoutAPI.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/fresh_display_id", ProfileProblemDisplayIDRefreshAPI.as_view()),
path("metrics", Metrics.as_view()),
@@ -33,10 +24,4 @@ urlpatterns = [
path("user_rank", UserRankAPI.as_view()),
path("user_activity_rank", UserActivityRankAPI.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 xlsxwriter
from django.contrib.auth.hashers import make_password
from django.db import IntegrityError, transaction
from django.db.models import F, Q
from django.http import HttpResponse
from django.utils.crypto import get_random_string
from submission.models import Submission
@@ -16,7 +13,6 @@ from ..decorators import super_admin_required
from ..models import AdminType, ProblemPermission, User, UserProfile
from ..serializers import (
EditUserSerializer,
GenerateUserSerializer,
ImportUserSerializer,
UserAdminSerializer,
)
@@ -168,78 +164,6 @@ class UserAdminAPI(APIView):
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):
@super_admin_required
def post(self, request):

View File

@@ -1,18 +1,17 @@
import asyncio
import os
from importlib import import_module
from django.conf import settings
from django.contrib import auth
from django.db.models import Count, Q
from django.utils import timezone
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 problem.models import Problem
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.constants import CacheKey
from utils.shortcuts import datetime2str, rand_str
@@ -23,11 +22,7 @@ from ..serializers import (
EditUserProfileSerializer,
ImageUploadForm,
RankInfoSerializer,
SSOSerializer,
UserChangeEmailSerializer,
UserChangePasswordSerializer,
UserLoginSerializer,
UsernameOrEmailCheckSerializer,
UserProfileSerializer,
UserRegisterSerializer,
)
@@ -132,23 +127,6 @@ class UserLogoutAPI(AsyncAPIView):
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):
@validate_serializer(UserRegisterSerializer)
async def post(self, request):
@@ -169,87 +147,6 @@ class UserRegisterAPI(AsyncAPIView):
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):
async def get(self, request):
username = request.GET.get("username", "")
@@ -354,40 +251,3 @@ class ProfileProblemDisplayIDRefreshAPI(AsyncAPIView):
await profile.asave(update_fields=["acm_problems_status"])
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,
HitokotoAPI,
JudgeServerHeartbeatAPI,
LanguagesAPI,
WebsiteConfigAPI,
)
@@ -12,7 +11,6 @@ urlpatterns = [
path("website", WebsiteConfigAPI.as_view()),
# 这里必须要有 /
path("judge_server_heartbeat/", JudgeServerHeartbeatAPI.as_view()),
path("languages", LanguagesAPI.as_view()), # DEPRECATED: 前端未调用
path("hitokoto", HitokotoAPI.as_view()),
path("class_usernames", ClassUsernamesAPI.as_view()),
]

View File

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

View File

@@ -1,11 +1,9 @@
from django.urls import path
from ..views.admin import ACMContestHelper, ContestAnnouncementAPI, ContestAPI, ContestCloneAPI, DownloadContestSubmissions
from ..views.admin import ACMContestHelper, ContestAPI, ContestCloneAPI
urlpatterns = [
path("contest", ContestAPI.as_view()),
path("contest/clone", ContestCloneAPI.as_view()),
path("contest/announcement", ContestAnnouncementAPI.as_view()), # DEPRECATED: 前端未调用
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 ..views.oj import ContestAccessAPI, ContestAnnouncementListAPI, ContestAPI, ContestListAPI, ContestPasswordVerifyAPI, ContestRankAPI
from ..views.oj import ContestAccessAPI, ContestAPI, ContestListAPI, ContestPasswordVerifyAPI, ContestRankAPI
urlpatterns = [
path("contests", ContestListAPI.as_view()),
path("contest", ContestAPI.as_view()),
path("contest/password", ContestPasswordVerifyAPI.as_view()),
path("contest/announcement", ContestAnnouncementListAPI.as_view()), # DEPRECATED: 前端未调用
path("contest/access", ContestAccessAPI.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 ipaddress import ip_network
from django.http import FileResponse
from django.utils.timezone import now
from account.decorators import ensure_created_by, super_admin_required, teacher_admin_required
from account.models import User
from account.decorators import ensure_created_by, teacher_admin_required
from problem.models import Problem
from submission.models import JudgeStatus, Submission
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 (
ACMContesHelperSerializer,
ContestAdminSerializer,
ContestAnnouncementSerializer,
ContestCloneSerializer,
CreateConetestSeriaizer,
CreateContestAnnouncementSerializer,
EditConetestSeriaizer,
EditContestAnnouncementSerializer,
)
@@ -96,73 +85,6 @@ class ContestAPI(APIView):
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):
@teacher_admin_required
def get(self, request):
@@ -217,55 +139,6 @@ class ACMContestHelper(APIView):
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):
@validate_serializer(ContestCloneSerializer)
@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.shortcuts import check_is_id, datetime2str
from ..models import ACMContestRank, Contest, ContestAnnouncement
from ..serializers import ACMContestRankSerializer, ContestAnnouncementSerializer, 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)
from ..models import ACMContestRank, Contest
from ..serializers import ACMContestRankSerializer, ContestPasswordVerifySerializer, ContestSerializer
class ContestAPI(AsyncAPIView):

View File

@@ -7,7 +7,6 @@ from problemset.views.admin import (
ProblemSetProblemAdminAPI,
ProblemSetProgressAdminAPI,
ProblemSetStatusAPI,
ProblemSetSyncAPI,
ProblemSetVisibleAPI,
)
@@ -63,9 +62,4 @@ urlpatterns = [
name="admin_problemset_progress_detail_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,
ProblemSetUserProgressAPI,
UserBadgeAPI,
UserProgressAPI,
)
urlpatterns = [
@@ -24,23 +23,12 @@ urlpatterns = [
ProblemSetProblemAPI.as_view(),
name="problemset_problems_api",
),
path( # DEPRECATED: 前端未调用
"problemset/<int:problem_set_id>/problems/<int:problem_id>",
ProblemSetProblemAPI.as_view(),
name="problemset_problem_detail_api",
),
# 进度相关API
path(
"problemset/progress",
ProblemSetProgressAPI.as_view(),
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
path("user/badges", UserBadgeAPI.as_view(), name="user_badges_api"),
path(

View File

@@ -347,25 +347,6 @@ class ProblemSetProgressAdminAPI(APIView):
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):
"""题单可见性管理API"""

View File

@@ -259,18 +259,6 @@ class ProblemSetProgressAPI(APIView):
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):
"""用户奖章API"""

View File

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

View File

@@ -219,15 +219,6 @@ class ContestSubmissionListAPI(AsyncAPIView):
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):
async def get(self, request):
now = timezone.now()

View File

@@ -1,8 +1,7 @@
from django.urls import path
from .views import SimditorFileUploadAPIView, SimditorImageUploadAPIView
from .views import SimditorImageUploadAPIView
urlpatterns = [
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 account.serializers import FileUploadForm, ImageUploadForm
from account.serializers import ImageUploadForm
from utils.api import CSRFExemptAPIView
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": 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})