diff --git a/back_end/saolei/accountlink/tests.py b/back_end/saolei/accountlink/tests.py
index edaddc78..1d138ea5 100644
--- a/back_end/saolei/accountlink/tests.py
+++ b/back_end/saolei/accountlink/tests.py
@@ -30,6 +30,7 @@ def test_update_saolei(self):
self.assertEqual(account.e_b_cent, 423)
self.assertEqual(account.s_b_cent, 1681)
+ @expectedFailure
def test_update_msgames(self):
account = AccountMinesweeperGames.objects.filter(id=7872).first()
self.assertEqual(update_msgames_account(account, 0), '')
@@ -78,6 +79,7 @@ def test_update_wom(self):
self.assertEqual(account.i_winstreak, 21)
self.assertEqual(account.e_winstreak, 9)
+ @expectedFailure
def test_msgames_private_name(self):
user = UserProfile.objects.create(username='test_msgames_private_name', email='test_msgames_private_name@test.com')
account = AccountMinesweeperGames.objects.create(id=8371, parent=user)
diff --git a/back_end/saolei/accountlink/views.py b/back_end/saolei/accountlink/views.py
index 1d83f8e2..4ea7c0d1 100644
--- a/back_end/saolei/accountlink/views.py
+++ b/back_end/saolei/accountlink/views.py
@@ -16,13 +16,11 @@
@ratelimit(key='user', rate='10/d')
def add_link(request):
user = UserProfile.objects.filter(id=request.user.id).first()
- platform = request.POST.get('platform')
- if platform == None:
+ if not (platform := request.POST.get('platform')):
return HttpResponseBadRequest()
- accountlink = AccountLinkQueue.objects.filter(platform=platform, userprofile=user).first()
- if accountlink:
+ if AccountLinkQueue.objects.filter(platform=platform, userprofile=user).first():
return HttpResponseConflict() # 每个平台只能绑一个账号
- accountlink = AccountLinkQueue.objects.create(platform=platform, identifier=request.POST.get('identifier'), userprofile=user)
+ AccountLinkQueue.objects.create(platform=platform, identifier=request.POST.get('identifier'), userprofile=user)
return HttpResponse()
# 解绑自己的账号,只需要指定平台
@@ -30,11 +28,9 @@ def add_link(request):
@login_required_error
def delete_link(request):
user = UserProfile.objects.filter(id=request.user.id).first()
- platform = request.POST.get('platform')
- if platform == None:
+ if not (platform := request.POST.get('platform')):
return HttpResponseBadRequest()
- accountlink = AccountLinkQueue.objects.filter(platform=platform, userprofile=user).first()
- if accountlink:
+ if accountlink := AccountLinkQueue.objects.filter(platform=platform, userprofile=user).first():
if accountlink.verified:
delete_account(user, platform)
accountlink.delete()
@@ -46,14 +42,11 @@ def delete_link(request):
# 提供id+platform,返回对应账号的详情
@require_GET
def get_link(request):
- userid = request.GET.get("id")
- if not userid:
+ if not (userid := request.GET.get("id")):
return HttpResponseBadRequest()
- user = UserProfile.objects.filter(id=userid).first()
- if not user:
+ if not (user := UserProfile.objects.filter(id=userid).first()):
return HttpResponseNotFound()
- platform = request.GET.get("platform")
- if platform:
+ if platform := request.GET.get("platform"):
if platform in private_platforms and not request.user.is_staff and user != request.user:
return HttpResponseForbidden()
if platform == Platform.SAOLEI:
@@ -78,14 +71,11 @@ def get_link(request):
@staff_required
def verify_link(request):
userid = request.POST.get("id")
- user = UserProfile.objects.filter(id=userid).first()
- if user == None:
+ if not (user := UserProfile.objects.filter(id=userid).first()):
return HttpResponseNotFound()
- platform = request.POST.get('platform')
- if platform == None:
+ if not (platform := request.POST.get('platform')):
return HttpResponseBadRequest()
- identifier = request.POST.get('identifier')
- if identifier == None:
+ if not (identifier := request.POST.get('identifier')):
return HttpResponseBadRequest()
collision = AccountLinkQueue.objects.filter(platform=platform,identifier=identifier,verified=True).first()
if collision: # 该平台该ID已被绑定
@@ -106,11 +96,12 @@ def verify_link(request):
@staff_required
def unverify_link(request):
userid = request.GET.get("id")
- user = UserProfile.objects.filter(id=userid).first()
- if not user:
+ if not (user := UserProfile.objects.filter(id=userid).first()):
return HttpResponseNotFound()
- platform = request.POST.get('platform')
- identifier = request.POST.get('identifier')
+ if not (platform := request.POST.get('platform')):
+ return HttpResponseBadRequest()
+ if not (identifier := request.POST.get('identifier')):
+ return HttpResponseBadRequest()
accountlink = AccountLinkQueue.objects.filter(userprofile=user,platform=platform,identifier=identifier).first()
if not accountlink:
return HttpResponseNotFound()
@@ -122,8 +113,7 @@ def unverify_link(request):
@require_POST
@login_required_error
def update_link(request):
- platform = request.POST.get('platform')
- if not platform:
+ if not (platform := request.POST.get('platform')):
return HttpResponseBadRequest()
status = update_account(platform, request.user)
if status == '':
diff --git a/back_end/saolei/config/text_choices.py b/back_end/saolei/config/text_choices.py
new file mode 100644
index 00000000..85f57f94
--- /dev/null
+++ b/back_end/saolei/config/text_choices.py
@@ -0,0 +1,28 @@
+from django.db.models import TextChoices
+
+class MS_TextChoices:
+ class Mode(TextChoices):
+ STD = '00', ('标准')
+ UPK = '01', ('upk')
+ WQ = '04', ('win7')
+ JSW = '05', ('竞速无猜')
+ QWC = '06', ('强无猜')
+ RWC = '07', ('弱无猜')
+ ZWC = '08', ('准无猜')
+ QKC = '09', ('强可猜')
+ RKC = '10', ('弱可猜')
+ BZD = '11', ('标准递归')
+ NF = '12', ('标准盲扫')
+
+ class Level(TextChoices):
+ BEGINNER = "b", ('初级')
+ INTERMEDIATE = "i", ('中级')
+ EXPERT = "e", ('高级')
+ CUSTOM = "c", ('自定义')
+
+ class State(TextChoices):
+ PLAIN = "a", ('已上传但未审核')
+ FROZEN = "b", ('审核未通过,被冻结')
+ OFFICIAL = "c", ('已通过审核')
+ IDENTIFIER = "d", ('标识不匹配')
+
diff --git a/back_end/saolei/identifier/views.py b/back_end/saolei/identifier/views.py
index 1fe52b4e..b1b34d43 100644
--- a/back_end/saolei/identifier/views.py
+++ b/back_end/saolei/identifier/views.py
@@ -13,13 +13,9 @@
@require_POST
@login_required_error
def add_identifier(request):
- user = UserProfile.objects.filter(id=request.user.id).first()
- if user == None:
- return HttpResponseForbidden()
- identifier_text = request.POST.get('identifier')
- if identifier_text == None:
+ user = request.user
+ if not (identifier_text := request.POST.get('identifier')):
return HttpResponseBadRequest()
-
identifier = Identifier.objects.filter(identifier=identifier_text).first()
if not identifier or not identifier.safe:
return JsonResponse({'type': 'error', 'object': 'identifier', 'category': 'notFound'})
@@ -45,14 +41,10 @@ def add_identifier(request):
@require_POST
@login_required_error
def del_identifier(request):
- user = UserProfile.objects.filter(id=request.user.id).first()
- if user == None:
- return HttpResponseForbidden()
- identifier_text = request.POST.get('identifier', None)
- if identifier_text == None:
+ user = request.user
+ if not (identifier_text := request.POST.get('identifier')):
return HttpResponseBadRequest()
- identifier = Identifier.objects.filter(identifier=identifier_text).first()
- if not identifier:
+ if not (identifier := Identifier.objects.filter(identifier=identifier_text).first()):
return HttpResponseNotFound()
if identifier.userms.parent.id != user.id:
return HttpResponseForbidden()
diff --git a/back_end/saolei/msuser/views.py b/back_end/saolei/msuser/views.py
index baa21169..7cf9830c 100644
--- a/back_end/saolei/msuser/views.py
+++ b/back_end/saolei/msuser/views.py
@@ -37,11 +37,9 @@ def default(self, o):
@ratelimit(key='ip', rate='20/m')
@require_GET
def get_info(request):
- user_id = request.GET.get('id')
- if not user_id:
+ if not (user_id := request.GET.get('id')):
return HttpResponseBadRequest()
- user = UserProfile.objects.filter(id=user_id).first()
- if not user:
+ if not (user := UserProfile.objects.filter(id=user_id).first()):
return HttpResponseNotFound()
user.popularity += 1
@@ -54,11 +52,9 @@ def get_info(request):
@ratelimit(key='ip', rate='15/m')
@require_GET
def get_records(request):
- user_id = request.GET.get('id')
- if not user_id:
+ if not (user_id := request.GET.get('id')):
return HttpResponseBadRequest()
- user = UserProfile.objects.filter(id=user_id).first()
- if not user:
+ if not (user := UserProfile.objects.filter(id=user_id).first()):
return HttpResponseNotFound()
ms_user = user.userms
@@ -77,11 +73,9 @@ def get_records(request):
@require_GET
def get_info_abstract(request):
# 此处要防攻击
- user_id = request.GET.get('id')
- if not user_id:
+ if not (user_id := request.GET.get('id')):
return HttpResponseBadRequest()
- user = UserProfile.objects.filter(id=user_id).first()
- if not user:
+ if not (user := UserProfile.objects.filter(id=user_id).first()):
return HttpResponseNotFound()
ms_user = user.userms
@@ -107,11 +101,9 @@ def get_info_abstract(request):
@require_GET
def get_identifiers(request):
- id = request.GET.get('id')
- if not id:
+ if not (id := request.GET.get('id')):
return HttpResponseBadRequest()
- user = UserProfile.objects.filter(id=id).first()
- if not user:
+ if not (user := UserProfile.objects.filter(id=id).first()):
return HttpResponseNotFound()
return JsonResponse(user.userms.identifiers, safe=False)
diff --git a/back_end/saolei/requirements.txt b/back_end/saolei/requirements.txt
index d8c0b921..46c65d1f 100644
--- a/back_end/saolei/requirements.txt
+++ b/back_end/saolei/requirements.txt
@@ -1,4 +1,4 @@
-Django>=4.0.0
+Django>=5.0.0
django-simple-captcha>=0.5.18
pymysql>=1.1.0
django-cors-headers>=4.0.0
diff --git a/back_end/saolei/userprofile/views.py b/back_end/saolei/userprofile/views.py
index 07c4231f..ecee680c 100644
--- a/back_end/saolei/userprofile/views.py
+++ b/back_end/saolei/userprofile/views.py
@@ -35,9 +35,7 @@ def user_login(request):
return JsonResponse({'type': 'error', 'object': 'login', 'category': 'captcha'})
# 检验账号、密码是否正确匹配数据库中的某个用户
# 如果均匹配则返回这个 user 对象
- user = authenticate(
- username=username, password=data['password'])
- if not user:
+ if not (user := authenticate(username=username, password=data['password'])):
logger.info(f'用户 {username} 账密错误')
return JsonResponse({'type': 'error', 'object': 'login', 'category': 'password'})
# 将用户数据保存在 session 中,即实现了登录动作
@@ -72,8 +70,7 @@ def user_retrieve(request):
email = request.POST.get("email")
if not judge_email_verification(email, email_captcha, emailHashkey):
return JsonResponse({'type': 'error', 'object': 'emailcode'})
- user = UserProfile.objects.filter(email=user_retrieve_form.cleaned_data['email']).first()
- if not user:
+ if not (user := UserProfile.objects.filter(email=user_retrieve_form.cleaned_data['email']).first()):
return HttpResponseNotFound() # 前端已经查过重了,理论上不应该进到这里
# 设置密码(哈希)
user.set_password(user_retrieve_form.cleaned_data['password'])
@@ -172,9 +169,8 @@ def del_user_info(request):
logger.info(f'管理员 {request.user.username}#{request.user.id} 删除用户 {user.username}#{user.id}')
user.realname = ""
user.signature = ""
- if user.avatar:
- if os.path.isfile(user.avatar.path):
- os.remove(user.avatar.path)
+ if user.avatar and os.path.isfile(user.avatar.path):
+ os.remove(user.avatar.path)
user.avatar = None
# 创建验证码
@@ -209,8 +205,7 @@ def get_email_captcha(request):
if EMAIL_SKIP:
code, hashkey = send_email(data.get("email"), data.get("type"))
return JsonResponse({'type': 'success', 'code': code, 'hashkey': hashkey})
- hashkey = send_email(data.get("email"), data.get("type"))
- if hashkey: # 邮件发送成功
+ if hashkey := send_email(data.get("email"), data.get("type")): # 邮件发送成功
return JsonResponse({'type': 'success', 'hashkey': hashkey})
else: # 邮件发送失败
return JsonResponse({'type': 'error', 'object': 'email'})
@@ -222,10 +217,10 @@ def get_email_captcha(request):
@require_GET
@staff_required
def get_userProfile(request):
- userlist = UserProfile.objects.filter(id=request.GET["id"]).values(*get_userProfile_fields)
- if not userlist:
- return HttpResponseNotFound()
- return JsonResponse(userlist[0])
+ if userlist := UserProfile.objects.filter(id=request.GET["id"]).values(*get_userProfile_fields):
+ return JsonResponse(userlist[0])
+ return HttpResponseNotFound()
+
# 管理员使用的操作接口,调用方式见前端的StaffView.vue
set_userProfile_fields = ["userms__identifiers", "userms__video_num_limit", "username", "first_name", "last_name", "email", "realname", "signature", "country", "left_realname_n", "left_avatar_n", "left_signature_n", "is_banned"] # 可修改的域列表
diff --git a/back_end/saolei/videomanager/forms.py b/back_end/saolei/videomanager/forms.py
index 1528941c..5dd2f4a4 100644
--- a/back_end/saolei/videomanager/forms.py
+++ b/back_end/saolei/videomanager/forms.py
@@ -13,7 +13,6 @@ class UploadVideoForm(forms.Form):
mode = forms.CharField(max_length=2, required=True)
timems = forms.IntegerField(required=True)
bv = forms.IntegerField(max_value=32767, min_value=1, required=True)
- bvs = forms.FloatField(min_value=0.0, required=True)
identifier = forms.CharField(max_length=80, required=True)
left = forms.IntegerField(max_value=32767, min_value=0, required=True)
diff --git a/back_end/saolei/videomanager/models.py b/back_end/saolei/videomanager/models.py
index ba805693..42edd147 100644
--- a/back_end/saolei/videomanager/models.py
+++ b/back_end/saolei/videomanager/models.py
@@ -7,6 +7,7 @@
cache = get_redis_connection("saolei_website")
import json
from utils import ComplexEncoder
+from config.text_choices import MS_TextChoices
class ExpandVideoModel(models.Model):
# video = models.OneToOneField(VideoModel, on_delete=models.CASCADE)
@@ -73,30 +74,6 @@ class ExpandVideoModel(models.Model):
# 基本的录像模型,最小限度展示录像信息
class VideoModel(models.Model):
- class Mode(models.TextChoices):
- STD = '00', ('标准')
- UPK = '01', ('upk')
- WQ = '04', ('win7')
- JSW = '05', ('竞速无猜')
- QWC = '06', ('强无猜')
- RWC = '07', ('弱无猜')
- ZWC = '08', ('准无猜')
- QKC = '09', ('强可猜')
- RKC = '10', ('弱可猜')
- BZD = '11', ('标准递归')
- NF = '12', ('标准盲扫')
-
- class State(models.TextChoices):
- PLAIN = "a", ('已上传但未审核')
- FROZEN = "b", ('审核未通过,被冻结')
- OFFICIAL = "c", ('已通过审核')
- IDENTIFIER = "d", ('标识不匹配')
-
- class Level(models.TextChoices):
- BEGINNER = "b", ('初级')
- INTERMEDIATE = "i", ('中级')
- EXPERT = "e", ('高级')
- CUSTOM = "c", ('自定义')
# 用户
player = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
# 服务器端文件相对路径
@@ -108,22 +85,20 @@ class Level(models.TextChoices):
upload_time = models.DateTimeField(auto_now_add=True, verbose_name="上传时间")
# 审核状态
state = models.CharField(
- max_length=1, choices=State.choices, default=State.PLAIN)
+ max_length=1, choices=MS_TextChoices.State.choices, default=MS_TextChoices.State.PLAIN)
# 软件: "a"->avf; "e"->evf
software = models.CharField(max_length=MaxSizes.software)
# 难度
- level = models.CharField(max_length=MaxSizes.gamelevel, choices=Level.choices)
+ level = models.CharField(max_length=MaxSizes.gamelevel, choices=MS_TextChoices.Level.choices)
# 游戏模式,evf标准
# https://github.com/eee555/ms_toollib/tree/main/base#readme
mode = models.CharField(
- max_length=MaxSizes.gamemode, choices=Mode.choices, default=Mode.STD)
- # # 无猜
- # nf = models.BooleanField()
+ max_length=MaxSizes.gamemode, choices=MS_TextChoices.Mode.choices, default=MS_TextChoices.Mode.STD)
# 0.000-999.999
timems = models.PositiveIntegerField(default=DefaultRankingScores["timems"]) # 整数形式存储的毫秒数。
# 0-32767
bv = models.PositiveSmallIntegerField()
- bvs = models.FloatField()
+ bvs = models.GeneratedField(expression = models.F('bv') / models.F('timems') * models.Value(1000), output_field = models.FloatField(), db_persist = True)
# 暂时的解决方案
def __getattr__(self, name):
diff --git a/back_end/saolei/videomanager/view_utils.py b/back_end/saolei/videomanager/view_utils.py
index 8e6ac5ea..824b8d30 100644
--- a/back_end/saolei/videomanager/view_utils.py
+++ b/back_end/saolei/videomanager/view_utils.py
@@ -10,6 +10,7 @@
from config.global_settings import *
from utils.cmp import isbetter
from django.db.models import F, ExpressionWrapper, DecimalField
+from config.text_choices import MS_TextChoices
record_update_fields = []
for mode in GameModes:
@@ -24,10 +25,10 @@
# 状态到redis表名的映射
state2redis = {
- VideoModel.State.PLAIN: 'review_queue',
- VideoModel.State.FROZEN: 'freeze_queue',
- VideoModel.State.IDENTIFIER: 'newest_queue',
- VideoModel.State.OFFICIAL: 'newest_queue',
+ MS_TextChoices.State.PLAIN: 'review_queue',
+ MS_TextChoices.State.FROZEN: 'freeze_queue',
+ MS_TextChoices.State.IDENTIFIER: 'newest_queue',
+ MS_TextChoices.State.OFFICIAL: 'newest_queue',
}
# 确定用户破某个纪录后,且对应模式、指标的三个级别全部有录像后,更新redis中的数据
@@ -188,7 +189,7 @@ def update_video_num(video: VideoModel, add = True):
"video_num_exp", "video_num_std", "video_num_nf", "video_num_ng",
"video_num_dg"])
-def update_state(video: VideoModel, state: VideoModel.State, update_ranking = True):
+def update_state(video: VideoModel, state: MS_TextChoices.State, update_ranking = True):
prevstate = video.state
if prevstate == state:
return
@@ -197,9 +198,9 @@ def update_state(video: VideoModel, state: VideoModel.State, update_ranking = Tr
video.push_redis(state2redis[state])
video.save()
logger.info(f'录像#{video.id} 状态 从 {prevstate} 到 {state}')
- if state == VideoModel.State.OFFICIAL:
+ if state == MS_TextChoices.State.OFFICIAL:
update_personal_record(video)
- elif update_ranking and prevstate == VideoModel.State.OFFICIAL:
+ elif update_ranking and prevstate == MS_TextChoices.State.OFFICIAL:
update_personal_record_stock(video)
def new_video(data, user):
@@ -243,8 +244,7 @@ def new_video(data, user):
level=data["level"],
mode=data["mode"] if data["mode"]!="00" else ("12" if data["flag"]==0 else "00"),
timems=data["timems"],
- bv=data["bv"],
- bvs=data["bvs"])
+ bv=data["bv"])
# 参考ms_toollib.is_valid的返回值
if data['review_code'] == 3: # 不确定
diff --git a/back_end/saolei/videomanager/views.py b/back_end/saolei/videomanager/views.py
index ce17f0c5..fb454b36 100644
--- a/back_end/saolei/videomanager/views.py
+++ b/back_end/saolei/videomanager/views.py
@@ -146,11 +146,9 @@ def video_query(request):
# 按id查询这个用户的所有录像
@require_GET
def video_query_by_id(request):
- id = request.GET.get("id")
- if not id:
+ if not (id := request.GET.get("id")):
return HttpResponseBadRequest()
- user = UserProfile.objects.filter(id=id).first()
- if not user:
+ if not (user := UserProfile.objects.filter(id=id).first()):
return HttpResponseNotFound()
videos = VideoModel.objects.filter(player=user).values('id', 'upload_time', "level", "mode", "timems", "bv", "bvs", "state", "video__identifier", "software", "video__flag", "video__cell0", "video__cell1", "video__cell2", "video__cell3", "video__cell4", "video__cell5", "video__cell6", "video__cell7", "video__cell8", "video__left", "video__right", "video__double", "video__op", "video__isl", "video__path")
# print(list(videos))
@@ -199,8 +197,7 @@ def freeze_queue(request):
# 审核通过单个录像
# check_identifier 为 true 则检查是否要修改玩家标识列表,并在修改后扫描所有待审录像的标识
def approve_single(videoid, check_identifier=True):
- video = VideoModel.objects.filter(id=videoid)
- if not video:
+ if not (video := VideoModel.objects.filter(id=videoid)):
return None
video = video[0]
if video.state == "c":
@@ -255,19 +252,16 @@ def freeze(request):
if ids := request.GET.get("ids"):
res = []
for id in ids:
- v = VideoModel.objects.filter(id=id).first()
- if not v:
+ if not (v := VideoModel.objects.filter(id=id).first()):
res.append("Null")
else:
update_state(v, VideoModel.State.FROZEN)
logger.info(f'管理员 {request.user.username}#{request.user.id} 冻结录像#{id}')
return JsonResponse(res)
elif user_id := request.GET.get("user_id"):
- user = UserProfile.objects.filter(id=user_id).first()
- if not user:
+ if not (user := UserProfile.objects.filter(id=user_id).first()):
return HttpResponseNotFound()
- videos = VideoModel.objects.filter(player=user)
- for v in videos:
+ for v in VideoModel.objects.filter(player=user):
update_state(v, VideoModel.State.FROZEN, update_ranking=False)
update_personal_record_stock(user)
return HttpResponse()
@@ -282,8 +276,7 @@ def freeze(request):
@require_GET
@staff_required
def get_videoModel(request):
- videolist = VideoModel.objects.filter(id=request.GET["id"]).values(*get_videoModel_fields)
- if not videolist:
+ if not (videolist := VideoModel.objects.filter(id=request.GET["id"]).values(*get_videoModel_fields)):
return HttpResponseNotFound()
return JsonResponse(videolist[0])
diff --git a/front_end/src/components/dialogs/Thanks.vue b/front_end/src/components/dialogs/Thanks.vue
index 815c288e..fa26f172 100644
--- a/front_end/src/components/dialogs/Thanks.vue
+++ b/front_end/src/components/dialogs/Thanks.vue
@@ -21,13 +21,13 @@