diff --git a/account/middleware.py b/account/middleware.py index b674346d..1ef78ccf 100644 --- a/account/middleware.py +++ b/account/middleware.py @@ -10,7 +10,7 @@ class SessionRecordMiddleware(MiddlewareMixin): if request.user.is_authenticated(): session = request.session session["user_agent"] = request.META.get("HTTP_USER_AGENT", "") - session["ip"] = request.META.get("HTTP_X_REAL_IP", "UNKNOWN IP") + session["ip"] = request.META.get("HTTP_X_REAL_IP", request.META.get("REMOTE_ADDR")) session["last_activity"] = now() user_sessions = request.user.session_keys if session.session_key not in user_sessions: diff --git a/conf/tests.py b/conf/tests.py index 5906b078..cd834446 100644 --- a/conf/tests.py +++ b/conf/tests.py @@ -4,7 +4,6 @@ from django.utils import timezone from options.options import SysOptions from utils.api.tests import APITestCase -from utils.constants import CacheKey from .models import JudgeServer diff --git a/contest/models.py b/contest/models.py index eb1c88a8..08a3babb 100644 --- a/contest/models.py +++ b/contest/models.py @@ -46,7 +46,7 @@ class Contest(models.Model): return user.is_authenticated() and (self.created_by == user or user.admin_type == AdminType.SUPER_ADMIN) def check_oi_permission(self, user): - if self.status != ContestStatus.CONTEST_ENDED and self.real_time_rank == False: + if self.status != ContestStatus.CONTEST_ENDED and not self.real_time_rank: if self.is_contest_admin(user): return True else: diff --git a/deploy/Dockerfile b/deploy/Dockerfile index 6f3d17d1..ce70ae41 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.5 +FROM python:3.6 ADD requirements.txt /tmp RUN pip install -r /tmp/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple WORKDIR /app diff --git a/deploy/requirements.txt b/deploy/requirements.txt index 1d2032c9..f6bb6fe7 100644 --- a/deploy/requirements.txt +++ b/deploy/requirements.txt @@ -1,7 +1,6 @@ django==1.11.4 djangorestframework==3.4.0 pillow -jsonfield otpauth flake8-quotes pytz diff --git a/judge/dispatcher.py b/judge/dispatcher.py index 829a43d7..199e627f 100644 --- a/judge/dispatcher.py +++ b/judge/dispatcher.py @@ -136,9 +136,11 @@ class JudgeDispatcher(object): self.submission.save() self.release_judge_server(server.id) - self.update_problem_status() if self.contest_id: + self.update_contest_problem_status() self.update_contest_rank() + else: + self.update_problem_status() # 至此判题结束,尝试处理任务队列中剩余的任务 process_pending_task() @@ -150,65 +152,91 @@ class JudgeDispatcher(object): return self._request(urljoin(service_url, "compile_spj"), data=data) def update_problem_status(self): - if self.contest_id and self.contest.status != ContestStatus.CONTEST_UNDERWAY: - logger.info("Contest debug mode, id: " + str(self.contest_id) + ", submission id: " + self.submission.id) - return + result = str(self.submission.result) + problem_id = str(self.problem.id) with transaction.atomic(): - # prepare problem and user_profile + # update problem status problem = Problem.objects.select_for_update().get(contest_id=self.contest_id, id=self.problem.id) - user = User.objects.select_for_update().select_for_update("userprofile").get(id=self.submission.user_id) - user_profile = user.userprofile - if self.contest_id: - key = "contest_problems" - else: - key = "problems" - acm_problems_status = user_profile.acm_problems_status.get(key, {}) - oi_problems_status = user_profile.oi_problems_status.get(key, {}) - problem_id = str(self.problem.id) - problem_info = problem.statistic_info - - # update problem info - result = str(self.submission.result) - problem_info[result] = problem_info.get(result, 0) + 1 - problem.statistic_info = problem_info - - # update submission and accepted number counter problem.submission_number += 1 if self.submission.result == JudgeStatus.ACCEPTED: problem.accepted_number += 1 - # submission in a contest will not be counted in user profile - if not self.contest_id: + problem_info = problem.statistic_info + problem_info[result] = problem_info.get(result, 0) + 1 + problem.save(update_fields=["accepted_number", "submission_number", "statistic_info"]) + + # update_userprofile + user = User.objects.select_for_update().get(id=self.submission.user_id) + user_profile = user.userprofile + if problem.rule_type == ProblemRuleType.ACM: user_profile.submission_number += 1 if self.submission.result == JudgeStatus.ACCEPTED: user_profile.accepted_number += 1 - - if self.problem.rule_type == ProblemRuleType.ACM: - # update user_profile + acm_problems_status = user_profile.acm_problems_status.get("problems", {}) if problem_id not in acm_problems_status: acm_problems_status[problem_id] = {"status": self.submission.result, "_id": self.problem._id} - # skip if the problem has been accepted elif acm_problems_status[problem_id]["status"] != JudgeStatus.ACCEPTED: acm_problems_status[problem_id]["status"] = self.submission.result - user_profile.acm_problems_status[key] = acm_problems_status + user_profile.acm_problems_status["problems"] = acm_problems_status + user_profile.save(update_fields=["submission_number", "accepted_number", "acm_problems_status"]) else: - # update user_profile + oi_problems_status = user_profile.oi_problems_status.get("problems", {}) score = self.submission.statistic_info["score"] if problem_id not in oi_problems_status: user_profile.add_score(score) oi_problems_status[problem_id] = {"status": self.submission.result, - "_id": self.problem._id, - "score": score} + "_id": self.problem._id, + "score": score} else: # minus last time score, add this time score - user_profile.add_score(this_time_score=score, last_time_score=oi_problems_status[problem_id]["score"]) + user_profile.add_score(this_time_score=score, + last_time_score=oi_problems_status[problem_id]["score"]) oi_problems_status[problem_id]["score"] = score oi_problems_status[problem_id]["status"] = self.submission.result - user_profile.oi_problems_status[key] = oi_problems_status + user_profile.oi_problems_status["problems"] = oi_problems_status + user_profile.save(update_fields=["oi_problems_status"]) + def update_contest_problem_status(self): + if self.contest_id and self.contest.status != ContestStatus.CONTEST_UNDERWAY: + logger.info("Contest debug mode, id: " + str(self.contest_id) + ", submission id: " + self.submission.id) + return + with transaction.atomic(): + user = User.objects.select_for_update().select_related("userprofile").get(id=self.submission.user_id) + user_profile = user.userprofile + problem_id = str(self.problem.id) + if self.contest.rule_type == ContestRuleType.ACM: + contest_problems_status = user_profile.acm_problems_status.get("contest_problems", {}) + if problem_id not in contest_problems_status: + contest_problems_status[problem_id] = {"status": self.submission.result, "_id": self.problem._id} + elif contest_problems_status[problem_id]["status"] != JudgeStatus.ACCEPTED: + contest_problems_status[problem_id]["status"] = self.submission.result + else: + # 如果已AC, 直接跳过 不计入任何计数器 + return + user_profile.acm_problems_status["contest_problems"] = contest_problems_status + user_profile.save(update_fields=["acm_problems_status"]) + + elif self.contest.rule_type == ContestRuleType.OI: + contest_problems_status = user_profile.oi_problems_status.get("contest_problems", {}) + score = self.submission.statistic_info["score"] + if problem_id not in contest_problems_status: + contest_problems_status[problem_id] = {"status": self.submission.result, + "_id": self.problem._id, + "score": score} + else: + contest_problems_status[problem_id]["score"] = score + contest_problems_status[problem_id]["status"] = self.submission.result + user_profile.oi_problems_status["contest_problems"] = contest_problems_status + user_profile.save(update_fields=["oi_problems_status"]) + + problem = Problem.objects.select_for_update().get(contest_id=self.contest_id, id=self.problem.id) + result = str(self.submission.result) + problem_info = problem.statistic_info + problem_info[result] = problem_info.get(result, 0) + 1 + problem.submission_number += 1 + if self.submission.result == JudgeStatus.ACCEPTED: + problem.accepted_number += 1 problem.save(update_fields=["submission_number", "accepted_number", "statistic_info"]) - user_profile.save(update_fields=[ - "submission_number", "accepted_number", "acm_problems_status", "oi_problems_status"]) def update_contest_rank(self): if self.contest_id and self.contest.status != ContestStatus.CONTEST_UNDERWAY: diff --git a/problem/migrations/0009_auto_20171011_1214.py b/problem/migrations/0009_auto_20171011_1214.py index 7073b8f9..e219ff29 100644 --- a/problem/migrations/0009_auto_20171011_1214.py +++ b/problem/migrations/0009_auto_20171011_1214.py @@ -38,4 +38,8 @@ class Migration(migrations.Migration): name='test_case_score', field=django.contrib.postgres.fields.jsonb.JSONField(), ), + migrations.AlterModelOptions( + name='problem', + options={'ordering': ('create_time',)}, + ), ] diff --git a/problem/models.py b/problem/models.py index 90537932..6fb1c799 100644 --- a/problem/models.py +++ b/problem/models.py @@ -71,6 +71,7 @@ class Problem(models.Model): class Meta: db_table = "problem" unique_together = (("_id", "contest"),) + ordering = ("create_time",) def add_submission_number(self): self.submission_number = models.F("submission_number") + 1 diff --git a/problem/tests.py b/problem/tests.py index 7efa668b..072abd48 100644 --- a/problem/tests.py +++ b/problem/tests.py @@ -25,6 +25,7 @@ DEFAULT_PROBLEM_DATA = {"_id": "A-110", "title": "test", "description": "

