初步完成编辑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)
# 是否已经公开了题目,防止重复公开
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:
db_table = "contest_problem"

View File

@ -105,9 +105,26 @@ class EditContestProblemSerializer(serializers.Serializer):
hint = serializers.CharField(max_length=3000, allow_blank=True)
visible = serializers.BooleanField()
sort_index = serializers.CharField(max_length=30)
score = serializers.IntegerField(required=False, default=0)
class ContestPasswordVerifySerializer(serializers.Serializer):
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
import json
import datetime
import redis
import logging
import requests
from django.shortcuts import render
from django.db import IntegrityError
@ -28,7 +29,11 @@ from .decorators import check_user_contest_permission
from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer,
CreateContestProblemSerializer, ContestProblemSerializer,
ContestPasswordVerifySerializer,
EditContestProblemSerializer)
EditContestProblemSerializer, CreateContestVJProblemSerializer,
EditContestVJProblemSerializer)
logger = logging.getLogger("app_info")
class ContestAdminAPIView(APIView):
@ -278,12 +283,98 @@ class ContestProblemAdminAPIView(APIView):
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):
@super_admin_required
def post(self, request):
problem_id = request.data.get("problem_id", -1)
try:
problem = ContestProblem.objects.get(id=problem_id)
if problem.is_public:
return error_response(u"题目已经公开")
problem.is_public = True
problem.save()
except ContestProblem.DoesNotExist:
@ -296,8 +387,6 @@ class MakeContestProblemPublicAPIView(APIView):
hint=problem.hint, created_by=problem.created_by,
time_limit=problem.time_limit, memory_limit=problem.memory_limit,
visible=False, difficulty=-1, source=problem.contest.title)
problem.is_public = True
problem.save()
return success_response(u"创建成功")

View File

@ -13,7 +13,7 @@ from announcement.views import AnnouncementAdminAPIView
from contest.views import (ContestAdminAPIView, ContestProblemAdminAPIView,
ContestPasswordVerifyAPIView, ContestTimeAPIView,
MakeContestProblemPublicAPIView)
MakeContestProblemPublicAPIView, ContestVJProblemAPIView)
from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView,
JoinGroupAPIView, JoinGroupRequestAdminAPIView, GroupPrometAdminAPIView)
@ -65,6 +65,7 @@ urlpatterns = [
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_vj_problem/$', ContestVJProblemAPIView.as_view(), name="contest_vj_problem_admin_api"),
url(r'^api/admin/contest_problem/public/', MakeContestProblemPublicAPIView.as_view(),
name="make_contest_problem_public"),
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 格式的数据
samples = models.TextField(blank=True)
# 测试用例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)
# 创建时间

View File

@ -25,21 +25,23 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
},
makeProblemPublic: function(problem){
$.ajax({
url: "/api/admin/contest_problem/public/",
method: "post",
dataType: "json",
data: {"problem_id": problem.id},
success: function(response){
if(response.code){
bsAlert(response.data);
if(confirm("您确定要公开题目么? 请勿在比赛未结束的时候公开题目。")) {
$.ajax({
url: "/api/admin/contest_problem/public/",
method: "post",
dataType: "json",
data: {"problem_id": problem.id},
success: function (response) {
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",
success: function (data) {
if (!data.code) {
if (!data.data.length) {
if (admin_type != 2)
if (!data.data.length && admin_type != 2) {
bsAlert("您的用户权限只能创建小组内比赛,但是您还没有创建过小组");
return;
return;
}
vm.allGroups = [];
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) {
if (!e.isDefaultPrevented()) {
e.preventDefault();
if (!avalon.vmodels.testCaseUploader.uploaded) {
bsAlert("你还没有上传测试数据!");
return false;
if(!vm.isVJ) {
if (!avalon.vmodels.testCaseUploader.uploaded) {
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 == "") {
bsAlert("题目描述不能为空!");
return false;
}
if (vm.timeLimit < 30 || vm.timeLimit > 5000) {
bsAlert("保证时间限制是一个30-5000的合法整数");
return false;
}
if (vm.samples.length == 0) {
bsAlert("请至少添加一组样例!");
return false;
@ -55,6 +62,13 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert",
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++) {
ajaxData.samples.push({
input: vm.samples.$model[i].input,
@ -64,7 +78,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert",
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/admin/contest_problem/",
url: url,
dataType: "json",
data: JSON.stringify(ajaxData),
method: method,
@ -99,6 +113,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert",
outputDescription: "",
testCaseId: "",
testCaseList: [],
isVJ: false,
contestProblemDescriptionEditor: {
editorId: "contest-problem-description-editor",
@ -147,6 +162,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert",
vm.outputDescription = "";
vm.testCaseId = "";
vm.testCaseList = [];
vm.isVJ = false;
}
if (avalon.vmodels.admin.contestProblemStatus == "edit") {
@ -170,8 +186,11 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert",
vm.visible = problem.visible;
vm.inputDescription = problem.input_description;
vm.outputDescription = problem.output_description;
vm.score = problem.score;
avalon.vmodels.testCaseUploader.setTestCase(problem.test_case_id);
vm.isVJ = problem.test_case_id == null;
// vj题目不需要上传数据
if (!vm.isVJ) {
avalon.vmodels.testCaseUploader.setTestCase(problem.test_case_id);
}
vm.samples = [];
for (var i = 0; i < problem.samples.length; i++) {
vm.samples.push({

View File

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

View File

@ -20,7 +20,7 @@
<td></td>
</tr>
<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.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td ms-text="el.visible?'可见':'不可见'"></td>

View File

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