初步完成编辑VJ题目的功能

This commit is contained in:
virusdefender 2016-03-10 23:34:06 +08:00
parent cf2fc9df1a
commit bbaebc4d70
14 changed files with 247 additions and 42 deletions

View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.1 on 2016-03-10 11:17
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contest', '0013_auto_20151017_1511'),
]
operations = [
migrations.AddField(
model_name='contestproblem',
name='vj_name',
field=models.CharField(blank=True, max_length=20, null=True),
),
migrations.AddField(
model_name='contestproblem',
name='vj_problem_url',
field=models.URLField(blank=True, null=True),
),
migrations.AlterField(
model_name='contestproblem',
name='test_case_id',
field=models.CharField(blank=True, max_length=40, null=True),
),
]

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.1 on 2016-03-10 12:09
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contest', '0014_auto_20160310_1917'),
]
operations = [
migrations.AddField(
model_name='contestproblem',
name='spj',
field=models.BooleanField(default=False),
),
]

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.1 on 2016-03-10 12:11
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contest', '0015_contestproblem_spj'),
]
operations = [
migrations.AddField(
model_name='contestproblem',
name='vj_problem_id',
field=models.CharField(blank=True, max_length=40, null=True),
),
]

View File

@ -67,6 +67,11 @@ class ContestProblem(AbstractProblem):
sort_index = models.CharField(max_length=30) sort_index = models.CharField(max_length=30)
# 是否已经公开了题目,防止重复公开 # 是否已经公开了题目,防止重复公开
is_public = models.BooleanField(default=False) is_public = models.BooleanField(default=False)
spj = models.BooleanField(default=False)
# 如果是vj题目才会使用
vj_name = models.CharField(max_length=20, blank=True, null=True)
vj_problem_id = models.CharField(max_length=40, blank=True, null=True)
vj_problem_url = models.URLField(blank=True, null=True)
class Meta: class Meta:
db_table = "contest_problem" db_table = "contest_problem"

View File

@ -105,9 +105,26 @@ class EditContestProblemSerializer(serializers.Serializer):
hint = serializers.CharField(max_length=3000, allow_blank=True) hint = serializers.CharField(max_length=3000, allow_blank=True)
visible = serializers.BooleanField() visible = serializers.BooleanField()
sort_index = serializers.CharField(max_length=30) sort_index = serializers.CharField(max_length=30)
score = serializers.IntegerField(required=False, default=0)
class ContestPasswordVerifySerializer(serializers.Serializer): class ContestPasswordVerifySerializer(serializers.Serializer):
contest_id = serializers.IntegerField() contest_id = serializers.IntegerField()
password = serializers.CharField(max_length=30) password = serializers.CharField(max_length=30)
class CreateContestVJProblemSerializer(serializers.Serializer):
contest_id = serializers.IntegerField()
oj = serializers.CharField(max_length=30)
url = serializers.URLField()
class EditContestVJProblemSerializer(serializers.Serializer):
id = serializers.IntegerField()
title = serializers.CharField(max_length=50)
description = serializers.CharField(max_length=10000)
input_description = serializers.CharField(max_length=10000)
output_description = serializers.CharField(max_length=10000)
samples = ContestProblemSampleSerializer()
hint = serializers.CharField(max_length=3000, allow_blank=True)
visible = serializers.BooleanField()
sort_index = serializers.CharField(max_length=30)

View File

