From d650252a1a45aad1300de3f43d845567f946192f Mon Sep 17 00:00:00 2001 From: zema1 Date: Sat, 30 Sep 2017 10:26:54 +0800 Subject: [PATCH] separate contest submission and regular submission --- account/decorators.py | 9 +++--- contest/models.py | 5 ++- contest/tests.py | 18 ++--------- contest/views/admin.py | 2 +- problem/tests.py | 37 +++++++++++++--------- problem/views/oj.py | 2 +- submission/tests.py | 33 ++++++++++++-------- submission/urls/oj.py | 5 +-- submission/views/oj.py | 69 ++++++++++++++++++++++++++---------------- 9 files changed, 103 insertions(+), 77 deletions(-) diff --git a/account/decorators.py b/account/decorators.py index 0b14f89f..f47c4a03 100644 --- a/account/decorators.py +++ b/account/decorators.py @@ -80,16 +80,17 @@ def check_contest_permission(func): except Contest.DoesNotExist: return self.error("Contest %s doesn't exist" % contest_id) - if self.contest.status == ContestStatus.CONTEST_NOT_START and user != self.contest.created_by: + # creator or owner + if self.contest.is_contest_admin(user): + return func(*args, **kwargs) + + if self.contest.status == ContestStatus.CONTEST_NOT_START: return self.error("Contest has not started yet.") if self.contest.contest_type == ContestType.PASSWORD_PROTECTED_CONTEST: # Anonymous if not user.is_authenticated(): return self.error("Please login in first.") - # creator - if user == self.contest.created_by: - return func(*args, **kwargs) # password error if ("contests" not in request.session) or (self.contest.id not in request.session["contests"]): return self.error("Password is required.") diff --git a/contest/models.py b/contest/models.py index 80ac92ab..3383d17a 100644 --- a/contest/models.py +++ b/contest/models.py @@ -2,7 +2,7 @@ from django.db import models from django.utils.timezone import now from jsonfield import JSONField -from account.models import User +from account.models import User, AdminType from utils.models import RichTextField @@ -56,6 +56,9 @@ class Contest(models.Model): return ContestType.PASSWORD_PROTECTED_CONTEST return ContestType.PUBLIC_CONTEST + def is_contest_admin(self, user): + return user.is_authenticated() and (self.created_by == user or user.admin_type == AdminType.SUPER_ADMIN) + class Meta: db_table = "contest" ordering = ("-create_time",) diff --git a/contest/tests.py b/contest/tests.py index 583035cf..c481becb 100644 --- a/contest/tests.py +++ b/contest/tests.py @@ -74,17 +74,17 @@ class ContestAPITest(APITestCase): response = self.client.get("{}?id={}".format(self.url, contest_id)) self.assertSuccess(response) - def test_contest_password(self): + def test_regular_user_validate_contest_password(self): contest_id = self.create_contest().data["data"]["id"] self.create_user("test", "test123") url = self.reverse("contest_password_api") resp = self.client.post(url, {"contest_id": contest_id, "password": "error_password"}) - self.assertFailed(resp) + self.assertDictEqual(resp.data, {"error": "error", "data": "Password doesn't match."}) resp = self.client.post(url, {"contest_id": contest_id, "password": DEFAULT_CONTEST_DATA["password"]}) self.assertSuccess(resp) - def test_contest_access(self): + def test_regular_user_access_contest(self): contest_id = self.create_contest().data["data"]["id"] self.create_user("test", "test123") url = self.reverse("contest_access_api") @@ -97,18 +97,6 @@ class ContestAPITest(APITestCase): resp = self.client.get(url + "?contest_id=" + str(contest_id)) self.assertSuccess(resp) - # def test_get_not_started_contest(self): - # contest_id = self.create_contest().data["data"]["id"] - # resp = self.client.get(self.url + "?id=" + str(contest_id)) - # self.assertSuccess(resp) - # - # self.create_user("test", "1234") - # url = self.reverse("contest_password_api") - # resp = self.client.post(url, {"contest_id": contest_id, "password": DEFAULT_CONTEST_DATA["password"]}) - # self.assertSuccess(resp) - # resp = self.client.get(self.url + "?id=" + str(contest_id)) - # self.assertFailed(resp) - class ContestAnnouncementAPITest(APITestCase): def setUp(self): diff --git a/contest/views/admin.py b/contest/views/admin.py index 5f9a62c6..37244fad 100644 --- a/contest/views/admin.py +++ b/contest/views/admin.py @@ -18,7 +18,7 @@ class ContestAPI(APIView): data["created_by"] = request.user if data["end_time"] <= data["start_time"]: return self.error("Start time must occur earlier than end time") - if not data["password"]: + if data.get("password") and data["password"] == "": data["password"] = None contest = Contest.objects.create(**data) return self.success(ContestAdminSerializer(contest).data) diff --git a/problem/tests.py b/problem/tests.py index 79791274..4e081ff7 100644 --- a/problem/tests.py +++ b/problem/tests.py @@ -1,6 +1,7 @@ import copy import os import shutil +from datetime import timedelta from zipfile import ZipFile from django.conf import settings @@ -9,6 +10,7 @@ from utils.api.tests import APITestCase from .models import ProblemTag from .views.admin import TestCaseUploadAPI +from contest.models import Contest from contest.tests import DEFAULT_CONTEST_DATA DEFAULT_PROBLEM_DATA = {"_id": "A-110", "title": "test", "description": "

