diff --git a/account/migrations/0003_userprofile_total_score.py b/account/migrations/0003_userprofile_total_score.py new file mode 100644 index 0000000..f836b91 --- /dev/null +++ b/account/migrations/0003_userprofile_total_score.py @@ -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', + ), + ] diff --git a/account/models.py b/account/models.py index 3563dd7..cb71bfc 100644 --- a/account/models.py +++ b/account/models.py @@ -69,28 +69,38 @@ def _random_avatar(): class UserProfile(models.Model): user = models.OneToOneField(User) - # Store user problem solution status with json string format - # {"problems": {1: JudgeStatus.ACCEPTED}, "contest_problems": {20: JudgeStatus.PENDING)} + # Store user problem solution status with json string format, Only for problems not contest_problems + # ACM: {1: {status: JudgeStatus.ACCEPTED}} + # OI: {1: {score: 33}} problems_status = JSONField(default={}) avatar = models.CharField(max_length=50, default=_random_avatar) blog = models.URLField(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) school = 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) time_zone = 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): - self.accepted_problem_number = models.F("accepted_problem_number") + 1 + self.accepted_number = models.F("accepted_number") + 1 self.save() def add_submission_number(self): self.submission_number = models.F("submission_number") + 1 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: db_table = "user_profile" diff --git a/account/serializers.py b/account/serializers.py index 69e312a..9b10fc3 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -1,6 +1,6 @@ from django import forms -from utils.api import DateTimeTZField, serializers +from utils.api import DateTimeTZField, serializers, UsernameSerializer from .models import AdminType, ProblemPermission, User, UserProfile @@ -97,3 +97,10 @@ class TwoFactorAuthCodeSerializer(serializers.Serializer): class AvatarUploadForm(forms.Form): file = forms.FileField() + + +class RankInfoSerializer(serializers.ModelSerializer): + user = UsernameSerializer() + + class Meta: + model = UserProfile diff --git a/account/urls/oj.py b/account/urls/oj.py index 1d65729..e31a81e 100644 --- a/account/urls/oj.py +++ b/account/urls/oj.py @@ -3,7 +3,8 @@ from django.conf.urls import url from ..views.oj import (ApplyResetPasswordAPI, ResetPasswordAPI, UserChangePasswordAPI, UserRegisterAPI, UserLoginAPI, UserLogoutAPI, UsernameOrEmailCheck, - SSOAPI, AvatarUploadAPI, TwoFactorAuthAPI, UserProfileAPI) + SSOAPI, AvatarUploadAPI, TwoFactorAuthAPI, UserProfileAPI, + UserRankAPI) from utils.captcha.views import CaptchaAPIView @@ -19,5 +20,6 @@ urlpatterns = [ url(r"^profile/?$", UserProfileAPI.as_view(), name="user_profile_api"), url(r"^avatar/upload/?$", AvatarUploadAPI.as_view(), name="avatar_upload_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"), ] diff --git a/account/views/oj.py b/account/views/oj.py index 351224c..1107a7d 100644 --- a/account/views/oj.py +++ b/account/views/oj.py @@ -18,10 +18,10 @@ from utils.shortcuts import rand_str from ..decorators import login_required from ..models import User, UserProfile -from ..serializers import (ApplyResetPasswordSerializer, - ResetPasswordSerializer, +from ..serializers import (ApplyResetPasswordSerializer, ResetPasswordSerializer, UserChangePasswordSerializer, UserLoginSerializer, - UserRegisterSerializer, UsernameOrEmailCheckSerializer) + UserRegisterSerializer, UsernameOrEmailCheckSerializer, + RankInfoSerializer) from ..serializers import (SSOSerializer, TwoFactorAuthCodeSerializer, UserProfileSerializer, EditUserProfileSerializer, AvatarUploadForm) @@ -32,6 +32,7 @@ class UserProfileAPI(APIView): """ 判断是否登录, 若登录返回用户信息 """ + @method_decorator(ensure_csrf_cookie) def get(self, request, **kwargs): user = request.user @@ -321,3 +322,16 @@ class ResetPasswordAPI(APIView): user.set_password(data["password"]) user.save() 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)) diff --git a/judge/dispatcher.py b/judge/dispatcher.py index 1bcdba2..9b4339d 100644 --- a/judge/dispatcher.py +++ b/judge/dispatcher.py @@ -40,7 +40,7 @@ class JudgeDispatcher(object): .get(_id=problem_id, contest_id=self.submission.contest_id) self.contest = self.problem.contest else: - self.problem = Problem.objects.get(pk=problem_id) + self.problem = Problem.objects.get(_id=problem_id) def _request(self, url, data=None): kwargs = {"headers": {"X-Judge-Server-Token": self.token, @@ -75,7 +75,7 @@ class JudgeDispatcher(object): def judge(self, output=False): server = self.choose_judge_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)) 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["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"])) # 多个测试点全部正确则AC,否则 ACM模式下取第一个错误的测试点的状态, OI模式若全部错误则取第一个错误测试点状态,否则为部分正确 @@ -144,9 +145,9 @@ class JudgeDispatcher(object): self.problem.add_ac_number() with transaction.atomic(): 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: - problem = Problem.objects.select_related().get(_id=self.problem.id) + problem = Problem.objects.select_related().get(_id=self.problem._id) info = problem.statistic_info result = str(self.submission.result) info[result] = info.get(result, 0) + 1 @@ -155,21 +156,35 @@ class JudgeDispatcher(object): def update_user_profile(self): with transaction.atomic(): - # 更新user profile user = User.objects.select_for_update().get(id=self.submission.user_id) user_profile = user.userprofile user_profile.add_submission_number() problems_status = user_profile.problems_status - if "problems" not in problems_status: - problems_status["problems"] = {} - # 之前状态不是ac, 现在是ac了 需要更新用户ac题目数量计数器,这里需要判重 - if problems_status["problems"].get(str(self.problem.id), JudgeStatus.WRONG_ANSWER) != JudgeStatus.ACCEPTED: - if self.submission.result == JudgeStatus.ACCEPTED: - user_profile.add_accepted_problem_number() - problems_status["problems"][str(self.problem.id)] = JudgeStatus.ACCEPTED + problem_id = str(self.problem._id) + if self.problem.rule_type == ProblemRuleType.ACM: + if problem_id not in problems_status: + problems_status[problem_id] = {"status": self.submission.result} + 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: - 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.save(update_fields=["problems_status"])