@ -1,7 +1,8 @@
# coding=utf-8 # coding=utf-8
import json import json
import datetime import datetime
import redis import logging
import requests
from django.shortcuts import render from django.shortcuts import render
from django.db import IntegrityError from django.db import IntegrityError
@ -28,7 +29,11 @@ from .decorators import check_user_contest_permission
from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer, from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer,
CreateContestProblemSerializer, ContestProblemSerializer, CreateContestProblemSerializer, ContestProblemSerializer,
ContestPasswordVerifySerializer, ContestPasswordVerifySerializer,
EditContestProblemSerializer) EditContestProblemSerializer, CreateContestVJProblemSerializer,
EditContestVJProblemSerializer)
logger = logging.getLogger("app_info")
class ContestAdminAPIView(APIView): class ContestAdminAPIView(APIView):
@ -278,12 +283,98 @@ class ContestProblemAdminAPIView(APIView):
return paginate(request, contest_problems, ContestProblemSerializer) return paginate(request, contest_problems, ContestProblemSerializer)
class ContestVJProblemAPIView(APIView):
def post(self, request):
serializer = CreateContestVJProblemSerializer(data=request.data)
if serializer.is_valid():
data = serializer.data
try:
contest = Contest.objects.get(id=data["contest_id"])
if request.user.admin_type != SUPER_ADMIN:
contest_set = Contest.objects.filter(groups__in=request.user.managed_groups.all())
if contest not in contest_set:
return error_response(u"比赛不存在")
except Contest.DoesNotExist:
return error_response(u"比赛不存在")
url = "http://127.0.0.1:8000/problem/?oj=%s&url=%s" % (data["oj"], data["url"])
try:
r = requests.get(url).json()
except Exception as e:
logger.error("Exception %s when fetching url: %s" % (str(e), url))
return error_response(u"请求VJ API失败")
if r["code"] == 0:
# 爬取完成
if r["data"]["status"] == 0:
vj_problem = r["data"]
try:
ContestProblem.objects.get(contest=contest, vj_problem_id=vj_problem["id"])
return error_response(u"该VJ题目已经存在")
except ContestProblem.DoesNotExist:
pass
ContestProblem.objects.create(contest=contest,
title=vj_problem["title"],
description=vj_problem["description"],
input_description=vj_problem["input_description"],
output_description=vj_problem["output_description"],
samples=json.dumps(vj_problem["samples"]),
time_limit=vj_problem["time_limit"],
memory_limit=vj_problem["memory_limit"],
created_by=request.user,
is_public=True,
spj=vj_problem["spj"],
hint=vj_problem["hint"],
sort_index="_vj", vj_name=data["oj"],
vj_problem_id=vj_problem["id"],
vj_problem_url=data["url"])
return success_response(r)
# 正在爬取
elif r["data"]["status"] == 1:
return success_response({"status": 1})
# 失败
elif r["data"]["status"] == 2:
return error_response(u"VJ 题目爬取失败")
else:
logger.error("VJ API return %s" % json.dumps(r))
return error_response(u"请求VJ API失败, 返回信息: " + r["data"])
else:
return serializer_invalid_response(serializer)
def put(self, request):
serializer = EditContestVJProblemSerializer(data=request.data)
if serializer.is_valid():
data = serializer.data
try:
contest_problem = ContestProblem.objects.get(id=data["id"])
except ContestProblem.DoesNotExist:
return error_response(u"该比赛题目不存在!")
contest = Contest.objects.get(id=contest_problem.contest_id)
if request.user.admin_type != SUPER_ADMIN and contest.created_by != request.user:
return error_response(u"比赛不存在")
contest_problem.title = data["title"]
contest_problem.description = data["description"]
contest_problem.input_description = data["input_description"]
contest_problem.output_description = data["output_description"]
contest_problem.samples = json.dumps(data["samples"])
contest_problem.hint = data["hint"]
contest_problem.visible = data["visible"]
contest_problem.sort_index = data["sort_index"]
contest_problem.last_update_time = now()
contest_problem.save()
return success_response(ContestProblemSerializer(contest_problem).data)
else:
return serializer_invalid_response(serializer)
class MakeContestProblemPublicAPIView(APIView): class MakeContestProblemPublicAPIView(APIView):
@super_admin_required @super_admin_required
def post(self, request): def post(self, request):
problem_id = request.data.get("problem_id", -1) problem_id = request.data.get("problem_id", -1)
try: try:
problem = ContestProblem.objects.get(id=problem_id) problem = ContestProblem.objects.get(id=problem_id)
if problem.is_public:
return error_response(u"题目已经公开")
problem.is_public = True problem.is_public = True
problem.save() problem.save()
except ContestProblem.DoesNotExist: except ContestProblem.DoesNotExist:
@ -296,8 +387,6 @@ class MakeContestProblemPublicAPIView(APIView):
hint=problem.hint, created_by=problem.created_by, hint=problem.hint, created_by=problem.created_by,
time_limit=problem.time_limit, memory_limit=problem.memory_limit, time_limit=problem.time_limit, memory_limit=problem.memory_limit,
visible=False, difficulty=-1, source=problem.contest.title) visible=False, difficulty=-1, source=problem.contest.title)
problem.is_public = True
problem.save()
return success_response(u"创建成功") return success_response(u"创建成功")