test

", "input_description": "test", @@ -193,32 +195,37 @@ class ContestProblemTest(APITestCase): self.create_admin() url = self.reverse("contest_admin_api") - self.contest = self.client.post(url, data=DEFAULT_CONTEST_DATA) - self.data = DEFAULT_PROBLEM_DATA - self.data["contest"] = self.contest.data["data"]["id"] + contest_data = copy.deepcopy(DEFAULT_CONTEST_DATA) + contest_data["password"] = "" + contest_data["start_time"] = contest_data["start_time"] + timedelta(hours=1) + self.contest = self.client.post(url, data=contest_data).data["data"] + + problem_data = copy.deepcopy(DEFAULT_PROBLEM_DATA) + problem_data["contest"] = self.contest["id"] url = self.reverse("contest_problem_admin_api") - self.problem = self.client.post(url, self.data) + self.problem = self.client.post(url, problem_data).data["data"] def test_get_contest_problem_list(self): - contest_id = self.contest.data["data"]["id"] + contest_id = self.contest["id"] resp = self.client.get(self.url + "?contest_id=" + str(contest_id)) self.assertSuccess(resp) self.assertEqual(len(resp.data["data"]), 1) def test_get_one_contest_problem(self): - contest_id = self.contest.data["data"]["id"] - problem_id = self.problem.data["data"]["_id"] + contest_id = self.contest["id"] + problem_id = self.problem["_id"] resp = self.client.get("{}?contest_id={}&problem_id={}".format(self.url, contest_id, problem_id)) self.assertSuccess(resp) - def test_regular_user_get_contest_problem(self): + def test_regular_user_get_not_started_contest_problem(self): self.create_user("test", "test123") - contest_id = self.contest.data["data"]["id"] - problem_id = self.problem.data["data"]["_id"] - resp = self.client.get("{}?contest_id={}&problem_id={}".format(self.url, contest_id, problem_id)) - self.assertFailed(resp) + resp = self.client.get(self.url + "?contest_id=" + str(self.contest["id"])) + self.assertDictEqual(resp.data, {"error": "error", "data": "Contest has not started yet."}) - url = self.reverse("contest_password_api") - self.client.post(url, {"contest_id": contest_id, "password": DEFAULT_CONTEST_DATA["password"]}) - resp = self.client.get("{}?contest_id={}&problem_id={}".format(self.url, contest_id, problem_id)) + def test_reguar_user_get_started_contest_problem(self): + self.create_user("test", "test123") + contest = Contest.objects.first() + contest.start_time = contest.start_time - timedelta(hours=1) + contest.save() + resp = self.client.get(self.url + "?contest_id=" + str(self.contest["id"])) self.assertSuccess(resp) diff --git a/problem/views/oj.py b/problem/views/oj.py index f5b03f86..0a6b066b 100644 --- a/problem/views/oj.py +++ b/problem/views/oj.py @@ -77,7 +77,7 @@ class ContestProblemAPI(APIView): contest_problems = Problem.objects.select_related("created_by").filter(contest=self.contest, visible=True) # 根据profile, 为做过的题目添加标记 data = ContestProblemSerializer(contest_problems, many=True).data - if request.user.id: + if request.user.is_authenticated() and self.contest.rule_type != ContestRuleType.OI: profile = request.user.userprofile if self.contest.rule_type == ContestRuleType.ACM: problems_status = profile.acm_problems_status.get("contest_problems", {}) diff --git a/submission/tests.py b/submission/tests.py index ca4ffa5c..c7348827 100644 --- a/submission/tests.py +++ b/submission/tests.py @@ -5,7 +5,7 @@ from .models import Submission from problem.models import Problem, ProblemTag from utils.api.tests import APITestCase -DEFAULT_PROBLEM_DATA = {"_id": "110", "title": "test", "description": "

