style: ruff format 全仓库

行宽 180 下把历史遗留的折行表达式合并,无语义改动。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 04:45:26 -06:00
parent db104ac091
commit 3a9ab83ba5
56 changed files with 455 additions and 769 deletions

View File

@@ -62,6 +62,7 @@ class APIView(View):
- self.response 返回一个django HttpResponse, 具体在self.response_class中实现
- parse请求的类需要定义在request_parser中, 目前只支持json和urlencoded的类型, 用来解析请求的数据
"""
request_parsers = (JSONParser, URLEncodedParser)
response_class = JSONResponse
@@ -134,11 +135,10 @@ class APIView(View):
offset = 0
# 只调用一次 count(),避免重复查询
count = query_set.count()
results = query_set[offset:offset + limit]
results = query_set[offset : offset + limit]
if object_serializer:
results = object_serializer(results, many=True, context={"request": request}).data
data = {"results": results,
"total": count}
data = {"results": results, "total": count}
return data
def dispatch(self, request, *args, **kwargs):
@@ -215,8 +215,9 @@ class AsyncAPIView(APIView):
offset = 0
if offset < 0:
offset = 0
async def _slice():
return [item async for item in query_set[offset:offset + limit]]
return [item async for item in query_set[offset : offset + limit]]
count, results = await asyncio.gather(
query_set.acount(),
@@ -245,8 +246,10 @@ def validate_serializer(serializer):
def post(self, request):
return self.success(request.data)
"""
def validate(view_method):
if inspect.iscoroutinefunction(view_method):
@functools.wraps(view_method)
async def async_handle(*args, **kwargs):
self = args[0]
@@ -261,6 +264,7 @@ def validate_serializer(serializer):
return response
else:
return self.invalid_serializer(s)
return async_handle
@functools.wraps(view_method)
@@ -274,6 +278,7 @@ def validate_serializer(serializer):
return view_method(*args, **kwargs)
else:
return self.invalid_serializer(s)
return handle
return validate

View File

@@ -15,7 +15,7 @@ class Command(BaseCommand):
password = options["password"]
action = options["action"]
if not(username and password and action):
if not (username and password and action):
self.stdout.write(self.style.ERROR("Invalid args"))
exit(1)
@@ -24,8 +24,7 @@ class Command(BaseCommand):
self.stdout.write(self.style.SUCCESS(f"User {username} exists, operation ignored"))
exit()
user = User.objects.create(username=username, admin_type=AdminType.SUPER_ADMIN,
problem_permission=ProblemPermission.ALL)
user = User.objects.create(username=username, admin_type=AdminType.SUPER_ADMIN, problem_permission=ProblemPermission.ALL)
user.set_password(password)
user.save()
UserProfile.objects.create(user=user)

View File

@@ -37,7 +37,7 @@ def build_query_string(kv_data, ignore_none=True):
query_string += "&"
else:
query_string = "?"
query_string += (k + "=" + str(v))
query_string += k + "=" + str(v)
return query_string
@@ -60,8 +60,7 @@ def datetime2str(value, format="iso-8601"):
def natural_sort_key(s, _nsre=re.compile(r"(\d+)")):
return [int(text) if text.isdigit() else text.lower()
for text in re.split(_nsre, s)]
return [int(text) if text.isdigit() else text.lower() for text in re.split(_nsre, s)]
def send_email(smtp_config, from_name, to_email, to_name, subject, content):

View File