View File

@ -13,7 +13,7 @@ from announcement.views import AnnouncementAdminAPIView
from contest.views import (ContestAdminAPIView, ContestProblemAdminAPIView, from contest.views import (ContestAdminAPIView, ContestProblemAdminAPIView,
ContestPasswordVerifyAPIView, ContestTimeAPIView, ContestPasswordVerifyAPIView, ContestTimeAPIView,
MakeContestProblemPublicAPIView) MakeContestProblemPublicAPIView, ContestVJProblemAPIView)
from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView, from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView,
JoinGroupAPIView, JoinGroupRequestAdminAPIView, GroupPrometAdminAPIView) JoinGroupAPIView, JoinGroupRequestAdminAPIView, GroupPrometAdminAPIView)
@ -65,6 +65,7 @@ urlpatterns = [
url(r'^api/admin/problem/$', ProblemAdminAPIView.as_view(), name="problem_admin_api"), url(r'^api/admin/problem/$', ProblemAdminAPIView.as_view(), name="problem_admin_api"),
url(r'^api/admin/contest_problem/$', ContestProblemAdminAPIView.as_view(), name="contest_problem_admin_api"), url(r'^api/admin/contest_problem/$', ContestProblemAdminAPIView.as_view(), name="contest_problem_admin_api"),
url(r'^api/admin/contest_vj_problem/$', ContestVJProblemAPIView.as_view(), name="contest_vj_problem_admin_api"),
url(r'^api/admin/contest_problem/public/', MakeContestProblemPublicAPIView.as_view(), url(r'^api/admin/contest_problem/public/', MakeContestProblemPublicAPIView.as_view(),
name="make_contest_problem_public"), name="make_contest_problem_public"),
url(r'^api/admin/test_case_upload/$', TestCaseUploadAPIView.as_view(), name="test_case_upload_api"), url(r'^api/admin/test_case_upload/$', TestCaseUploadAPIView.as_view(), name="test_case_upload_api"),

View File

@ -24,7 +24,7 @@ class AbstractProblem(models.Model):
# 样例输入 可能会存储 json 格式的数据 # 样例输入 可能会存储 json 格式的数据
samples = models.TextField(blank=True) samples = models.TextField(blank=True)
# 测试用例id 这个id 可以用来拼接得到测试用例的文件存储位置 # 测试用例id 这个id 可以用来拼接得到测试用例的文件存储位置
test_case_id = models.CharField(max_length=40) test_case_id = models.CharField(max_length=40, blank=True, null=True)
# 提示 # 提示
hint = RichTextField(blank=True, null=True) hint = RichTextField(blank=True, null=True)
# 创建时间 # 创建时间

View File

@ -25,21 +25,23 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
}, },
makeProblemPublic: function(problem){ makeProblemPublic: function(problem){
$.ajax({ if(confirm("您确定要公开题目么? 请勿在比赛未结束的时候公开题目。")) {
url: "/api/admin/contest_problem/public/", $.ajax({
method: "post", url: "/api/admin/contest_problem/public/",
dataType: "json", method: "post",
data: {"problem_id": problem.id}, dataType: "json",
success: function(response){ data: {"problem_id": problem.id},
if(response.code){ success: function (response) {
bsAlert(response.data); if (response.code) {
bsAlert(response.data);
}
else {
problem.is_public = true;
bsAlert("公开题目成功,现在处于隐藏状态,请添加标签难度等信息。");
}
} }
else{ })
problem.is_public = true; }
alert("公开题目成功,现在处于隐藏状态,请添加标签难度等信息。");
}
}
})
} }
}); });
} }

