ranklist相关的改动
This commit is contained in:
25
account/migrations/0003_userprofile_total_score.py
Normal file
25
account/migrations/0003_userprofile_total_score.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.6 on 2017-08-20 02:03
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0002_auto_20170209_1028'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userprofile',
|
||||||
|
name='total_score',
|
||||||
|
field=models.BigIntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='userprofile',
|
||||||
|
old_name='accepted_problem_number',
|
||||||
|
new_name='accepted_number',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -69,28 +69,38 @@ def _random_avatar():
|
|||||||
|
|
||||||
class UserProfile(models.Model):
|
class UserProfile(models.Model):
|
||||||
user = models.OneToOneField(User)
|
user = models.OneToOneField(User)
|
||||||
# Store user problem solution status with json string format
|
# Store user problem solution status with json string format, Only for problems not contest_problems
|
||||||
# {"problems": {1: JudgeStatus.ACCEPTED}, "contest_problems": {20: JudgeStatus.PENDING)}
|
# ACM: {1: {status: JudgeStatus.ACCEPTED}}
|
||||||
|
# OI: {1: {score: 33}}
|
||||||
problems_status = JSONField(default={})
|
problems_status = JSONField(default={})
|
||||||
avatar = models.CharField(max_length=50, default=_random_avatar)
|
avatar = models.CharField(max_length=50, default=_random_avatar)
|
||||||
blog = models.URLField(blank=True, null=True)
|
blog = models.URLField(blank=True, null=True)
|
||||||
mood = models.CharField(max_length=200, blank=True, null=True)
|
mood = models.CharField(max_length=200, blank=True, null=True)
|
||||||
accepted_problem_number = models.IntegerField(default=0)
|
|
||||||
submission_number = models.IntegerField(default=0)
|
|
||||||
phone_number = models.CharField(max_length=15, blank=True, null=True)
|
phone_number = models.CharField(max_length=15, blank=True, null=True)
|
||||||
school = models.CharField(max_length=200, blank=True, null=True)
|
school = models.CharField(max_length=200, blank=True, null=True)
|
||||||
major = models.CharField(max_length=200, blank=True, null=True)
|
major = models.CharField(max_length=200, blank=True, null=True)
|
||||||
student_id = models.CharField(max_length=15, blank=True, null=True)
|
student_id = models.CharField(max_length=15, blank=True, null=True)
|
||||||
time_zone = models.CharField(max_length=32, blank=True, null=True)
|
time_zone = models.CharField(max_length=32, blank=True, null=True)
|
||||||
language = models.CharField(max_length=32, blank=True, null=True)
|
language = models.CharField(max_length=32, blank=True, null=True)
|
||||||
|
# for ACM
|
||||||
|
accepted_number = models.IntegerField(default=0)
|
||||||
|
# for OI
|
||||||
|
total_score = models.BigIntegerField(default=0)
|
||||||
|
submission_number = models.IntegerField(default=0)
|
||||||
|
|
||||||
def add_accepted_problem_number(self):
|
def add_accepted_problem_number(self):
|
||||||
self.accepted_problem_number = models.F("accepted_problem_number") + 1
|
self.accepted_number = models.F("accepted_number") + 1
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def add_submission_number(self):
|
def add_submission_number(self):
|
||||||
self.submission_number = models.F("submission_number") + 1
|
self.submission_number = models.F("submission_number") + 1
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
# 计算总分时, 应先减掉上次该题所得分数, 然后再加上本次所得分数
|
||||||
|
def add_score(self, this_time_score, last_time_score=None):
|
||||||
|
last_time_score = last_time_score or 0
|
||||||
|
self.total_score = models.F("total_score") - last_time_score + this_time_score
|
||||||
|
self.save()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "user_profile"
|
db_table = "user_profile"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from utils.api import DateTimeTZField, serializers
|
from utils.api import DateTimeTZField, serializers, UsernameSerializer
|
||||||
|
|
||||||
from .models import AdminType, ProblemPermission, User, UserProfile
|
from .models import AdminType, ProblemPermission, User, UserProfile
|
||||||
|
|
||||||
@@ -97,3 +97,10 @@ class TwoFactorAuthCodeSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
class AvatarUploadForm(forms.Form):
|
class AvatarUploadForm(forms.Form):
|
||||||
file = forms.FileField()
|
file = forms.FileField()
|
||||||
|
|
||||||
|
|
||||||
|
class RankInfoSerializer(serializers.ModelSerializer):
|
||||||
|
user = UsernameSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = UserProfile
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ from django.conf.urls import url
|
|||||||
from ..views.oj import (ApplyResetPasswordAPI, ResetPasswordAPI,
|
from ..views.oj import (ApplyResetPasswordAPI, ResetPasswordAPI,
|
||||||
UserChangePasswordAPI, UserRegisterAPI,
|
UserChangePasswordAPI, UserRegisterAPI,
|
||||||
UserLoginAPI, UserLogoutAPI, UsernameOrEmailCheck,
|
UserLoginAPI, UserLogoutAPI, UsernameOrEmailCheck,
|
||||||
SSOAPI, AvatarUploadAPI, TwoFactorAuthAPI, UserProfileAPI)
|
SSOAPI, AvatarUploadAPI, TwoFactorAuthAPI, UserProfileAPI,
|
||||||
|
UserRankAPI)
|
||||||
|
|
||||||
from utils.captcha.views import CaptchaAPIView
|
from utils.captcha.views import CaptchaAPIView
|
||||||
|
|
||||||
@@ -19,5 +20,6 @@ urlpatterns = [
|
|||||||
url(r"^profile/?$", UserProfileAPI.as_view(), name="user_profile_api"),
|
url(r"^profile/?$", UserProfileAPI.as_view(), name="user_profile_api"),
|
||||||
url(r"^avatar/upload/?$", AvatarUploadAPI.as_view(), name="avatar_upload_api"),
|
url(r"^avatar/upload/?$", AvatarUploadAPI.as_view(), name="avatar_upload_api"),
|
||||||
url(r"^sso/?$", SSOAPI.as_view(), name="sso_api"),
|
url(r"^sso/?$", SSOAPI.as_view(), name="sso_api"),
|
||||||
url(r"^two_factor_auth/?$", TwoFactorAuthAPI.as_view(), name="two_factor_auth_api")
|
url(r"^two_factor_auth/?$", TwoFactorAuthAPI.as_view(), name="two_factor_auth_api"),
|
||||||
|
url(r"^user_rank/?$", UserRankAPI.as_view(), name="user_rank_api"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ from utils.shortcuts import rand_str
|
|||||||
|
|
||||||
from ..decorators import login_required
|
from ..decorators import login_required
|
||||||
from ..models import User, UserProfile
|
from ..models import User, UserProfile
|
||||||
from ..serializers import (ApplyResetPasswordSerializer,
|
from ..serializers import (ApplyResetPasswordSerializer, ResetPasswordSerializer,
|
||||||
ResetPasswordSerializer,
|
|
||||||
UserChangePasswordSerializer, UserLoginSerializer,
|
UserChangePasswordSerializer, UserLoginSerializer,
|
||||||
UserRegisterSerializer, UsernameOrEmailCheckSerializer)
|
UserRegisterSerializer, UsernameOrEmailCheckSerializer,
|
||||||
|
RankInfoSerializer)
|
||||||
from ..serializers import (SSOSerializer, TwoFactorAuthCodeSerializer,
|
from ..serializers import (SSOSerializer, TwoFactorAuthCodeSerializer,
|
||||||
UserProfileSerializer,
|
UserProfileSerializer,
|
||||||
EditUserProfileSerializer, AvatarUploadForm)
|
EditUserProfileSerializer, AvatarUploadForm)
|
||||||
@@ -32,6 +32,7 @@ class UserProfileAPI(APIView):
|
|||||||
"""
|
"""
|
||||||
判断是否登录, 若登录返回用户信息
|
判断是否登录, 若登录返回用户信息
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@method_decorator(ensure_csrf_cookie)
|
@method_decorator(ensure_csrf_cookie)
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
user = request.user
|
user = request.user
|
||||||
@@ -321,3 +322,16 @@ class ResetPasswordAPI(APIView):
|
|||||||
user.set_password(data["password"])
|
user.set_password(data["password"])
|
||||||
user.save()
|
user.save()
|
||||||
return self.success("Succeeded")
|
return self.success("Succeeded")
|
||||||
|
|
||||||
|
|
||||||
|
class UserRankAPI(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
rule_type = request.GET.get("rule")
|
||||||
|
if rule_type not in ["acm", "oi"]:
|
||||||
|
rule_type = "acm"
|
||||||
|
profiles = UserProfile.objects.select_related("user").filter(submission_number__gt=0)
|
||||||
|
if rule_type == "acm":
|
||||||
|
profiles = profiles.order_by("-accepted_number", "submission_number")
|
||||||
|
else:
|
||||||
|
profiles = profiles.order_by("-total_score")
|
||||||
|
return self.success(self.paginate_data(request, profiles, RankInfoSerializer))
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class JudgeDispatcher(object):
|
|||||||
.get(_id=problem_id, contest_id=self.submission.contest_id)
|
.get(_id=problem_id, contest_id=self.submission.contest_id)
|
||||||
self.contest = self.problem.contest
|
self.contest = self.problem.contest
|
||||||
else:
|
else:
|
||||||
self.problem = Problem.objects.get(pk=problem_id)
|
self.problem = Problem.objects.get(_id=problem_id)
|
||||||
|
|
||||||
def _request(self, url, data=None):
|
def _request(self, url, data=None):
|
||||||
kwargs = {"headers": {"X-Judge-Server-Token": self.token,
|
kwargs = {"headers": {"X-Judge-Server-Token": self.token,
|
||||||
@@ -75,7 +75,7 @@ class JudgeDispatcher(object):
|
|||||||
def judge(self, output=False):
|
def judge(self, output=False):
|
||||||
server = self.choose_judge_server()
|
server = self.choose_judge_server()
|
||||||
if not server:
|
if not server:
|
||||||
data = {"submission_id": self.submission.id, "problem_id": self.problem.id}
|
data = {"submission_id": self.submission.id, "problem_id": self.problem._id}
|
||||||
self.redis_conn.lpush(CacheKey.waiting_queue, json.dumps(data))
|
self.redis_conn.lpush(CacheKey.waiting_queue, json.dumps(data))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -111,6 +111,7 @@ class JudgeDispatcher(object):
|
|||||||
# 用时和内存占用保存为多个测试点中最长的那个
|
# 用时和内存占用保存为多个测试点中最长的那个
|
||||||
self.submission.statistic_info["time_cost"] = max([x["cpu_time"] for x in resp["data"]])
|
self.submission.statistic_info["time_cost"] = max([x["cpu_time"] for x in resp["data"]])
|
||||||
self.submission.statistic_info["memory_cost"] = max([x["memory"] for x in resp["data"]])
|
self.submission.statistic_info["memory_cost"] = max([x["memory"] for x in resp["data"]])
|
||||||
|
# todo OI statistic_info["score"]
|
||||||
|
|
||||||
error_test_case = list(filter(lambda case: case["result"] != 0, resp["data"]))
|
error_test_case = list(filter(lambda case: case["result"] != 0, resp["data"]))
|
||||||
# 多个测试点全部正确则AC,否则 ACM模式下取第一个错误的测试点的状态, OI模式若全部错误则取第一个错误测试点状态,否则为部分正确
|
# 多个测试点全部正确则AC,否则 ACM模式下取第一个错误的测试点的状态, OI模式若全部错误则取第一个错误测试点状态,否则为部分正确
|
||||||
@@ -144,9 +145,9 @@ class JudgeDispatcher(object):
|
|||||||
self.problem.add_ac_number()
|
self.problem.add_ac_number()
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
if self.submission.contest_id:
|
if self.submission.contest_id:
|
||||||
problem = ContestProblem.objects.select_for_update().get(_id=self.problem.id, contest_id=self.contest.id)
|
problem = ContestProblem.objects.select_for_update().get(_id=self.problem._id, contest_id=self.contest.id)
|
||||||
else:
|
else:
|
||||||
problem = Problem.objects.select_related().get(_id=self.problem.id)
|
problem = Problem.objects.select_related().get(_id=self.problem._id)
|
||||||
info = problem.statistic_info
|
info = problem.statistic_info
|
||||||
result = str(self.submission.result)
|
result = str(self.submission.result)
|
||||||
info[result] = info.get(result, 0) + 1
|
info[result] = info.get(result, 0) + 1
|
||||||
@@ -155,21 +156,35 @@ class JudgeDispatcher(object):
|
|||||||
|
|
||||||
def update_user_profile(self):
|
def update_user_profile(self):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
# 更新user profile
|
|
||||||
user = User.objects.select_for_update().get(id=self.submission.user_id)
|
user = User.objects.select_for_update().get(id=self.submission.user_id)
|
||||||
user_profile = user.userprofile
|
user_profile = user.userprofile
|
||||||
user_profile.add_submission_number()
|
user_profile.add_submission_number()
|
||||||
problems_status = user_profile.problems_status
|
problems_status = user_profile.problems_status
|
||||||
if "problems" not in problems_status:
|
|
||||||
problems_status["problems"] = {}
|
|
||||||
|
|
||||||
# 之前状态不是ac, 现在是ac了 需要更新用户ac题目数量计数器,这里需要判重
|
problem_id = str(self.problem._id)
|
||||||
if problems_status["problems"].get(str(self.problem.id), JudgeStatus.WRONG_ANSWER) != JudgeStatus.ACCEPTED:
|
if self.problem.rule_type == ProblemRuleType.ACM:
|
||||||
if self.submission.result == JudgeStatus.ACCEPTED:
|
if problem_id not in problems_status:
|
||||||
user_profile.add_accepted_problem_number()
|
problems_status[problem_id] = {"status": self.submission.result}
|
||||||
problems_status["problems"][str(self.problem.id)] = JudgeStatus.ACCEPTED
|
if self.submission.result == JudgeStatus.ACCEPTED:
|
||||||
|
user_profile.add_accepted_problem_number()
|
||||||
|
# 以前提交过, ac了直接略过
|
||||||
|
elif problems_status[problem_id]["status"] != JudgeStatus.ACCEPTED:
|
||||||
|
if self.submission.result == JudgeStatus.ACCEPTED:
|
||||||
|
user_profile.add_accepted_problem_number()
|
||||||
|
problems_status[problem_id]["status"] = JudgeStatus.ACCEPTED
|
||||||
|
else:
|
||||||
|
problems_status[problem_id]["status"] = self.submission.result
|
||||||
|
|
||||||
|
else:
|
||||||
|
score = self.submission.statistic_info["score"]
|
||||||
|
if problem_id not in problems_status:
|
||||||
|
user_profile.add_score(score)
|
||||||
|
problems_status[problem_id] = {"score": score}
|
||||||
else:
|
else:
|
||||||
problems_status["problems"][str(self.problem.id)] = JudgeStatus.WRONG_ANSWER
|
# 加上本次 减掉上次的score
|
||||||
|
user_profile.add_score(score, problems_status[problem_id]["score"])
|
||||||
|
problems_status[problem_id] = {"score": score}
|
||||||
|
|
||||||
user_profile.problems_status = problems_status
|
user_profile.problems_status = problems_status
|
||||||
user_profile.save(update_fields=["problems_status"])
|
user_profile.save(update_fields=["problems_status"])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user