test

", "input_description": "test", +DEFAULT_PROBLEM_DATA = {"_id": "A-110", "title": "test", "description": "

test

", "input_description": "test", "output_description": "test", "time_limit": 1000, "memory_limit": 256, "difficulty": "Low", "visible": True, "tags": ["test"], "languages": ["C", "C++", "Java", "Python2"], "template": {}, "samples": [{"input": "test", "output": "test"}], "spj": False, "spj_language": "C", @@ -25,13 +25,28 @@ DEFAULT_SUBMISSION_DATA = { "language": "C", "statistic_info": {} } +# todo contest submission -class SubmissionListTest(APITestCase): +class SubmissionPrepare(APITestCase): + def _create_problem_and_submission(self): + user = self.create_admin("test", "test123", login=False) + problem_data = deepcopy(DEFAULT_PROBLEM_DATA) + problem_data.pop("tags") + problem_data["created_by"] = user + self.problem = Problem.objects.create(**problem_data) + for tag in DEFAULT_PROBLEM_DATA["tags"]: + tag = ProblemTag.objects.create(name=tag) + self.problem.tags.add(tag) + self.problem.save() + self.submission = Submission.objects.create(**DEFAULT_SUBMISSION_DATA) + + +class SubmissionListTest(SubmissionPrepare): def setUp(self): + self._create_problem_and_submission() self.create_user("123", "345") self.url = self.reverse("submission_list_api") - Submission.objects.create(**DEFAULT_SUBMISSION_DATA) def test_get_submission_list(self): resp = self.client.get(self.url, data={"limit": "10"}) @@ -39,16 +54,10 @@ class SubmissionListTest(APITestCase): @mock.patch("submission.views.oj.judge_task.delay") -class SubmissionAPITest(APITestCase): +class SubmissionAPITest(SubmissionPrepare): def setUp(self): - self.user = self.create_user("test", "test123") - tag = ProblemTag.objects.create(name="test") - problem_data = deepcopy(DEFAULT_PROBLEM_DATA) - problem_data.pop("tags") - problem_data["created_by"] = self.user - self.problem = Problem.objects.create(**problem_data) - self.problem.tags.add(tag) - self.problem.save() + self._create_problem_and_submission() + self.user = self.create_user("123", "test123") self.url = self.reverse("submission_api") def test_create_submission(self, judge_task): diff --git a/submission/urls/oj.py b/submission/urls/oj.py index 30cc62f3..f2a77039 100644 --- a/submission/urls/oj.py +++ b/submission/urls/oj.py @@ -1,8 +1,9 @@ from django.conf.urls import url -from ..views.oj import SubmissionAPI, SubmissionListAPI +from ..views.oj import SubmissionAPI, SubmissionListAPI, ContestSubmissionListAPI urlpatterns = [ url(r"^submission/?$", SubmissionAPI.as_view(), name="submission_api"), - url(r"^submissions/?$", SubmissionListAPI.as_view(), name="submission_list_api") + url(r"^submissions/?$", SubmissionListAPI.as_view(), name="submission_list_api"), + url(r"^contest_submissions/?$", ContestSubmissionListAPI.as_view(), name="contest_submission_list_api"), ] diff --git a/submission/views/oj.py b/submission/views/oj.py index e2c59f27..274418a7 100644 --- a/submission/views/oj.py +++ b/submission/views/oj.py @@ -1,9 +1,8 @@ -from account.models import AdminType from account.decorators import login_required, check_contest_permission from judge.tasks import judge_task # from judge.dispatcher import JudgeDispatcher from problem.models import Problem, ProblemRuleType -from contest.models import Contest, ContestStatus +from contest.models import Contest, ContestStatus, ContestRuleType from utils.api import APIView, validate_serializer from utils.throttling import TokenBucket, BucketController from ..models import Submission @@ -64,7 +63,7 @@ class SubmissionAPI(APIView): def get(self, request): submission_id = request.GET.get("id") if not submission_id: - return self.error("Parameter id do esn't exist.") + return self.error("Parameter id doesn't exist.") try: submission = Submission.objects.select_related("problem").get(id=submission_id) except Submission.DoesNotExist: @@ -82,36 +81,15 @@ class SubmissionListAPI(APIView): if not request.GET.get("limit"): return self.error("Limit is needed") if request.GET.get("contest_id"): - return self._get_contest_submission_list(request) + return self.error("Parameter error") submissions = Submission.objects.filter(contest_id__isnull=True) - return self.process_submissions(request, submissions) - - @check_contest_permission - def _get_contest_submission_list(self, request): - contest = self.contest - # todo OI mode - submissions = Submission.objects.filter(contest_id=contest.id) - # filter the test submissions submitted before contest start - if contest.status != ContestStatus.CONTEST_NOT_START: - print(contest.start_time) - submissions = submissions.filter(create_time__gte=contest.start_time) - - # 封榜的时候只能看到自己的提交 - if not contest.real_time_rank: - if request.user and not ( - request.user.admin_type == AdminType.SUPER_ADMIN or request.user == contest.created_by): - submissions = submissions.filter(user_id=request.user.id) - - return self.process_submissions(request, submissions) - - def process_submissions(self, request, submissions): problem_id = request.GET.get("problem_id") myself = request.GET.get("myself") result = request.GET.get("result") if problem_id: try: - problem = Problem.objects.get(_id=problem_id, visible=True) + problem = Problem.objects.get(_id=problem_id, contest_id__isnull=True, visible=True) except Problem.DoesNotExist: return self.error("Problem doesn't exist") submissions = submissions.filter(problem=problem) @@ -122,3 +100,42 @@ class SubmissionListAPI(APIView): data = self.paginate_data(request, submissions) data["results"] = SubmissionListSerializer(data["results"], many=True, user=request.user).data return self.success(data) + + +class ContestSubmissionListAPI(APIView): + @check_contest_permission + def get(self, request): + if not request.GET.get("limit"): + return self.error("Limit is needed") + + contest = self.contest + if contest.rule_type == ContestRuleType.OI and not contest.is_contest_admin(request.user): + return self.error("No permission for OI contest submissions") + + submissions = Submission.objects.filter(contest_id=contest.id) + problem_id = request.GET.get("problem_id") + myself = request.GET.get("myself") + result = request.GET.get("result") + if problem_id: + try: + problem = Problem.objects.get(_id=problem_id, contest_id=contest.id, visible=True) + except Problem.DoesNotExist: + return self.error("Problem doesn't exist") + submissions = submissions.filter(problem=problem) + + if myself and myself == "1": + submissions = submissions.filter(user_id=request.user.id) + if result: + submissions = submissions.filter(result=result) + + # filter the test submissions submitted before contest start + if contest.status != ContestStatus.CONTEST_NOT_START: + submissions = submissions.filter(create_time__gte=contest.start_time) + + # 封榜的时候只能看到自己的提交 + if not contest.real_time_rank and not contest.is_contest_admin(request.user): + submissions = submissions.filter(user_id=request.user.id) + + data = self.paginate_data(request, submissions) + data["results"] = SubmissionListSerializer(data["results"], many=True, user=request.user).data + return self.success(data)