View File

@ -107,10 +107,9 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
if (!data.code) { if (!data.code) {
if (!data.data.length) { if (!data.data.length && admin_type != 2) {
if (admin_type != 2)
bsAlert("您的用户权限只能创建小组内比赛,但是您还没有创建过小组"); bsAlert("您的用户权限只能创建小组内比赛,但是您还没有创建过小组");
return; return;
} }
vm.allGroups = []; vm.allGroups = [];
for (var i = 0; i < data.data.length; i++) { for (var i = 0; i < data.data.length; i++) {

View File

@ -8,18 +8,25 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert",
.on('submit', function (e) { .on('submit', function (e) {
if (!e.isDefaultPrevented()) { if (!e.isDefaultPrevented()) {
e.preventDefault(); e.preventDefault();
if (!avalon.vmodels.testCaseUploader.uploaded) { if(!vm.isVJ) {
bsAlert("你还没有上传测试数据!"); if (!avalon.vmodels.testCaseUploader.uploaded) {
return false; bsAlert("你还没有上传测试数据!");
return false;
}
if (vm.timeLimit < 30 || vm.timeLimit > 5000) {
bsAlert("保证时间限制是一个30-5000的合法整数");
return false;
}
if (vm.memoryLimit < 32) {
bsAlert("内存不得小于32M");
return false;
}
} }
if (avalon.vmodels.contestProblemDescriptionEditor.content == "") { if (avalon.vmodels.contestProblemDescriptionEditor.content == "") {
bsAlert("题目描述不能为空!"); bsAlert("题目描述不能为空!");
return false; return false;
} }
if (vm.timeLimit < 30 || vm.timeLimit > 5000) {
bsAlert("保证时间限制是一个30-5000的合法整数");
return false;
}
if (vm.samples.length == 0) { if (vm.samples.length == 0) {
bsAlert("请至少添加一组样例!"); bsAlert("请至少添加一组样例!");
return false; return false;
@ -55,6 +62,13 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert",
var alertContent = "题目创建成功"; var alertContent = "题目创建成功";
} }
if(vm.isVJ) {
var url = "/api/admin/contest_vj_problem/";
}
else {
var url = "/api/admin/contest_problem/";
}
for (var i = 0; i < vm.samples.$model.length; i++) { for (var i = 0; i < vm.samples.$model.length; i++) {
ajaxData.samples.push({ ajaxData.samples.push({
input: vm.samples.$model[i].input, input: vm.samples.$model[i].input,
@ -64,7 +78,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert",
$.ajax({ $.ajax({
beforeSend: csrfTokenHeader, beforeSend: csrfTokenHeader,
url: "/api/admin/contest_problem/", url: url,
dataType: "json", dataType: "json",
data: JSON.stringify(ajaxData), data: JSON.stringify(ajaxData),
method: method, method: method,
@ -99,6 +113,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert",
outputDescription: "", outputDescription: "",
testCaseId: "", testCaseId: "",
testCaseList: [], testCaseList: [],
isVJ: false,
contestProblemDescriptionEditor: { contestProblemDescriptionEditor: {
editorId: "contest-problem-description-editor", editorId: "contest-problem-description-editor",
@ -147,6 +162,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert",
vm.outputDescription = ""; vm.outputDescription = "";
vm.testCaseId = ""; vm.testCaseId = "";
vm.testCaseList = []; vm.testCaseList = [];
vm.isVJ = false;
} }
if (avalon.vmodels.admin.contestProblemStatus == "edit") { if (avalon.vmodels.admin.contestProblemStatus == "edit") {
@ -170,8 +186,11 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert",
vm.visible = problem.visible; vm.visible = problem.visible;
vm.inputDescription = problem.input_description; vm.inputDescription = problem.input_description;
vm.outputDescription = problem.output_description; vm.outputDescription = problem.output_description;
vm.score = problem.score; vm.isVJ = problem.test_case_id == null;
avalon.vmodels.testCaseUploader.setTestCase(problem.test_case_id); // vj题目不需要上传数据
if (!vm.isVJ) {
avalon.vmodels.testCaseUploader.setTestCase(problem.test_case_id);
}
vm.samples = []; vm.samples = [];
for (var i = 0; i < problem.samples.length; i++) { for (var i = 0; i < problem.samples.length; i++) {
vm.samples.push({ vm.samples.push({

View File

@ -29,14 +29,15 @@
<div class="col-md-3"> <div class="col-md-3">
<div class="form-group"><label>时间限制(ms)</label> <div class="form-group"><label>时间限制(ms)</label>
<input type="number" name="timeLimit" class="form-control" ms-duplex="timeLimit" <input type="number" name="timeLimit" class="form-control" ms-duplex="timeLimit"
data-error="请输入时间限制(保证是一个30-5000的合法整数)" required> data-error="请输入时间限制(保证是一个30-5000的合法整数)" required ms-attr-disabled="isVJ">
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<div class="form-group"><label>内存限制(MB)</label> <div class="form-group"><label>内存限制(MB)</label>
<input type="number" name="memory" class="form-control" ms-duplex="memoryLimit" <input type="number" name="memory" class="form-control"
data-error="请输入内存限制(保证是一个合法整数)" required> title="非VJ的题目内存低于512M可能会造成Java代码无法启动" ms-duplex="memoryLimit"
data-error="请输入内存限制(保证是一个合法整数)" required ms-attr-disabled="isVJ">
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
</div> </div>
@ -96,7 +97,9 @@
</div> </div>
</div> </div>
</div> </div>
<ms:testcaseuploader $id="testCaseUploader"></ms:testcaseuploader> <div ms-if="!isVJ">
<ms:testcaseuploader $id="testCaseUploader"></ms:testcaseuploader>
</div>
<div class="form-group col-md-12"> <div class="form-group col-md-12">
<label>提示</label> <label>提示</label>
<ms:editor $id="contestProblemHintEditor" config="contestProblemHintEditor"></ms:editor> <ms:editor $id="contestProblemHintEditor" config="contestProblemHintEditor"></ms:editor>

View File

@ -20,7 +20,7 @@
<td></td> <td></td>
</tr> </tr>
<tr ms-repeat="problemList"> <tr ms-repeat="problemList">
<td>{{ el.sort_index }}</td> <td><span ms-if="el.vj_name" class="glyphicon glyphicon-send" title="Virtual Judge"></span> {{ el.sort_index }}</td>
<td>{{ el.title }}</td> <td>{{ el.title }}</td>
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td> <td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td ms-text="el.visible?'可见':'不可见'"></td> <td ms-text="el.visible?'可见':'不可见'"></td>

View File

@ -6,12 +6,12 @@
<div class="problem-section"> <div class="problem-section">
<label class="problem-label">输入</label> <label class="problem-label">输入</label>
<p class="problem-detail">{{ problem.input_description }}</p> <p class="problem-detail">{{ problem.input_description|linebreaksbr }}</p>
</div> </div>
<div class="problem-section"> <div class="problem-section">
<label class="problem-label">输出</label> <label class="problem-label">输出</label>
<p class="problem-detail">{{ problem.output_description }}</p> <p class="problem-detail">{{ problem.output_description|linebreaksbr }}</p>
</div> </div>
{% for item in samples %} {% for item in samples %}
<div class="problem-section"> <div class="problem-section">