添加最近提交

This commit is contained in:
2025-04-13 16:48:40 +08:00
parent 8d6e9ff1f9
commit 47cf28eb67
2 changed files with 125 additions and 43 deletions

View File

@@ -1,11 +1,26 @@
from django.urls import path
from ..views.oj import (ApplyResetPasswordAPI, ResetPasswordAPI,
UserChangePasswordAPI, UserRegisterAPI, UserChangeEmailAPI,
UserLoginAPI, UserLogoutAPI, UsernameOrEmailCheck,
AvatarUploadAPI, TwoFactorAuthAPI, UserProfileAPI,
UserRankAPI, UserActivityRankAPI, CheckTFARequiredAPI, SessionManagementAPI,
ProfileProblemDisplayIDRefreshAPI, OpenAPIAppkeyAPI, SSOAPI)
from ..views.oj import (
ApplyResetPasswordAPI,
ResetPasswordAPI,
UserChangePasswordAPI,
UserProfileAnalyse,
UserRegisterAPI,
UserChangeEmailAPI,
UserLoginAPI,
UserLogoutAPI,
UsernameOrEmailCheck,
AvatarUploadAPI,
TwoFactorAuthAPI,
UserProfileAPI,
UserRankAPI,
UserActivityRankAPI,
CheckTFARequiredAPI,
SessionManagementAPI,
ProfileProblemDisplayIDRefreshAPI,
OpenAPIAppkeyAPI,
SSOAPI,
)
from utils.captcha.views import CaptchaAPIView
@@ -20,13 +35,20 @@ urlpatterns = [
path("captcha", CaptchaAPIView.as_view()),
path("check_username_or_email", UsernameOrEmailCheck.as_view()),
path("profile", UserProfileAPI.as_view(), name="user_profile_api"),
path("profile/analyse", UserProfileAnalyse.as_view()),
path("profile/fresh_display_id", ProfileProblemDisplayIDRefreshAPI.as_view()),
path("upload_avatar", AvatarUploadAPI.as_view()),
path("tfa_required", CheckTFARequiredAPI.as_view()),
path("two_factor_auth", TwoFactorAuthAPI.as_view(),),
path(
"two_factor_auth",
TwoFactorAuthAPI.as_view(),
),
path("user_rank", UserRankAPI.as_view()),
path("user_activity_rank", UserActivityRankAPI.as_view()),
path("sessions", SessionManagementAPI.as_view()),
path("open_api_appkey", OpenAPIAppkeyAPI.as_view(),),
path("sso", SSOAPI.as_view())
path(
"open_api_appkey",
OpenAPIAppkeyAPI.as_view(),
),
path("sso", SSOAPI.as_view()),
]

View File

