From 8ce6040a5b9c0059e085e86eedee91312310e20e Mon Sep 17 00:00:00 2001 From: virusdefender Date: Thu, 2 Feb 2017 16:59:15 +0800 Subject: [PATCH] add create problem api --- contest/migrations/0003_auto_20170202_0826.py | 82 +++++++++++++++++++ judge/languages.py | 6 +- problem/migrations/0001_initial.py | 78 ++++++++++++++++++ problem/migrations/0002_auto_20170202_0826.py | 73 +++++++++++++++++ problem/migrations/__init__.py | 0 problem/models.py | 35 ++++---- problem/serializers.py | 61 ++++++++++++++ problem/urls/admin.py | 5 +- problem/urls/oj.py | 2 +- problem/views/admin.py | 50 ++++++++++- utils/api/_serializers.py | 7 -- utils/api/api.py | 20 +++-- 12 files changed, 384 insertions(+), 35 deletions(-) create mode 100644 contest/migrations/0003_auto_20170202_0826.py create mode 100644 problem/migrations/0001_initial.py create mode 100644 problem/migrations/0002_auto_20170202_0826.py create mode 100644 problem/migrations/__init__.py diff --git a/contest/migrations/0003_auto_20170202_0826.py b/contest/migrations/0003_auto_20170202_0826.py new file mode 100644 index 00000000..554b0052 --- /dev/null +++ b/contest/migrations/0003_auto_20170202_0826.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-02-02 08:26 +from __future__ import unicode_literals + +from django.db import migrations, models +import jsonfield.fields +import utils.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('problem', '0002_auto_20170202_0826'), + ('contest', '0002_contestannouncement_created_by'), + ] + + operations = [ + migrations.AddField( + model_name='contestproblem', + name='difficulty', + field=models.CharField(default='LOW', max_length=32), + preserve_default=False, + ), + migrations.AddField( + model_name='contestproblem', + name='languages', + field=jsonfield.fields.JSONField(default=[]), + preserve_default=False, + ), + migrations.AddField( + model_name='contestproblem', + name='rule_type', + field=models.CharField(default='ACM', max_length=32), + preserve_default=False, + ), + migrations.AddField( + model_name='contestproblem', + name='source', + field=models.CharField(blank=True, max_length=200, null=True), + ), + migrations.AddField( + model_name='contestproblem', + name='tags', + field=models.ManyToManyField(to='problem.ProblemTag'), + ), + migrations.AddField( + model_name='contestproblem', + name='test_case_score', + field=jsonfield.fields.JSONField(default={}), + preserve_default=False, + ), + migrations.AlterField( + model_name='contestproblem', + name='input_description', + field=utils.models.RichTextField(), + ), + migrations.AlterField( + model_name='contestproblem', + name='output_description', + field=utils.models.RichTextField(), + ), + migrations.AlterField( + model_name='contestproblem', + name='samples', + field=jsonfield.fields.JSONField(), + ), + migrations.AlterField( + model_name='contestproblem', + name='spj_language', + field=models.CharField(blank=True, max_length=32, null=True), + ), + migrations.AlterField( + model_name='contestproblem', + name='test_case_id', + field=models.CharField(max_length=32), + ), + migrations.AlterField( + model_name='contestproblem', + name='title', + field=models.CharField(max_length=128), + ), + ] diff --git a/judge/languages.py b/judge/languages.py index 94ef3e3a..2ccd72af 100644 --- a/judge/languages.py +++ b/judge/languages.py @@ -89,7 +89,7 @@ _py2_lang_config = { }, "run": { "command": "/usr/bin/python {exe_path}", - "seccomp_rule": None, + "seccomp_rule": "general", } } @@ -103,3 +103,7 @@ languages = [ ] spj_languages = list(filter(lambda item: "spj" in item, languages)) + + +language_names = [item["name"] for item in languages] +spj_language_names = [item["name"] for item in spj_languages] diff --git a/problem/migrations/0001_initial.py b/problem/migrations/0001_initial.py new file mode 100644 index 00000000..87dcb4f0 --- /dev/null +++ b/problem/migrations/0001_initial.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-01-31 09:35 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import jsonfield.fields +import utils.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Problem', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=50)), + ('description', utils.models.RichTextField()), + ('input_description', models.CharField(max_length=10000)), + ('output_description', models.CharField(max_length=10000)), + ('samples', models.TextField(blank=True)), + ('test_case_id', models.CharField(max_length=40)), + ('hint', utils.models.RichTextField(blank=True, null=True)), + ('create_time', models.DateTimeField(auto_now_add=True)), + ('last_update_time', models.DateTimeField(blank=True, null=True)), + ('time_limit', models.IntegerField()), + ('memory_limit', models.IntegerField()), + ('spj', models.BooleanField(default=False)), + ('spj_language', models.IntegerField(blank=True, null=True)), + ('spj_code', models.TextField(blank=True, null=True)), + ('spj_version', models.CharField(blank=True, max_length=32, null=True)), + ('visible', models.BooleanField(default=True)), + ('total_submit_number', models.IntegerField(default=0)), + ('total_accepted_number', models.IntegerField(default=0)), + ('difficulty', models.IntegerField()), + ('source', models.CharField(blank=True, max_length=200, null=True)), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + 'db_table': 'problem', + }, + ), + migrations.CreateModel( + name='ProblemTag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=30)), + ], + options={ + 'db_table': 'problem_tag', + }, + ), + migrations.CreateModel( + name='TestCaseScore', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('test_case_id', models.CharField(max_length=32)), + ('score', jsonfield.fields.JSONField()), + ], + options={ + 'db_table': 'test_case_score', + }, + ), + migrations.AddField( + model_name='problem', + name='tags', + field=models.ManyToManyField(to='problem.ProblemTag'), + ), + ] diff --git a/problem/migrations/0002_auto_20170202_0826.py b/problem/migrations/0002_auto_20170202_0826.py new file mode 100644 index 00000000..9b56810b --- /dev/null +++ b/problem/migrations/0002_auto_20170202_0826.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-02-02 08:26 +from __future__ import unicode_literals + +from django.db import migrations, models +import jsonfield.fields +import utils.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('problem', '0001_initial'), + ] + + operations = [ + migrations.DeleteModel( + name='TestCaseScore', + ), + migrations.AddField( + model_name='problem', + name='languages', + field=jsonfield.fields.JSONField(default=[]), + preserve_default=False, + ), + migrations.AddField( + model_name='problem', + name='rule_type', + field=models.CharField(default='ACM', max_length=32), + preserve_default=False, + ), + migrations.AddField( + model_name='problem', + name='test_case_score', + field=jsonfield.fields.JSONField(default={}), + preserve_default=False, + ), + migrations.AlterField( + model_name='problem', + name='difficulty', + field=models.CharField(max_length=32), + ), + migrations.AlterField( + model_name='problem', + name='input_description', + field=utils.models.RichTextField(), + ), + migrations.AlterField( + model_name='problem', + name='output_description', + field=utils.models.RichTextField(), + ), + migrations.AlterField( + model_name='problem', + name='samples', + field=jsonfield.fields.JSONField(), + ), + migrations.AlterField( + model_name='problem', + name='spj_language', + field=models.CharField(blank=True, max_length=32, null=True), + ), + migrations.AlterField( + model_name='problem', + name='test_case_id', + field=models.CharField(max_length=32), + ), + migrations.AlterField( + model_name='problem', + name='title', + field=models.CharField(max_length=128), + ), + ] diff --git a/problem/migrations/__init__.py b/problem/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/problem/models.py b/problem/models.py index 072abec7..e0507c4c 100644 --- a/problem/models.py +++ b/problem/models.py @@ -12,16 +12,23 @@ class ProblemTag(models.Model): db_table = "problem_tag" +class ProblemRuleType(object): + ACM = "ACM" + OI = "OI" + + class AbstractProblem(models.Model): - title = models.CharField(max_length=50) + title = models.CharField(max_length=128) # HTML description = RichTextField() - input_description = models.CharField(max_length=10000) - output_description = models.CharField(max_length=10000) + input_description = RichTextField() + output_description = RichTextField() # [{input: "test", output: "123"}, {input: "test123", output: "456"}] - samples = models.TextField(blank=True) - test_case_id = models.CharField(max_length=40) + samples = JSONField() + test_case_id = models.CharField(max_length=32) + test_case_score = JSONField() hint = RichTextField(blank=True, null=True) + languages = JSONField() create_time = models.DateTimeField(auto_now_add=True) # we can not use auto_now here last_update_time = models.DateTimeField(blank=True, null=True) @@ -32,10 +39,14 @@ class AbstractProblem(models.Model): memory_limit = models.IntegerField() # special judge related spj = models.BooleanField(default=False) - spj_language = models.IntegerField(blank=True, null=True) + spj_language = models.CharField(max_length=32, blank=True, null=True) spj_code = models.TextField(blank=True, null=True) spj_version = models.CharField(max_length=32, blank=True, null=True) + rule_type = models.CharField(max_length=32) visible = models.BooleanField(default=True) + difficulty = models.CharField(max_length=32) + tags = models.ManyToManyField(ProblemTag) + source = models.CharField(max_length=200, blank=True, null=True) total_submit_number = models.IntegerField(default=0) total_accepted_number = models.IntegerField(default=0) @@ -53,14 +64,4 @@ class AbstractProblem(models.Model): class Problem(AbstractProblem): - difficulty = models.IntegerField() - tags = models.ManyToManyField(ProblemTag) - source = models.CharField(max_length=200, blank=True, null=True) - - -class TestCaseScore(models.Model): - test_case_id = models.CharField(max_length=32) - score = JSONField() - - class Meta: - db_table = "test_case_score" + pass diff --git a/problem/serializers.py b/problem/serializers.py index 68b209fe..ae4bc4d7 100644 --- a/problem/serializers.py +++ b/problem/serializers.py @@ -1,6 +1,67 @@ from django import forms +from judge.languages import language_names, spj_language_names +from utils.api import DateTimeTZField, UsernameSerializer, serializers + +from .models import Problem, ProblemRuleType, ProblemTag + class TestCaseUploadForm(forms.Form): spj = forms.CharField(max_length=12) file = forms.FileField() + + +class CreateSampleSerializer(serializers.Serializer): + input = serializers.CharField() + output = serializers.CharField() + + +class CreateTestCaseScoreSerializer(serializers.Serializer): + input_name = serializers.CharField(max_length=32) + score = serializers.IntegerField(min_value=0) + + +class Difficulty(object): + LOW = "Low" + MID = "Mid" + HIGH = "High" + + +class CreateProblemSerializer(serializers.Serializer): + title = serializers.CharField(max_length=128) + description = serializers.CharField() + input_description = serializers.CharField() + output_description = serializers.CharField() + samples = serializers.ListField(child=CreateSampleSerializer()) + test_case_id = serializers.CharField(max_length=32) + test_case_score = serializers.ListField(child=CreateTestCaseScoreSerializer()) + hint = serializers.CharField(allow_blank=True) + time_limit = serializers.IntegerField(min_value=1, max_value=1000 * 60) + memory_limit = serializers.IntegerField(min_value=1, max_value=1024) + languages = serializers.ListField(child=serializers.ChoiceField(choices=language_names)) + rule_type = serializers.ChoiceField(choices=[ProblemRuleType.ACM, ProblemRuleType.OI]) + spj = serializers.BooleanField() + spj_language = serializers.ChoiceField(choices=spj_language_names, allow_blank=True) + spj_code = serializers.CharField(allow_blank=True) + visible = serializers.BooleanField() + difficulty = serializers.ChoiceField(choices=[Difficulty.LOW, Difficulty.MID, Difficulty.HIGH]) + tags = serializers.ListField(child=serializers.CharField(max_length=32)) + source = serializers.CharField(max_length=256, allow_blank=True) + + +class TagSerializer(serializers.ModelSerializer): + class Meta: + model = ProblemTag + + +class ProblemSerializer(serializers.ModelSerializer): + samples = serializers.JSONField() + test_case_score = serializers.JSONField() + languages = serializers.JSONField() + tags = TagSerializer(many=True) + create_time = DateTimeTZField() + last_update_time = DateTimeTZField() + created_by = UsernameSerializer() + + class Meta: + model = Problem diff --git a/problem/urls/admin.py b/problem/urls/admin.py index 941dbedb..b9a4ec21 100644 --- a/problem/urls/admin.py +++ b/problem/urls/admin.py @@ -1,7 +1,8 @@ from django.conf.urls import url -from ..views.admin import TestCaseUploadAPI +from ..views.admin import ProblemAPI, TestCaseUploadAPI urlpatterns = [ - url(r"^test_case/upload$", TestCaseUploadAPI.as_view(), name="test_case_upload_api") + url(r"^test_case/upload$", TestCaseUploadAPI.as_view(), name="test_case_upload_api"), + url(r"^problem$", ProblemAPI.as_view(), name="problem_api") ] diff --git a/problem/urls/oj.py b/problem/urls/oj.py index d84d0241..d99155a6 100644 --- a/problem/urls/oj.py +++ b/problem/urls/oj.py @@ -3,5 +3,5 @@ from django.conf.urls import url from ..views.oj import ProblemTagAPI urlpatterns = [ - url(r"^tags$", ProblemTagAPI.as_view(), name="problem_tag_list_api") + url(r"^problem/tags$", ProblemTagAPI.as_view(), name="problem_tag_list_api") ] diff --git a/problem/views/admin.py b/problem/views/admin.py index 455210e1..c44d4ffb 100644 --- a/problem/views/admin.py +++ b/problem/views/admin.py @@ -6,10 +6,12 @@ import zipfile from django.conf import settings from account.decorators import admin_required -from utils.api import CSRFExemptAPIView +from utils.api import APIView, CSRFExemptAPIView, validate_serializer from utils.shortcuts import rand_str -from ..serializers import TestCaseUploadForm +from ..models import Problem, ProblemRuleType, ProblemTag +from ..serializers import (CreateProblemSerializer, ProblemSerializer, + TestCaseUploadForm) class TestCaseUploadAPI(CSRFExemptAPIView): @@ -103,3 +105,47 @@ class TestCaseUploadAPI(CSRFExemptAPIView): 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}) + + +class ProblemAPI(APIView): + @validate_serializer(CreateProblemSerializer) + def post(self, request): + data = request.data + if not data["languages"]: + return self.error("Invalid languages") + if not data["samples"]: + return self.error("Invalid samples") + if data["spj"]: + if not data["spj_language"] or not data["spj_code"]: + return self.error("Invalid spj") + data["spj_version"] = hashlib.md5((data["spj_language"] + ":" + data["spj_code"]).encode("utf-8")).hexdigest() + else: + data["spj_language"] = None + data["spj_code"] = None + if data["rule_type"] == ProblemRuleType.OI: + if not data["test_case_score"]: + return self.error("Test case score is required") + for item in data["test_case_score"]: + if item["score"] <= 0: + return self.error("Invalid score") + # todo check filename + else: + data["test_case_score"] = {} + data["created_by"] = request.user + tags = data.pop("tags") + if not tags: + return self.error("Tags is required") + problem = Problem.objects.create(**data) + for item in tags: + try: + tag = ProblemTag.objects.get(name=item) + except ProblemTag.DoesNotExist: + tag = ProblemTag.objects.create(name=item) + problem.tags.add(tag) + return self.success() + + def get(self, request): + problems = Problem.objects.all().order_by("-create_time") + if request.user.is_admin_role(): + problems = problems.filter(created_by=request.user) + return self.success(self.paginate_data(request, problems, ProblemSerializer)) diff --git a/utils/api/_serializers.py b/utils/api/_serializers.py index 0ffa767e..4cfe3f4c 100644 --- a/utils/api/_serializers.py +++ b/utils/api/_serializers.py @@ -1,14 +1,7 @@ -import json - from django.utils import timezone from rest_framework import serializers -class JSONField(serializers.Field): - def to_representation(self, value): - return json.loads(value) - - class DateTimeTZField(serializers.DateTimeField): def to_representation(self, value): self.format = "%Y-%-m-%d %H:%M:%S %Z" diff --git a/utils/api/api.py b/utils/api/api.py index 280ac68b..7400fde4 100644 --- a/utils/api/api.py +++ b/utils/api/api.py @@ -1,6 +1,7 @@ import functools import json import logging +from collections import OrderedDict from django.http import HttpResponse, QueryDict from django.utils.decorators import method_decorator @@ -80,12 +81,21 @@ class APIView(View): def error(self, msg, err="error"): return self.response({"error": err, "data": msg}) + def _serializer_error_to_str(self, errors): + for k, v in errors.items(): + if isinstance(v, list): + return k, v[0] + elif isinstance(v, OrderedDict): + for _k, _v in v.items(): + return self._serializer_error_to_str({_k: _v}) + def invalid_serializer(self, serializer): - for k, v in serializer.errors.items(): - if k != "non_field_errors": - return self.error(err="invalid-" + k, msg=k + ": " + v[0]) - else: - return self.error(err="invalid-field", msg=k[0]) + print(serializer.errors) + k, v = self._serializer_error_to_str(serializer.errors) + if k != "non_field_errors": + return self.error(err="invalid-" + k, msg=k + ": " + v) + else: + return self.error(err="invalid-field", msg=v) def server_error(self): return self.error(err="server-error", msg="server error")