@@ -5,6 +5,7 @@ class TokenBucket:
"""
注意对于单个key的操作不是线程安全的
"""
def __init__(self, key, capacity, fill_rate, default_capacity, redis_conn):
"""
:param capacity: 最大容量

View File

@@ -18,17 +18,11 @@ class SimditorImageUploadAPIView(CSRFExemptAPIView):
if form.is_valid():
img = form.cleaned_data["image"]
else:
return self.response({
"success": False,
"msg": "Upload failed",
"file_path": ""})
return self.response({"success": False, "msg": "Upload failed", "file_path": ""})
suffix = os.path.splitext(img.name)[-1].lower()
if suffix not in [".gif", ".jpg", ".jpeg", ".bmp", ".png"]:
return self.response({
"success": False,
"msg": "Unsupported file format",
"file_path": ""})
return self.response({"success": False, "msg": "Unsupported file format", "file_path": ""})
img_name = rand_str(10) + suffix
try:
with open(os.path.join(settings.UPLOAD_DIR, img_name), "wb") as imgFile:
@@ -36,14 +30,8 @@ class SimditorImageUploadAPIView(CSRFExemptAPIView):
imgFile.write(chunk)
except IOError as e:
logger.error(e)
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": 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)
@@ -55,10 +43,7 @@ class SimditorFileUploadAPIView(CSRFExemptAPIView):
if form.is_valid():
file = form.cleaned_data["file"]
else:
return self.response({
"success": False,
"msg": "Upload failed"
})
return self.response({"success": False, "msg": "Upload failed"})
suffix = os.path.splitext(file.name)[-1].lower()
file_name = rand_str(10) + suffix
@@ -68,11 +53,5 @@ class SimditorFileUploadAPIView(CSRFExemptAPIView):
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})
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})

View File

@@ -1,6 +1,7 @@
"""
WebSocket utility functions for pushing real-time updates
"""
import logging
from asgiref.sync import async_to_sync
@@ -12,21 +13,21 @@ logger = logging.getLogger(__name__)
def push_submission_update(submission_id: str, user_id: int, data: dict):
"""
推送提交状态更新到指定用户的 WebSocket 连接
Args:
submission_id: 提交 ID
user_id: 用户 ID
data: 要发送的数据,应该包含 type, submission_id, result 等字段
"""
channel_layer = get_channel_layer()
if channel_layer is None:
logger.warning("Channel layer is not configured, cannot push submission update")
return
# 构建组名,与 SubmissionConsumer 中的组名一致
group_name = f"submission_user_{user_id}"
try:
# 向指定用户组发送消息
# type 字段对应 consumer 中的方法名submission_update
@@ -35,7 +36,7 @@ def push_submission_update(submission_id: str, user_id: int, data: dict):
{
"type": "submission_update", # 对应 SubmissionConsumer.submission_update 方法
"data": data,
}
},
)
logger.info(f"Pushed submission update: submission_id={submission_id}, user_id={user_id}, status={data.get('status')}")
except Exception as e:
@@ -45,30 +46,27 @@ def push_submission_update(submission_id: str, user_id: int, data: dict):
def push_to_user(user_id: int, message_type: str, data: dict):
"""
向指定用户推送自定义消息
Args:
user_id: 用户 ID
message_type: 消息类型
data: 消息数据
"""
channel_layer = get_channel_layer()
if channel_layer is None:
logger.warning("Channel layer is not configured, cannot push message")
return
group_name = f"submission_user_{user_id}"
try:
async_to_sync(channel_layer.group_send)(
group_name,
{
"type": "submission_update",
"data": {
"type": message_type,
**data
},
}
"data": {"type": message_type, **data},
},
)
logger.info(f"Pushed message to user {user_id}: type={message_type}")
except Exception as e:
@@ -78,21 +76,21 @@ def push_to_user(user_id: int, message_type: str, data: dict):
def push_flowchart_evaluation_update(submission_id: str, user_id: int, data: dict):
"""
推送流程图评分状态更新到指定用户的 WebSocket 连接
Args:
submission_id: 流程图提交 ID
user_id: 用户 ID
data: 要发送的数据,应该包含 type, submission_id, score, grade, feedback 等字段
"""
channel_layer = get_channel_layer()
if channel_layer is None:
logger.warning("Channel layer is not configured, cannot push flowchart evaluation update")
return
# 构建组名,与 FlowchartConsumer 中的组名一致
group_name = f"flowchart_user_{user_id}"
try:
# 向指定用户组发送消息
# type 字段对应 consumer 中的方法名flowchart_evaluation_update
@@ -101,7 +99,7 @@ def push_flowchart_evaluation_update(submission_id: str, user_id: int, data: dic
{
"type": "flowchart_evaluation_update", # 对应 FlowchartConsumer.flowchart_evaluation_update 方法
"data": data,
}
},
)
logger.info(f"Pushed flowchart evaluation update: submission_id={submission_id}, user_id={user_id}, type={data.get('type')}")
except Exception as e:
@@ -111,33 +109,23 @@ def push_flowchart_evaluation_update(submission_id: str, user_id: int, data: dic
def push_config_update(key: str, value):
"""
推送配置更新到所有连接的客户端
Args:
key: 配置键名
value: 配置值
"""
channel_layer = get_channel_layer()
if channel_layer is None:
logger.warning("Channel layer is not configured, cannot push config update")
return
# 使用全局配置组名
group_name = "config_updates"
try:
# 向所有连接的客户端发送配置更新
async_to_sync(channel_layer.group_send)(
group_name,
{
"type": "config_update",
"data": {
"type": "config_update",
"key": key,
"value": value
}
}
)
async_to_sync(channel_layer.group_send)(group_name, {"type": "config_update", "data": {"type": "config_update", "key": key, "value": value}})
logger.info(f"Pushed config update: {key}={value}")
except Exception as e:
logger.error(f"Failed to push config update: {key}={value}, error={str(e)}")

View File

@@ -25,17 +25,52 @@ Python 2.6+ or 3.2+
Cannot defense xss in browser which is belowed IE7
浏览器版本IE7+ 或其他浏览器无法防御IE6及以下版本浏览器中的XSS
"""
import copy
import re
from html.parser import HTMLParser
class XSSHtml(HTMLParser):
allow_tags = ['a', 'img', 'br', 'strong', 'b', 'code', 'pre',
'p', 'div', 'em', 'span', 'h1', 'h2', 'h3', 'h4',
'h5', 'h6', 'blockquote', 'ul', 'ol', 'tr', 'th', 'td',
'hr', 'li', 'u', 'embed', 's', 'table', 'thead', 'tbody',
'caption', 'small', 'q', 'sup', 'sub', 'font']
allow_tags = [
"a",
"img",
"br",
"strong",
"b",
"code",
"pre",
"p",
"div",
"em",
"span",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"blockquote",
"ul",
"ol",
"tr",
"th",
"td",
"hr",
"li",
"u",
"embed",
"s",
"table",
"thead",
"tbody",
"caption",
"small",
"q",
"sup",
"sub",
"font",
]
common_attrs = ["style", "class", "name"]
nonend_tags = ["img", "hr", "br", "embed"]
tags_own_attrs = {
@@ -43,7 +78,7 @@ class XSSHtml(HTMLParser):
"a": ["href", "target", "rel", "title"],
"embed": ["src", "width", "height", "type", "allowfullscreen", "loop", "play", "wmode", "menu"],
"table": ["border", "cellpadding", "cellspacing"],
"font": ["color"]
"font": ["color"],
}
def __init__(self, allows=[]):
@@ -68,9 +103,9 @@ class XSSHtml(HTMLParser):
Get the safe html code
"""
for i in range(0, len(self.result)):
if self.result[i].strip('\n'):
if self.result[i].strip("\n"):
self.data.append(self.result[i])
return ''.join(self.data)
return "".join(self.data)
def handle_startendtag(self, tag, attrs):
self.handle_starttag(tag, attrs)
@@ -78,7 +113,7 @@ class XSSHtml(HTMLParser):
def handle_starttag(self, tag, attrs):
if tag not in self.allow_tags:
return
end_diagonal = ' /' if tag in self.nonend_tags else ''
end_diagonal = " /" if tag in self.nonend_tags else ""
if not end_diagonal:
self.start.append(tag)
attdict = {}
@@ -92,14 +127,14 @@ class XSSHtml(HTMLParser):
attdict = self.node_default(attdict)
attrs = []
for (key, value) in attdict.items():
for key, value in attdict.items():
attrs.append('%s="%s"' % (key, self._htmlspecialchars(value)))
attrs = (' ' + ' '.join(attrs)) if attrs else ''
self.result.append('<' + tag + attrs + end_diagonal + '>')
attrs = (" " + " ".join(attrs)) if attrs else ""
self.result.append("<" + tag + attrs + end_diagonal + ">")
def handle_endtag(self, tag):
if self.start and tag == self.start[len(self.start) - 1]:
self.result.append('</' + tag + '>')
self.result.append("</" + tag + ">")
self.start.pop()
def handle_data(self, data):
@@ -121,22 +156,23 @@ class XSSHtml(HTMLParser):
attrs = self._common_attr(attrs)
attrs = self._get_link(attrs, "href")
attrs = self._set_attr_default(attrs, "target", "_blank")
attrs = self._limit_attr(attrs, {
"target": ["_blank", "_self"]
})
attrs = self._limit_attr(attrs, {"target": ["_blank", "_self"]})
return attrs
def node_embed(self, attrs):
attrs = self._common_attr(attrs)
attrs = self._get_link(attrs, "src")
attrs = self._limit_attr(attrs, {
"type": ["application/x-shockwave-flash"],
"wmode": ["transparent", "window", "opaque"],
"play": ["true", "false"],
"loop": ["true", "false"],
"menu": ["true", "false"],
"allowfullscreen": ["true", "false"]
})
attrs = self._limit_attr(
attrs,
{
"type": ["application/x-shockwave-flash"],
"wmode": ["transparent", "window", "opaque"],
"play": ["true", "false"],
"loop": ["true", "false"],
"menu": ["true", "false"],
"allowfullscreen": ["true", "false"],
},
)
attrs["allowscriptaccess"] = "never"
attrs["allownetworking"] = "none"
return attrs
@@ -179,22 +215,19 @@ class XSSHtml(HTMLParser):
attrs = self._get_style(attrs)
return attrs
def _set_attr_default(self, attrs, name, default=''):
def _set_attr_default(self, attrs, name, default=""):
if name not in attrs:
attrs[name] = default
return attrs
def _limit_attr(self, attrs, limit={}):
for (key, value) in limit.items():
for key, value in limit.items():
if key in attrs and attrs[key] not in value:
del attrs[key]
return attrs
def _htmlspecialchars(self, html):
return html.replace("<", "&lt;") \
.replace(">", "&gt;") \
.replace('"', "&quot;") \
.replace("'", "&#039;")
return html.replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;").replace("'", "&#039;")
if "__main__" == __name__: