OnlineJudge/problem/views/admin.py

468 lines
17 KiB
Python
Raw Normal View History

2017-01-25 15:01:33 +00:00
import hashlib
import json
2017-01-25 08:29:00 +00:00
import os
2017-11-18 00:07:03 +00:00
import shutil
2017-01-25 08:29:00 +00:00
import zipfile
2017-11-14 13:06:33 +00:00
from wsgiref.util import FileWrapper
2017-01-25 08:29:00 +00:00
from django.conf import settings
2017-12-01 13:53:13 +00:00
from django.http import StreamingHttpResponse, HttpResponse
2017-01-25 08:29:00 +00:00
2018-01-04 11:27:41 +00:00
from account.decorators import problem_permission_required, ensure_created_by
2017-11-16 14:12:17 +00:00
from judge.dispatcher import SPJCompiler
from contest.models import Contest, ContestStatus
2017-11-18 00:07:03 +00:00
from submission.models import Submission
2017-02-02 08:59:15 +00:00
from utils.api import APIView, CSRFExemptAPIView, validate_serializer
2017-11-06 13:45:52 +00:00
from utils.shortcuts import rand_str, natural_sort_key
2017-01-26 09:06:24 +00:00
2017-09-24 01:48:17 +00:00
from ..models import Problem, ProblemRuleType, ProblemTag
2017-12-01 09:19:31 +00:00
from ..serializers import (CreateContestProblemSerializer, CompileSPJSerializer,
CreateProblemSerializer, EditProblemSerializer, EditContestProblemSerializer,
ProblemAdminSerializer, TestCaseUploadForm, ContestProblemMakePublicSerializer,
AddContestProblemSerializer)
2017-01-25 08:29:00 +00:00
2017-11-14 13:06:33 +00:00
class TestCaseAPI(CSRFExemptAPIView):
2017-01-25 08:29:00 +00:00
request_parsers = ()
def filter_name_list(self, name_list, spj):
ret = []
prefix = 1
if spj:
while True:
in_name = str(prefix) + ".in"
if in_name in name_list:
ret.append(in_name)
prefix += 1
continue
else:
2017-11-06 13:45:52 +00:00
return sorted(ret, key=natural_sort_key)
2017-01-25 08:29:00 +00:00
else:
while True:
in_name = str(prefix) + ".in"
out_name = str(prefix) + ".out"
if in_name in name_list and out_name in name_list:
ret.append(in_name)
ret.append(out_name)
prefix += 1
continue
else:
2017-11-06 13:45:52 +00:00
return sorted(ret, key=natural_sort_key)
2017-01-25 08:29:00 +00:00
2017-11-14 13:06:33 +00:00
def get(self, request):
problem_id = request.GET.get("problem_id")
if not problem_id:
return self.error("Parameter error, problem_id is required")
try:
problem = Problem.objects.get(id=problem_id)
except Problem.DoesNotExist:
return self.error("Problem does not exists")
2018-01-04 11:27:41 +00:00
if problem.contest:
ensure_created_by(problem.contest, request.user)
else:
ensure_created_by(problem, request.user)
2017-11-14 13:06:33 +00:00
test_case_dir = os.path.join(settings.TEST_CASE_DIR, problem.test_case_id)
if not os.path.isdir(test_case_dir):
return self.error("Test case does not exists")
name_list = self.filter_name_list(os.listdir(test_case_dir), problem.spj)
name_list.append("info")
2017-11-16 14:12:17 +00:00
file_name = os.path.join(test_case_dir, problem.test_case_id + ".zip")
2017-11-14 13:06:33 +00:00
with zipfile.ZipFile(file_name, "w") as file:
for test_case in name_list:
file.write(f"{test_case_dir}/{test_case}", test_case)
2017-12-01 13:53:13 +00:00
if os.environ.get("OJ_ENV") == "production":
response = HttpResponse()
response["X-Accel-Redirect"] = file_name
else:
response = StreamingHttpResponse(FileWrapper(open(file_name, "rb")),
content_type="application/octet-stream")
2017-12-01 13:53:13 +00:00
2017-11-14 13:06:33 +00:00
response["Content-Disposition"] = f"attachment; filename=problem_{problem.id}_test_cases.zip"
2017-12-01 13:53:13 +00:00
response["Content-Length"] = os.path.getsize(file_name)
2017-11-14 13:06:33 +00:00
return response
2017-01-25 08:29:00 +00:00
def post(self, request):
form = TestCaseUploadForm(request.POST, request.FILES)
if form.is_valid():
2017-01-25 08:39:16 +00:00
spj = form.cleaned_data["spj"] == "true"
2017-01-25 08:29:00 +00:00
file = form.cleaned_data["file"]
else:
return self.error("Upload failed")
tmp_file = os.path.join("/tmp", rand_str() + ".zip")
with open(tmp_file, "wb") as f:
for chunk in file:
f.write(chunk)
try:
zip_file = zipfile.ZipFile(tmp_file)
2017-01-25 15:01:33 +00:00
except zipfile.BadZipFile:
2017-01-25 08:29:00 +00:00
return self.error("Bad zip file")
name_list = zip_file.namelist()
test_case_list = self.filter_name_list(name_list, spj=spj)
if not test_case_list:
return self.error("Empty file")
test_case_id = rand_str()
test_case_dir = os.path.join(settings.TEST_CASE_DIR, test_case_id)
os.mkdir(test_case_dir)
2017-01-25 15:01:33 +00:00
size_cache = {}
md5_cache = {}
2017-01-25 08:29:00 +00:00
for item in test_case_list:
with open(os.path.join(test_case_dir, item), "wb") as f:
2017-01-25 15:01:33 +00:00
content = zip_file.read(item).replace(b"\r\n", b"\n")
size_cache[item] = len(content)
if item.endswith(".out"):
2017-10-31 08:33:25 +00:00
md5_cache[item] = hashlib.md5(content.rstrip()).hexdigest()
2017-01-25 15:01:33 +00:00
f.write(content)
test_case_info = {"spj": spj, "test_cases": {}}
2017-01-25 08:39:16 +00:00
hint = None
diff = set(name_list).difference(set(test_case_list))
if diff:
hint = ", ".join(diff) + " are ignored"
2017-01-25 15:01:33 +00:00
ret = []
if spj:
for index, item in enumerate(test_case_list):
data = {"input_name": item, "input_size": size_cache[item]}
ret.append(data)
test_case_info["test_cases"][str(index + 1)] = data
else:
# ["1.in", "1.out", "2.in", "2.out"] => [("1.in", "1.out"), ("2.in", "2.out")]
test_case_list = zip(*[test_case_list[i::2] for i in range(2)])
for index, item in enumerate(test_case_list):
data = {"stripped_output_md5": md5_cache[item[1]],
"input_size": size_cache[item[0]],
"output_size": size_cache[item[1]],
"input_name": item[0],
"output_name": item[1]}
ret.append(data)
test_case_info["test_cases"][str(index + 1)] = data
with open(os.path.join(test_case_dir, "info"), "w", encoding="utf-8") as f:
f.write(json.dumps(test_case_info, indent=4))
return self.success({"id": test_case_id, "info": ret, "hint": hint, "spj": spj})
2017-02-02 08:59:15 +00:00
2017-11-16 14:12:17 +00:00
class CompileSPJAPI(APIView):
@validate_serializer(CompileSPJSerializer)
def post(self, request):
data = request.data
spj_version = rand_str(8)
error = SPJCompiler(data["spj_code"], spj_version, data["spj_language"]).compile_spj()
if error:
return self.error(error)
else:
return self.success()
class ProblemBase(APIView):
def common_checks(self, request):
2017-02-02 08:59:15 +00:00
data = request.data
if data["spj"]:
if not data["spj_language"] or not data["spj_code"]:
return "Invalid spj"
2017-11-18 00:07:03 +00:00
if not data["spj_compile_ok"]:
return "SPJ code must be compiled successfully"
data["spj_version"] = hashlib.md5(
(data["spj_language"] + ":" + data["spj_code"]).encode("utf-8")).hexdigest()
2017-02-02 08:59:15 +00:00
else:
data["spj_language"] = None
data["spj_code"] = None
if data["rule_type"] == ProblemRuleType.OI:
2017-09-24 01:48:17 +00:00
total_score = 0
2017-02-02 08:59:15 +00:00
for item in data["test_case_score"]:
if item["score"] <= 0:
return "Invalid score"
2017-09-24 01:48:17 +00:00
else:
total_score += item["score"]
data["total_score"] = total_score
2017-02-02 08:59:15 +00:00
data["created_by"] = request.user
2017-02-10 07:13:03 +00:00
data["languages"] = list(data["languages"])
2017-11-18 00:07:03 +00:00
@problem_permission_required
def delete(self, request):
id = request.GET.get("id")
if not id:
2018-01-04 11:27:41 +00:00
return self.error("Invalid parameter, id is required")
2017-11-18 00:07:03 +00:00
try:
2018-01-04 11:27:41 +00:00
problem = Problem.objects.get(id=id, contest_id__isnull=True)
2017-11-18 00:07:03 +00:00
except Problem.DoesNotExist:
return self.error("Problem does not exists")
2018-01-04 11:27:41 +00:00
ensure_created_by(problem, request.user)
2017-11-18 00:07:03 +00:00
if Submission.objects.filter(problem=problem).exists():
return self.error("Can't delete the problem as it has submissions")
d = os.path.join(settings.TEST_CASE_DIR, problem.test_case_id)
if os.path.isdir(d):
shutil.rmtree(d, ignore_errors=True)
problem.delete()
return self.success()
2017-02-09 08:47:08 +00:00
class ProblemAPI(ProblemBase):
@problem_permission_required
2018-01-04 11:27:41 +00:00
@validate_serializer(CreateProblemSerializer)
def post(self, request):
data = request.data
_id = data["_id"]
2017-02-09 08:47:08 +00:00
if not _id:
return self.error("Display ID is required")
if Problem.objects.filter(_id=_id, contest_id__isnull=True).exists():
return self.error("Display ID already exists")
error_info = self.common_checks(request)
if error_info:
return self.error(error_info)
# todo check filename and score info
tags = data.pop("tags")
problem = Problem.objects.create(**data)
2017-02-09 08:47:08 +00:00
2017-02-02 08:59:15 +00:00
for item in tags:
try:
tag = ProblemTag.objects.get(name=item)
except ProblemTag.DoesNotExist:
tag = ProblemTag.objects.create(name=item)
problem.tags.add(tag)
2017-08-23 09:01:55 +00:00
return self.success(ProblemAdminSerializer(problem).data)
2017-02-02 08:59:15 +00:00
@problem_permission_required
2017-02-02 08:59:15 +00:00
def get(self, request):
2017-02-03 09:15:53 +00:00
problem_id = request.GET.get("id")
rule_type = request.GET.get("rule_type")
user = request.user
2017-02-03 09:15:53 +00:00
if problem_id:
try:
problem = Problem.objects.get(id=problem_id)
2018-01-04 11:27:41 +00:00
ensure_created_by(problem, request.user)
2017-08-23 09:01:55 +00:00
return self.success(ProblemAdminSerializer(problem).data)
2017-02-03 09:15:53 +00:00
except Problem.DoesNotExist:
return self.error("Problem does not exist")
2017-11-01 10:35:27 +00:00
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():
2017-02-27 04:35:46 +00:00
problems = problems.filter(created_by=user)
2017-02-17 12:14:03 +00:00
keyword = request.GET.get("keyword")
if keyword:
problems = problems.filter(title__contains=keyword)
2017-08-23 09:01:55 +00:00
return self.success(self.paginate_data(request, problems, ProblemAdminSerializer))
2017-02-03 16:10:44 +00:00
@problem_permission_required
2018-01-04 11:27:41 +00:00
@validate_serializer(EditProblemSerializer)
2017-02-03 16:10:44 +00:00
def put(self, request):
2017-02-04 04:23:36 +00:00
data = request.data
problem_id = data.pop("id")
2017-02-04 04:23:36 +00:00
try:
problem = Problem.objects.get(id=problem_id)
2018-01-04 11:27:41 +00:00
ensure_created_by(problem, request.user)
2017-02-04 04:23:36 +00:00
except Problem.DoesNotExist:
return self.error("Problem does not exist")
2017-02-09 08:47:08 +00:00
_id = data["_id"]
if not _id:
return self.error("Display ID is required")
if Problem.objects.exclude(id=problem_id).filter(_id=_id, contest_id__isnull=True).exists():
return self.error("Display ID already exists")
2017-02-17 12:14:03 +00:00
error_info = self.common_checks(request)
if error_info:
return self.error(error_info)
2017-02-04 04:23:36 +00:00
# todo check filename and score info
tags = data.pop("tags")
2017-10-21 02:51:35 +00:00
data["languages"] = list(data["languages"])
2017-02-04 04:23:36 +00:00
for k, v in data.items():
setattr(problem, k, v)
problem.save()
problem.tags.remove(*problem.tags.all())
for tag in tags:
try:
tag = ProblemTag.objects.get(name=tag)
except ProblemTag.DoesNotExist:
tag = ProblemTag.objects.create(name=tag)
problem.tags.add(tag)
2017-02-03 16:10:44 +00:00
return self.success()
2017-02-17 12:14:03 +00:00
class ContestProblemAPI(ProblemBase):
2017-02-17 12:14:03 +00:00
@validate_serializer(CreateContestProblemSerializer)
def post(self, request):
data = request.data
try:
contest = Contest.objects.get(id=data.pop("contest_id"))
2018-01-04 11:27:41 +00:00
ensure_created_by(contest, request.user)
2017-02-17 12:14:03 +00:00
except Contest.DoesNotExist:
return self.error("Contest does not exist")
if data["rule_type"] != contest.rule_type:
return self.error("Invalid rule type")
_id = data["_id"]
if not _id:
return self.error("Display ID is required")
2017-09-24 01:48:17 +00:00
if Problem.objects.filter(_id=_id, contest=contest).exists():
2017-02-17 12:14:03 +00:00
return self.error("Duplicate Display id")
error_info = self.common_checks(request)
if error_info:
return self.error(error_info)
2017-02-17 12:14:03 +00:00
# todo check filename and score info
data["contest"] = contest
tags = data.pop("tags")
2017-09-24 01:48:17 +00:00
problem = Problem.objects.create(**data)
2017-02-17 12:14:03 +00:00
for item in tags:
try:
tag = ProblemTag.objects.get(name=item)
except ProblemTag.DoesNotExist:
tag = ProblemTag.objects.create(name=item)
problem.tags.add(tag)
2017-12-01 09:19:31 +00:00
return self.success(ProblemAdminSerializer(problem).data)
2017-02-17 12:14:03 +00:00
@problem_permission_required
2017-02-17 12:14:03 +00:00
def get(self, request):
problem_id = request.GET.get("id")
contest_id = request.GET.get("contest_id")
user = request.user
if problem_id:
try:
2017-09-24 01:48:17 +00:00
problem = Problem.objects.get(id=problem_id)
2018-01-04 11:27:41 +00:00
ensure_created_by(problem, user)
2017-09-24 01:48:17 +00:00
except Problem.DoesNotExist:
2017-02-17 12:14:03 +00:00
return self.error("Problem does not exist")
2017-08-23 09:01:55 +00:00
return self.success(ProblemAdminSerializer(problem).data)
2017-02-17 12:14:03 +00:00
if not contest_id:
return self.error("Contest id is required")
2017-09-24 01:48:17 +00:00
problems = Problem.objects.filter(contest_id=contest_id).order_by("-create_time")
2017-02-17 12:14:03 +00:00
if user.is_admin():
problems = problems.filter(contest__created_by=user)
keyword = request.GET.get("keyword")
if keyword:
problems = problems.filter(title__contains=keyword)
2017-12-01 09:19:31 +00:00
return self.success(self.paginate_data(request, problems, ProblemAdminSerializer))
@validate_serializer(EditContestProblemSerializer)
@problem_permission_required
def put(self, request):
data = request.data
2018-01-04 11:27:41 +00:00
user = request.user
try:
contest = Contest.objects.get(id=data.pop("contest_id"))
2018-01-04 11:27:41 +00:00
ensure_created_by(contest, user)
except Contest.DoesNotExist:
return self.error("Contest does not exist")
if data["rule_type"] != contest.rule_type:
return self.error("Invalid rule type")
problem_id = data.pop("id")
try:
problem = Problem.objects.get(id=problem_id)
2018-01-04 11:27:41 +00:00
ensure_created_by(problem, user)
except Problem.DoesNotExist:
return self.error("Problem does not exist")
_id = data["_id"]
if not _id:
return self.error("Display ID is required")
if Problem.objects.exclude(id=problem_id).filter(_id=_id, contest=contest).exists():
return self.error("Display ID already exists")
error_info = self.common_checks(request)
if error_info:
return self.error(error_info)
# todo check filename and score info
tags = data.pop("tags")
data["languages"] = list(data["languages"])
for k, v in data.items():
setattr(problem, k, v)
problem.save()
problem.tags.remove(*problem.tags.all())
for tag in tags:
try:
tag = ProblemTag.objects.get(name=tag)
except ProblemTag.DoesNotExist:
tag = ProblemTag.objects.create(name=tag)
problem.tags.add(tag)
return self.success()
class MakeContestProblemPublicAPIView(APIView):
2017-11-07 11:04:41 +00:00
@validate_serializer(ContestProblemMakePublicSerializer)
@problem_permission_required
def post(self, request):
2017-11-07 11:04:41 +00:00
data = request.data
display_id = data.get("display_id")
if Problem.objects.filter(_id=display_id, contest_id__isnull=True).exists():
return self.error("Duplicate display ID")
try:
2017-11-07 11:04:41 +00:00
problem = Problem.objects.get(id=data["id"])
except Problem.DoesNotExist:
return self.error("Problem does not exist")
2017-11-07 11:04:41 +00:00
if not problem.contest or problem.is_public:
2018-01-02 12:05:33 +00:00
return self.error("Already be a public problem")
problem.is_public = True
problem.save()
# https://docs.djangoproject.com/en/1.11/topics/db/queries/#copying-model-instances
tags = problem.tags.all()
problem.pk = None
problem.contest = None
2017-11-07 11:04:41 +00:00
problem._id = display_id
2017-11-29 09:01:48 +00:00
problem.visible = False
problem.submission_number = problem.accepted_number = 0
problem.statistic_info = {}
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()