style: ruff format 全仓库
行宽 180 下把历史遗留的折行表达式合并,无语义改动。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -5,6 +5,7 @@ class TokenBucket:
|
||||
"""
|
||||
注意:对于单个key的操作不是线程安全的
|
||||
"""
|
||||
|
||||
def __init__(self, key, capacity, fill_rate, default_capacity, redis_conn):
|
||||
"""
|
||||
:param capacity: 最大容量
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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)}")
|
||||
|
||||
@@ -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("<", "<") \
|
||||
.replace(">", ">") \
|
||||
.replace('"', """) \
|
||||
.replace("'", "'")
|
||||
return html.replace("<", "<").replace(">", ">").replace('"', """).replace("'", "'")
|
||||
|
||||
|
||||
if "__main__" == __name__:
|
||||
|
||||
Reference in New Issue
Block a user