diff --git a/account/decorators.py b/account/decorators.py index d9c7cc95..839893f1 100644 --- a/account/decorators.py +++ b/account/decorators.py @@ -73,7 +73,7 @@ def check_contest_permission(check_type="details"): else: contest_id = request.GET.get("contest_id") if not contest_id: - return self.error("Parameter contest_id doesn't exist.") + return self.error("Parameter error, contest_id is required") try: # use self.contest to avoid query contest again in view. diff --git a/contest/views/admin.py b/contest/views/admin.py index ebc0c7ac..d4066b51 100644 --- a/contest/views/admin.py +++ b/contest/views/admin.py @@ -10,7 +10,7 @@ from ..models import Contest, ContestAnnouncement, ACMContestRank from ..serializers import (ContestAnnouncementSerializer, ContestAdminSerializer, CreateConetestSeriaizer, CreateContestAnnouncementSerializer, EditConetestSeriaizer, EditContestAnnouncementSerializer, - ACMContesHelperSerializer) + ACMContesHelperSerializer, ) class ContestAPI(APIView): diff --git a/problem/models.py b/problem/models.py index 5116f4b6..12a54228 100644 --- a/problem/models.py +++ b/problem/models.py @@ -4,6 +4,7 @@ from utils.models import JSONField from account.models import User from contest.models import Contest from utils.models import RichTextField +from utils.constants import Choices class ProblemTag(models.Model): @@ -13,7 +14,7 @@ class ProblemTag(models.Model): db_table = "problem_tag" -class ProblemRuleType(object): +class ProblemRuleType(Choices): ACM = "ACM" OI = "OI" diff --git a/problem/serializers.py b/problem/serializers.py index aee5721e..ded8f0f5 100644 --- a/problem/serializers.py +++ b/problem/serializers.py @@ -174,3 +174,9 @@ class ExportProblemSerializer(serializers.ModelSerializer): "input_description", "output_description", "test_case_score", "hint", "time_limit", "memory_limit", "samples", "template", "spj", "rule_type", "source", "template") + + +class AddContestProblemSerializer(serializers.Serializer): + contest_id = serializers.IntegerField() + problem_id = serializers.IntegerField() + display_id = serializers.CharField() diff --git a/problem/tests.py b/problem/tests.py index 46d9ac69..f4e7321a 100644 --- a/problem/tests.py +++ b/problem/tests.py @@ -17,7 +17,6 @@ from contest.tests import DEFAULT_CONTEST_DATA from .views.admin import TestCaseAPI from .utils import parse_problem_template - 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": {}, @@ -259,6 +258,29 @@ class ContestProblemTest(ProblemCreateTestBase): self.assertSuccess(resp) +class AddProblemFromPublicProblemAPITest(ProblemCreateTestBase): + def setUp(self): + admin = self.create_admin() + url = self.reverse("contest_admin_api") + 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"] + self.problem = self.add_problem(DEFAULT_PROBLEM_DATA, admin) + self.url = self.reverse("add_contest_problem_from_public_api") + self.data = { + "display_id": "1000", + "contest_id": self.contest["id"], + "problem_id": self.problem.id + } + + def test_add_contest_problem(self): + resp = self.client.post(self.url, data=self.data) + self.assertSuccess(resp) + self.assertTrue(Problem.objects.all().exists()) + self.assertTrue(Problem.objects.filter(contest_id=self.contest["id"]).exists()) + + class ParseProblemTemplateTest(APITestCase): def test_parse(self): template_str = """ diff --git a/problem/urls/admin.py b/problem/urls/admin.py index d4f99746..8e16a8a3 100644 --- a/problem/urls/admin.py +++ b/problem/urls/admin.py @@ -1,7 +1,7 @@ from django.conf.urls import url from ..views.admin import ContestProblemAPI, ProblemAPI, TestCaseAPI, MakeContestProblemPublicAPIView -from ..views.admin import CompileSPJAPI +from ..views.admin import CompileSPJAPI, AddContestProblemAPI urlpatterns = [ url(r"^test_case/?$", TestCaseAPI.as_view(), name="test_case_api"), @@ -9,4 +9,5 @@ urlpatterns = [ url(r"^problem/?$", ProblemAPI.as_view(), name="problem_admin_api"), url(r"^contest/problem/?$", ContestProblemAPI.as_view(), name="contest_problem_admin_api"), url(r"^contest_problem/make_public/?$", MakeContestProblemPublicAPIView.as_view(), name="make_public_api"), + url(r"^contest/add_problem_from_public/?$", AddContestProblemAPI.as_view(), name="add_contest_problem_from_public_api"), ] diff --git a/problem/views/admin.py b/problem/views/admin.py index 0e5045d7..352aa095 100644 --- a/problem/views/admin.py +++ b/problem/views/admin.py @@ -10,7 +10,7 @@ from django.http import StreamingHttpResponse, HttpResponse from account.decorators import problem_permission_required from judge.dispatcher import SPJCompiler -from contest.models import Contest +from contest.models import Contest, ContestStatus from submission.models import Submission from utils.api import APIView, CSRFExemptAPIView, validate_serializer from utils.shortcuts import rand_str, natural_sort_key @@ -18,7 +18,8 @@ from utils.shortcuts import rand_str, natural_sort_key from ..models import Problem, ProblemRuleType, ProblemTag from ..serializers import (CreateContestProblemSerializer, CompileSPJSerializer, CreateProblemSerializer, EditProblemSerializer, EditContestProblemSerializer, - ProblemAdminSerializer, TestCaseUploadForm, ContestProblemMakePublicSerializer) + ProblemAdminSerializer, TestCaseUploadForm, ContestProblemMakePublicSerializer, + AddContestProblemSerializer) class TestCaseAPI(CSRFExemptAPIView): @@ -71,7 +72,8 @@ class TestCaseAPI(CSRFExemptAPIView): response = HttpResponse() response["X-Accel-Redirect"] = file_name else: - response = StreamingHttpResponse(FileWrapper(open(file_name, "rb")), content_type="application/octet-stream") + response = StreamingHttpResponse(FileWrapper(open(file_name, "rb")), + content_type="application/octet-stream") response["Content-Disposition"] = f"attachment; filename=problem_{problem.id}_test_cases.zip" response["Content-Length"] = os.path.getsize(file_name) @@ -229,6 +231,7 @@ class ProblemAPI(ProblemBase): @problem_permission_required def get(self, request): problem_id = request.GET.get("id") + rule_type = request.GET.get("rule_type") user = request.user if problem_id: try: @@ -240,6 +243,12 @@ class ProblemAPI(ProblemBase): return self.error("Problem does not exist") problems = Problem.objects.filter(contest_id__isnull=True).order_by("-create_time") + if rule_type: + if rule_type not in ProblemRuleType.choices(): + return self.error("Invalid rule_type") + else: + problems = problems.filter(rule_type=rule_type) + if not user.can_mgmt_all_problem(): problems = problems.filter(created_by=user) keyword = request.GET.get("keyword") @@ -433,3 +442,31 @@ class MakeContestProblemPublicAPIView(APIView): problem.save() problem.tags.set(tags) return self.success() + + +class AddContestProblemAPI(APIView): + @validate_serializer(AddContestProblemSerializer) + def post(self, request): + data = request.data + try: + contest = Contest.objects.get(id=data["contest_id"]) + problem = Problem.objects.get(id=data["problem_id"]) + except (Contest.DoesNotExist, Problem.DoesNotExist): + return self.error("Contest or Problem does not exist") + + if contest.status == ContestStatus.CONTEST_ENDED: + return self.error("Contest has ended") + if Problem.objects.filter(contest=contest, _id=data["display_id"]).exists(): + return self.error("Duplicate display id in this contest") + + tags = problem.tags.all() + problem.pk = None + problem.contest = contest + problem.is_public = True + problem.visible = True + problem._id = request.data["display_id"] + problem.submission_number = problem.accepted_number = 0 + problem.statistic_info = {} + problem.save() + problem.tags.set(tags) + return self.success()