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:
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user