@@ -2,7 +2,6 @@ import os
from datetime import timedelta
from importlib import import_module
import qrcode
from django.conf import settings
from django.contrib import auth
from django.template.loader import render_to_string
@@ -10,6 +9,9 @@ from django.utils.decorators import method_decorator
from django.utils.timezone import now
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt
from django.db.models import Count
from django.utils import timezone
import qrcode
from otpauth import OtpAuth
from problem.models import Problem
@@ -21,12 +23,23 @@ from utils.captcha import Captcha
from utils.shortcuts import rand_str, img2base64, datetime2str
from ..decorators import login_required
from ..models import User, UserProfile, AdminType
from ..serializers import (ApplyResetPasswordSerializer, ResetPasswordSerializer,
UserChangePasswordSerializer, UserLoginSerializer,
UserRegisterSerializer, UsernameOrEmailCheckSerializer,
RankInfoSerializer, UserChangeEmailSerializer, SSOSerializer)
from ..serializers import (TwoFactorAuthCodeSerializer, UserProfileSerializer,
EditUserProfileSerializer, ImageUploadForm)
from ..serializers import (
ApplyResetPasswordSerializer,
ResetPasswordSerializer,
UserChangePasswordSerializer,
UserLoginSerializer,
UserRegisterSerializer,
UsernameOrEmailCheckSerializer,
RankInfoSerializer,
UserChangeEmailSerializer,
SSOSerializer,
)
from ..serializers import (
TwoFactorAuthCodeSerializer,
UserProfileSerializer,
EditUserProfileSerializer,
ImageUploadForm,
)
from ..tasks import send_email_async
@@ -50,7 +63,9 @@ class UserProfileAPI(APIView):
show_real_name = True
except User.DoesNotExist:
return self.error("User does not exist")
return self.success(UserProfileSerializer(user.userprofile, show_real_name=show_real_name).data)
return self.success(
UserProfileSerializer(user.userprofile, show_real_name=show_real_name).data
)
@validate_serializer(EditUserProfileSerializer)
@login_required
@@ -60,7 +75,19 @@ class UserProfileAPI(APIView):
for k, v in data.items():
setattr(user_profile, k, v)
user_profile.save()
return self.success(UserProfileSerializer(user_profile, show_real_name=True).data)
return self.success(
UserProfileSerializer(user_profile, show_real_name=True).data
)
class UserProfileAnalyse(APIView):
def get(self, request):
userid = request.GET.get("userid")
submission = Submission.objects.filter(user_id=userid).latest("create_time")
if not submission:
return self.error("暂无提交")
else:
return self.success({"now": timezone.now(), "last": submission.create_time})
class AvatarUploadAPI(APIView):
@@ -104,7 +131,11 @@ class TwoFactorAuthAPI(APIView):
user.save()
label = f"{SysOptions.website_name_shortcut}:{user.username}"
image = qrcode.make(OtpAuth(token).to_uri("totp", label, SysOptions.website_name.replace(" ", "")))
image = qrcode.make(
OtpAuth(token).to_uri(
"totp", label, SysOptions.website_name.replace(" ", "")
)
)
return self.success(img2base64(image))
@login_required
@@ -197,12 +228,11 @@ class UsernameOrEmailCheck(APIView):
"""
data = request.data
# True means already exist.
result = {
"username": False,
"email": False
}
result = {"username": False, "email": False}
if data.get("username"):
result["username"] = User.objects.filter(username=data["username"].lower()).exists()
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)
@@ -239,7 +269,9 @@ class UserChangeEmailAPI(APIView):
@login_required
def post(self, request):
data = request.data
user = auth.authenticate(username=request.user.username, password=data["password"])
user = auth.authenticate(
username=request.user.username, password=data["password"]
)
if user:
if user.two_factor_auth:
if "tfa_code" not in data:
@@ -292,8 +324,12 @@ class ApplyResetPasswordAPI(APIView):
user = User.objects.get(email__iexact=data["email"])
except User.DoesNotExist:
return self.error("User does not exist")
if user.reset_password_token_expire_time and 0 < int(
(user.reset_password_token_expire_time - now()).total_seconds()) < 20 * 60:
if (
user.reset_password_token_expire_time
and 0
< int((user.reset_password_token_expire_time - now()).total_seconds())
< 20 * 60
):
return self.error("You can only reset password once per 20 minutes")
user.reset_password_token = rand_str()
user.reset_password_token_expire_time = now() + timedelta(minutes=20)
@@ -301,14 +337,16 @@ class ApplyResetPasswordAPI(APIView):
render_data = {
"username": user.username,
"website_name": SysOptions.website_name,
"link": f"{SysOptions.website_base_url}/reset-password/{user.reset_password_token}"
"link": f"{SysOptions.website_base_url}/reset-password/{user.reset_password_token}",
}
email_html = render_to_string("reset_password_email.html", render_data)
send_email_async.send(from_name=SysOptions.website_name_shortcut,
send_email_async.send(
from_name=SysOptions.website_name_shortcut,
to_email=user.email,
to_name=user.username,
subject="Reset your password",
content=email_html)
content=email_html,
)
return self.success("Succeeded")
@@ -385,10 +423,15 @@ class UserRankAPI(APIView):
n = 0
if rule_type not in ContestRuleType.choices():
rule_type = ContestRuleType.ACM
profiles = UserProfile.objects.filter(user__admin_type=AdminType.REGULAR_USER, user__is_disabled=False,
user__username__icontains=username).select_related("user")
profiles = UserProfile.objects.filter(
user__admin_type=AdminType.REGULAR_USER,
user__is_disabled=False,
user__username__icontains=username,
).select_related("user")
if rule_type == ContestRuleType.ACM:
profiles = profiles.filter(accepted_number__gte=0).order_by("-accepted_number", "submission_number")
profiles = profiles.filter(accepted_number__gte=0).order_by(
"-accepted_number", "submission_number"
)
else:
profiles = profiles.filter(total_score__gt=0).order_by("-total_score")
if n > 0:
@@ -401,11 +444,20 @@ class UserActivityRankAPI(APIView):
start = request.GET.get("start")
if not start:
return self.error("start time is required")
admin_usernames = User.objects.filter(is_disabled=False).exclude(
admin_type=AdminType.REGULAR_USER).values_list("username", flat=True)
admin_usernames = (
User.objects.filter(is_disabled=False)
.exclude(admin_type=AdminType.REGULAR_USER)
.values_list("username", flat=True)
)
admin_len = len(admin_usernames)
submissions = Submission.objects.filter(contest_id__isnull=True, create_time__gte=start, result=JudgeStatus.ACCEPTED)
counts = submissions.values("username").annotate(count=Count("problem_id", distinct=True)).order_by("-count")[:10+admin_len]
submissions = Submission.objects.filter(
contest_id__isnull=True, create_time__gte=start, result=JudgeStatus.ACCEPTED
)
counts = (
submissions.values("username")
.annotate(count=Count("problem_id", distinct=True))
.order_by("-count")[: 10 + admin_len]
)
data = []
for count in counts:
if count["username"] not in admin_usernames:
@@ -422,7 +474,9 @@ class ProfileProblemDisplayIDRefreshAPI(APIView):
ids = list(acm_problems.keys()) + list(oi_problems.keys())
if not ids:
return self.success()
display_ids = Problem.objects.filter(id__in=ids, visible=True).values_list("_id", flat=True)
display_ids = Problem.objects.filter(id__in=ids, visible=True).values_list(
"_id", flat=True
)
id_map = dict(zip(ids, display_ids))
for k, v in acm_problems.items():
v["_id"] = id_map[k]
@@ -459,4 +513,10 @@ class SSOAPI(CSRFExemptAPIView):
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})
return self.success(
{
"username": user.username,
"avatar": user.userprofile.avatar,
"admin_type": user.admin_type,
}
)