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 @@
- +
- +
- +
diff --git a/front_end/src/components/widgets/IdentifierManager.vue b/front_end/src/components/widgets/IdentifierManager.vue index 91bb6070..293392d7 100644 --- a/front_end/src/components/widgets/IdentifierManager.vue +++ b/front_end/src/components/widgets/IdentifierManager.vue @@ -1,26 +1,35 @@