test "input_size": 0, "score": 0}], "rule_type": "ACM", "hint": "

test

", "source": "test"} + class ProblemCreateTestBase(APITestCase): @staticmethod def add_problem(problem_data, created_by): @@ -32,7 +33,8 @@ class ProblemCreateTestBase(APITestCase): if data["spj"]: if not data["spj_language"] or not data["spj_code"]: raise ValueError("Invalid spj") - data["spj_version"] = hashlib.md5((data["spj_language"] + ":" + data["spj_code"]).encode("utf-8")).hexdigest() + data["spj_version"] = hashlib.md5( + (data["spj_language"] + ":" + data["spj_code"]).encode("utf-8")).hexdigest() else: data["spj_language"] = None data["spj_code"] = None @@ -215,7 +217,7 @@ class ContestProblemAdminTest(APITestCase): self.assertEqual(len(resp.data["data"]), 1) def test_get_one_contest_problem(self): - contest, contest_problem = self.test_create_contest_problem() + contest, contest_problem = self.test_create_contest_problem() contest_id = contest.data["data"]["id"] problem_id = contest_problem.data["data"]["id"] resp = self.client.get(f"{self.url}?contest_id={contest_id}&id={problem_id}") @@ -233,7 +235,7 @@ class ContestProblemTest(ProblemCreateTestBase): self.problem = self.add_problem(DEFAULT_PROBLEM_DATA, admin) self.problem.contest_id = self.contest["id"] self.problem.save() - self.url = self.reverse("contest_problem_api") + self.url = self.reverse("contest_problem_api") def test_admin_get_contest_problem_list(self): contest_id = self.contest["id"] diff --git a/problem/urls/oj.py b/problem/urls/oj.py index c50cafc6..f7cd3ae3 100644 --- a/problem/urls/oj.py +++ b/problem/urls/oj.py @@ -1,9 +1,10 @@ from django.conf.urls import url -from ..views.oj import ProblemTagAPI, ProblemAPI, ContestProblemAPI +from ..views.oj import ProblemTagAPI, ProblemAPI, ContestProblemAPI, PickOneAPI urlpatterns = [ url(r"^problem/tags/?$", ProblemTagAPI.as_view(), name="problem_tag_list_api"), url(r"^problem/?$", ProblemAPI.as_view(), name="problem_api"), + url(r"^pickone/?$", PickOneAPI.as_view(), name="pick_one_api"), url(r"^contest/problem/?$", ContestProblemAPI.as_view(), name="contest_problem_api"), ] diff --git a/problem/views/oj.py b/problem/views/oj.py index 330e9332..2f54ed5d 100644 --- a/problem/views/oj.py +++ b/problem/views/oj.py @@ -1,3 +1,4 @@ +import random from django.db.models import Q from utils.api import APIView from account.decorators import check_contest_permission @@ -12,6 +13,15 @@ class ProblemTagAPI(APIView): return self.success(TagSerializer(ProblemTag.objects.all(), many=True).data) +class PickOneAPI(APIView): + def get(self, request): + problems = Problem.objects.filter(contest_id__isnull=True, visible=True) + count = problems.count() + if count == 0: + return self.error("No problem to pick") + return self.success(problems[random.randint(0, count - 1)]._id) + + class ProblemAPI(APIView): @staticmethod def _add_problem_status(request, queryset_values): @@ -24,7 +34,7 @@ class ProblemAPI(APIView): if results is not None: problems = results else: - problems = [queryset_values,] + problems = [queryset_values, ] for problem in problems: if problem["rule_type"] == ProblemRuleType.ACM: problem["my_status"] = acm_problems_status.get(str(problem["id"]), {}).get("status") @@ -36,7 +46,7 @@ class ProblemAPI(APIView): problem_id = request.GET.get("problem_id") if problem_id: try: - problem = Problem.objects.select_related("created_by")\ + problem = Problem.objects.select_related("created_by") \ .get(_id=problem_id, contest_id__isnull=True, visible=True) problem_data = ProblemSerializer(problem).data self._add_problem_status(request, problem_data) @@ -93,9 +103,8 @@ class ContestProblemAPI(APIView): except Problem.DoesNotExist: return self.error("Problem does not exist.") problem_data = ContestProblemSerializer(problem).data - self._add_problem_status(request, [problem_data,]) + self._add_problem_status(request, [problem_data, ]) return self.success(problem_data) - contest_problems = Problem.objects.select_related("created_by").filter(contest=self.contest, visible=True) # 根据profile, 为做过的题目添加标记 data = ContestProblemSerializer(contest_problems, many=True).data diff --git a/submission/models.py b/submission/models.py index 7f046a04..fe6a9916 100644 --- a/submission/models.py +++ b/submission/models.py @@ -1,6 +1,5 @@ from django.db import models from utils.models import JSONField -from account.models import AdminType from problem.models import Problem from contest.models import Contest diff --git a/submission/views/oj.py b/submission/views/oj.py index 4cf6a8c4..2430a206 100644 --- a/submission/views/oj.py +++ b/submission/views/oj.py @@ -1,6 +1,6 @@ from account.decorators import login_required, check_contest_permission -from judge.tasks import judge_task -# from judge.dispatcher import JudgeDispatcher +# from judge.tasks import judge_task +from judge.dispatcher import JudgeDispatcher from problem.models import Problem, ProblemRuleType from contest.models import Contest, ContestStatus, ContestRuleType from utils.api import APIView, validate_serializer @@ -39,8 +39,8 @@ def _submit(response, user, problem_id, language, code, contest_id): problem_id=problem.id, contest_id=contest_id) # use this for debug - # JudgeDispatcher(submission.id, problem.id).judge() - judge_task.delay(submission.id, problem.id) + JudgeDispatcher(submission.id, problem.id).judge() + # judge_task.delay(submission.id, problem.id) return response.success({"submission_id": submission.id}) @@ -160,4 +160,3 @@ class ContestSubmissionListAPI(APIView): data = self.paginate_data(request, submissions) data["results"] = SubmissionListSerializer(data["results"], many=True, user=request.user).data return self.success(data) -