mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2024-09-21 00:13:18 +00:00
Merge branch 'dev' into virusdefender-dev
* dev: ¢ûÂ䆉∫ÜÊØî˵õÂàóË°®È°µ [ÂêéÁ´Ø]ÂéªÊéâ‰∫ÜÁ∫éÁîü‰∫ßÂ∫èÂè∑ÁöÑjavascript,Êîπ‰∏∫‰ΩøÁî®Ê®°ÊùøËøáʪ§Âô®ÂÆûÁé∞(ÊàëÁöÑÊâÄÊúâÊèê‰∫§) [ÂâçÁ´Ø]‰øÆÊîπÂÆåÂñщ∫ÜÊ∑ªÂä†ÊØî˵õÈ°µÈù¢, ÊØî˵õÂàóË°®ÂäüËÉΩ‰ªç‰∏çÂÖ®Èù¢,Á®çÂêéÊîπËøõ[CI SKIP] ‰øÆÊîπ‰∫ÜcssºïÁî®Êñπºè[CI SKIP] [ÂêéÁ´Ø]‰øÆÊîπ‰∫Ücontest‰∏≠ api-docs ÁöÑÂ∞èbug[CI SKIP] Âàõª∫ÂâçÂè∞ÊØî˵õÂàóË°® ÂéªÊéâÂÜó‰ΩôËØ≠Âè•,Âõ†‰∏∫pageÂèòÈáèÂ∑≤ÁªèÊúâȪòËƧÂĺ‰∫Ü,‰∏çËÉΩ‰∏∫Á©∫ [ÂêéÁ´Ø]ÂâçÂè∞ÊàëÁöÑÊèê‰∫§È°µÈù¢ ¢ûº∫Êèê‰∫§Â∫èÂè∑ÁöÑÊòæÁ§∫,ÂéüÊù•ÊòØÊòæÁ§∫ÁúüÂÆûidÂç≥ÈöèÊú∫ÁöÑÊï£ÂàóÂĺ,‰∏ç•ΩÁúã,Áé∞Âú®ÊîπÊàêËá™ÁÑ∂Êï∞Â∫èÂàó,‰ΩÜÈúÄ˶ÅÁªìÂêàjavascriptÁîüÊàê,‰∏îÊòØÁõ∏ÂØπÂĺ,Âõ†‰∏∫Êï∞ÊçÆÂ∫ìÈáåÊ≤°ÊúâËøô‰∏™Â≠óÊƵ,ÊúâÁÇπÂà´Êâ≠‰∫Ü. Á¨¨‰∫å,Ê∑ªÂ䆉∫ÜÁî®Êà∑Ê≤°ÊúâÊèê‰∫§ËÆ∞ÂΩïÁöÑÂèçȶà. Á¨¨‰∏â,Êú¨ÊâìÁÆó¢ûÂä†Á≠õÈÄâÂäüËÉΩ,‰ΩÜÂõ†‰∏∫URLÈö扪•Áªü‰∏ĉΩúÁΩ¢,Âè™ÊúâÂú®Â¢ûÂä†Êñ∞ÁöÑurlÊâçËÉΩËæÉ•ΩÁöѧÑÁêÜ,‰∏ãʨ°ÂÜçËØ¥Êãú [ÂâçÁ´Ø]ÊØî˵õÂàóË°®È°µÈù¢(ÂêéÂè∞)ÁöÑËøõ‰∏ÄÊ≠•ÂÆåÂñÑ,‰∏çÂåÖÂê´api [ÂêéÁ´Ø]‰øÆÊîπ‰∫ÜÊàëÁöÑÊèê‰∫§ÂàóË°®ÁöÑÊ®°Êùøʆ∑ºè,Êï¥ÁêÜʆºÂºè [ÂêéÁ´Ø]‰øÆÊîπÊàëÁöÑÊèê‰∫§È°µÈù¢,ÂéªÊéâ‰∫ÜÂÜó‰ΩôËØ≠Âè•,Âπ∂Ê∑ªÂä†ÊµãËØï [ÂêéÁ´Ø-ÂâçÂè∞]Ê∑ªÂ䆉∫ÜsubmissionsÂàÜÈ°µÊòæÁ§∫(Âè™ÊòæÁ§∫ÂΩìÂâçÁî®Êà∑ÁöÑÊèê‰∫§),Ë∞ÉÁî®Â∑≤ÊúâÁöÑviewÂÆåÊàêÂçï‰∏™submissionÁöÑÊòæÁ§∫.ÊòæÁ§∫ÁïåÈù¢‰∏éÈóÆÈ¢òÂàÜÈ°µÊòæÁ§∫Áªü‰∏Ä.ÈóÆÈ¢òÊòØidÁöÑÊòæÁ§∫.url:http://127.0.0.1:8000/my_submissions/ [ÂâçÁ´Ø]‰øÆÊîπÊ∑ªÂä†ÊØî˵õÈ°µÈù¢,Êñ∞¢û‰∫܉ΩøÁî®Â∞èÁªÑapiÊü•ËØ¢ËØ•Áî®Êà∑ÊâÄÂàõª∫ÁöÑÊâÄÊúâÁöÑÂ∞èÁªÑÁöÑÂäüËÉΩ[CI SKIP] [ÂâçÁ´Ø]Ê∑ªÂä†ÊØî˵õÈ°µÈù¢Ëøõ‰∏ÄÊ≠•ÂÆåÂñÑ,Ê∑ªÂä†Â≠óÊƵÂåÖÊã¨ÊòØÂê¶ÊòæÁ§∫Êèê‰∫§,ÊØî˵õÊ®°Âºè,ÈóÆÈ¢òÂàÜÂĺ,ÂÖÅËÆ∏ÂèÇÂä†ÊØî˵õÁöÑÁî®Êà∑ÁªÑ,Âπ∂ÂÆåÂñÑÂÜÖÈÉ®ÈĪËæë,Âü∫Êú¨ÂèØÁ∫Ü,Âè™ÊòØÊ≤°ÂÜôajaxÊèê‰∫§Êï∞ÊçÆ,ÂíåÂïÊãâÂÂèñÂ∞èÁªÑ‰ø°ÊÅØÁöÑÈÉ®ÂàÜ[CI SKIP] [ÂâçÁ´Ø]‰øÆÊîπ‰∫ÜÊ∑ªÂä†ÊØî˵õÈ°µÁöÑÂΩ¢ÂºèÁªìÊûÑ,‰ªçÊúâbug[CI SKIP] Ê∑[ÂâçÁ´Ø]Ê∑ªÂä†ÊØîËÂêéÂè∞ÊØî˵õÂàóË°®[CI SKIP] [ÂâçÁ´Ø]Áªü‰∏ÄÈóÆÈ¢òÈá,ÊØî˵õÂàóË°®jsÁöÑÊñቪ∂Âêç_list.js. Ê∑ªÂ䆉∫ÜÊØî˵õÂàóË°®ÂíåÁºñËæëÊØî˵õÁöÑÈ°µÈù¢(§߉Ωì§ʆ∑ºè)[CI SKIP] Conflicts: contest/views.py
This commit is contained in:
commit
9977e156b1
@ -1,9 +1,12 @@
|
||||
# coding=utf-8
|
||||
import json
|
||||
import datetime
|
||||
from django.utils.timezone import localtime
|
||||
from django.shortcuts import render
|
||||
from django.db import IntegrityError
|
||||
from django.utils import dateparse
|
||||
from django.db.models import Q
|
||||
from django.core.paginator import Paginator
|
||||
from rest_framework.views import APIView
|
||||
from utils.shortcuts import (serializer_invalid_response, error_response,
|
||||
success_response, paginate, rand_str, error_page)
|
||||
@ -11,11 +14,13 @@ from utils.shortcuts import (serializer_invalid_response, error_response,
|
||||
from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN
|
||||
from account.decorators import login_required
|
||||
from group.models import Group
|
||||
from announcement.models import Announcement
|
||||
|
||||
from .models import Contest, ContestProblem
|
||||
from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer,
|
||||
CreateContestProblemSerializer, ContestProblemSerializer,
|
||||
EditContestProblemSerializer, ContestPasswordVerifySerializer)
|
||||
EditContestProblemSerializer, ContestPasswordVerifySerializer,
|
||||
EditContestProblemSerializer)
|
||||
|
||||
|
||||
class ContestAdminAPIView(APIView):
|
||||
@ -200,7 +205,7 @@ class ContestProblemAdminAPIView(APIView):
|
||||
"""
|
||||
比赛题目分页json api接口
|
||||
---
|
||||
response_serializer: ProblemSerializer
|
||||
response_serializer: ContestProblemSerializer
|
||||
"""
|
||||
contest_problem_id = request.GET.get("contest_problem_id", None)
|
||||
if contest_problem_id:
|
||||
@ -274,4 +279,47 @@ def contest_page(request, contest_id):
|
||||
if not result["result"]:
|
||||
return render(request, "oj/contest/contest_no_privilege.html", {"contenst": contest, "reason": result["reason"]})
|
||||
|
||||
return render(request, "oj/contest/contest_index.html", {"contest": contest})
|
||||
return render(request, "oj/contest/contest_index.html", {"contest": contest})
|
||||
|
||||
|
||||
def contest_list_page(request, page=1):
|
||||
# 正常情况
|
||||
contests = Contest.objects.filter(visible=True)
|
||||
|
||||
# 搜索的情况
|
||||
keyword = request.GET.get("keyword", None)
|
||||
if keyword:
|
||||
contests = contests.filter(title__contains=keyword)
|
||||
|
||||
# 筛选我能参加的比赛
|
||||
join = request.GET.get("join", None)
|
||||
if join:
|
||||
contests = Contest.objects.filter(Q(contest_type__in=[1, 2]) | Q(groups__in=request.user.group_set.all()))
|
||||
|
||||
paginator = Paginator(contests, 20)
|
||||
try:
|
||||
current_page = paginator.page(int(page))
|
||||
except Exception:
|
||||
return error_page(request, u"不存在的页码")
|
||||
|
||||
previous_page = next_page = None
|
||||
|
||||
try:
|
||||
previous_page = current_page.previous_page_number()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
next_page = current_page.next_page_number()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 右侧的公告列表
|
||||
announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
|
||||
# 系统当前时间
|
||||
now = datetime.datetime.now()
|
||||
return render(request, "oj/contest/contest_list.html",
|
||||
{"contests": current_page, "page": int(page),
|
||||
"previous_page": previous_page, "next_page": next_page,
|
||||
"keyword": keyword, "announcements": announcements,
|
||||
"join": join, "now": now})
|
@ -40,12 +40,15 @@ urlpatterns = [
|
||||
url(r'^api/admin/contest/$', ContestAdminAPIView.as_view(), name="contest_admin_api"),
|
||||
url(r'^api/admin/user/$', UserAdminAPIView.as_view(), name="user_admin_api"),
|
||||
url(r'^problem/(?P<problem_id>\d+)/$', "problem.views.problem_page", name="problem_page"),
|
||||
url(r'^contest/(?P<contest_id>\d+)/$', "contest.views.contest_page", name="contest_page"),
|
||||
url(r'^announcement/(?P<announcement_id>\d+)/$', "announcement.views.announcement_page",
|
||||
name="announcement_page"),
|
||||
url(r'^admin/contest/$', TemplateView.as_view(template_name="admin/contest/add_contest.html"),
|
||||
name="add_contest_page"),
|
||||
url(r'^problems/$', "problem.views.problem_list_page", name="problem_list_page"),
|
||||
url(r'^problems/(?P<page>\d+)/$', "problem.views.problem_list_page", name="problem_list_page"),
|
||||
url(r'^contests/$', "contest.views.contest_list_page", name="contest_list_page"),
|
||||
url(r'^contests/(?P<page>\d+)/$', "contest.views.contest_list_page", name="contest_list_page"),
|
||||
url(r'^admin/template/(?P<template_dir>\w+)/(?P<template_name>\w+).html$', AdminTemplateView.as_view(),
|
||||
name="admin_template"),
|
||||
url(r'^api/admin/group/$', GroupAdminAPIView.as_view(), name="group_admin_api"),
|
||||
@ -63,6 +66,8 @@ urlpatterns = [
|
||||
name="join_group_request_admin_api"),
|
||||
url(r'^api/submission/$', SubmissionAPIView.as_view(), name="submission_api"),
|
||||
url(r'^api/admin/submission/$', SubmissionAdminAPIView.as_view(), name="submission_admin_api_view"),
|
||||
url(r'^my_submissions/$', "submission.views.my_submission_list_page", name="my_submission_list_page"),
|
||||
url(r'^my_submissions/(?P<page>\d+)/$', "submission.views.my_submission_list_page", name="my_submission_list_page"),
|
||||
url(r'^api/admin/monitor/$', QueueLengthMonitorAPIView.as_view(), name="queue_length_monitor_api"),
|
||||
url(r'^contest/(?P<contest_id>\d+)/$', "contest.views.contest_page", name="contest_page"),
|
||||
url(r'^api/contest/password/$', ContestPasswordVerifyAPIView.as_view(), name="contest_password_verify_api"),
|
||||
|
5
static/src/css/add_contest.css
Normal file
5
static/src/css/add_contest.css
Normal file
@ -0,0 +1,5 @@
|
||||
.group-tag {
|
||||
padding-left: 5px; color: #46799b; background: #e0eaf1; white-space: nowrap;
|
||||
overflow: hidden; cursor: pointer; border-radius: 2px 0 0 2px;
|
||||
float: left; padding: 0 4px;box-sizing: border-box;list-style-type: none; margin: 5px;
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
@import url("webuploader/webuploader.css");
|
||||
@import url("datetime_picker/bootstrap-datetimepicker.css");
|
||||
@import url("tagEditor/jquery.tag-editor.css");
|
||||
@import url("add_contest.css");
|
||||
|
||||
#loading-gif{
|
||||
width: 40px;
|
||||
|
231
static/src/js/app/admin/contest/add_contest.js
Normal file
231
static/src/js/app/admin/contest/add_contest.js
Normal file
@ -0,0 +1,231 @@
|
||||
require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "datetimePicker",
|
||||
"validator"],
|
||||
function ($, avalon, editor, uploader, bsAlert, csrfTokenHeader) {
|
||||
avalon.vmodels.add_contest = null;
|
||||
$("#add-contest-form").validator().on('submit', function (e) {
|
||||
if (!e.isDefaultPrevented()){
|
||||
e.preventDefault();
|
||||
var ajaxData = {
|
||||
title: vm.title,
|
||||
description: vm.description,
|
||||
mode: vm.mode,
|
||||
contest_type: 0,
|
||||
show_rank: vm.showRank,
|
||||
show_user_submission: vm.showSubmission,
|
||||
//password: vm.password,
|
||||
start_time: vm.startTime,
|
||||
end_time: vm.endTime,
|
||||
visible: true
|
||||
};
|
||||
if (vm.choseGroupList[0].id == 0) //everyone | public contest
|
||||
if (vm.password == "")
|
||||
ajaxData.contest_type = 1;
|
||||
else{
|
||||
ajaxData.password = vm.password;
|
||||
}
|
||||
else { // Add groups info
|
||||
ajaxData.groups = [];
|
||||
for (var i = 0; vm.choseGroupList[i]; i++)
|
||||
ajaxData.groups.push(parseInt(vm.choseGroupList[i].id))
|
||||
}
|
||||
|
||||
|
||||
console.log(ajaxData);
|
||||
$.ajax({
|
||||
beforeSend: csrfTokenHeader,
|
||||
url: "/api/admin/contest/",
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(ajaxData),
|
||||
method: "post",
|
||||
contentType: "application/json",
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
bsAlert("添加成功!");
|
||||
console.log(data);
|
||||
}
|
||||
else {
|
||||
bsAlert(data.data);
|
||||
console.log(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log(JSON.stringify(ajaxData));
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
editor("#editor");
|
||||
editor("#problemDescriptionEditor");
|
||||
editor("#problemHintEditor");
|
||||
|
||||
var vm = avalon.define({
|
||||
$id: "add_contest",
|
||||
title: "",
|
||||
description: "",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
password: "",
|
||||
mode: "",
|
||||
showRank: false,
|
||||
showSubmission: false,
|
||||
problems: [],
|
||||
editingProblemId: 0,
|
||||
editSamples: [],
|
||||
editTestCaseList: [],
|
||||
group: "-1",
|
||||
groupList: [],
|
||||
choseGroupList: [],
|
||||
showProblemEditArea: function (problemIndex) {
|
||||
if (vm.editingProblemId == problemIndex){
|
||||
vm.problems[vm.editingProblemId-1].samples = vm.editSamples;
|
||||
vm.editingProblemId = 0;
|
||||
}
|
||||
else {
|
||||
if (vm.editingProblemId)
|
||||
{
|
||||
vm.problems[vm.editingProblemId-1].samples = vm.editSamples;
|
||||
vm.problems[vm.editingProblemId-1].testCaseList = vm.editTestCaseList;
|
||||
}
|
||||
vm.editingProblemId = problemIndex;
|
||||
vm.editSamples = [];
|
||||
vm.editSamples = vm.problems[vm.editingProblemId-1].samples;
|
||||
vm.editTestCaseList = [];
|
||||
vm.editTestCaseList = vm.problems[vm.editingProblemId-1].testCaseList;
|
||||
}
|
||||
},
|
||||
passwordUsable: false,
|
||||
add_problem: function () {
|
||||
var problem = {
|
||||
title: "",
|
||||
timeLimit: 1000,
|
||||
memoryLimit: 256,
|
||||
description: "",
|
||||
samples: [],
|
||||
visible: true,
|
||||
test_case_id: "",
|
||||
testCaseList: [],
|
||||
hint: "",
|
||||
score: 0,
|
||||
uploadSuccess: false,
|
||||
};
|
||||
vm.problems.push(problem);
|
||||
vm.showProblemEditArea(vm.problems.length);
|
||||
},
|
||||
del_problem: function (problemIndex) {
|
||||
if (confirm("你确定要删除么?")) {
|
||||
vm.editingProblemId = 0;
|
||||
vm.problems.remove(vm.problems[problemIndex-1]);
|
||||
}
|
||||
},
|
||||
hidden: function () {
|
||||
vm.problems[vm.editingProblemId-1].samples = editSamples;
|
||||
vm.problems[vm.editingProblemId-1].testCaseList = editTestCaseList;
|
||||
vm.editingProblemId = 0;
|
||||
},
|
||||
toggle: function (item) {
|
||||
item.visible = !item.visible;
|
||||
},
|
||||
add_sample: function () {
|
||||
vm.editSamples.push({visible: true, input: "", output: ""});
|
||||
},
|
||||
del_sample: function (sample) {
|
||||
if (confirm("你确定要删除么?")) {
|
||||
editSamples.remove(sample);
|
||||
}
|
||||
},
|
||||
getBtnContent: function (item) {
|
||||
if (item.visible)
|
||||
return "折叠";
|
||||
return "展开";
|
||||
},
|
||||
addGroup: function() {
|
||||
if (vm.group == -1) return;
|
||||
if (vm.groupList[vm.group].id == 0){
|
||||
vm.passwordUsable = true;
|
||||
vm.choseGroupList = [];
|
||||
for (var key in vm.groupList){
|
||||
vm.groupList[key].chose = true;
|
||||
}
|
||||
}
|
||||
vm.groupList[vm.group]. chose = true;
|
||||
vm.choseGroupList.push({name:vm.groupList[vm.group].name, index:vm.group, id:vm.groupList[vm.group].id});
|
||||
},
|
||||
unchose: function(groupIndex){
|
||||
if (vm.groupList[vm.choseGroupList[groupIndex].index].id == 0){
|
||||
vm.passwordUsable = false;
|
||||
for (key in vm.groupList){
|
||||
vm.groupList[key].chose = false;
|
||||
}
|
||||
}
|
||||
vm.groupList[vm.choseGroupList[groupIndex].index].chose = false;
|
||||
vm.choseGroupList.remove(vm.choseGroupList[groupIndex]);
|
||||
}
|
||||
});
|
||||
|
||||
var isSuperAdmin = true;
|
||||
$.ajax({ //用于获取该用户创建的所有小组的ajax请求
|
||||
beforeSend: csrfTokenHeader,
|
||||
url: "/api/admin/group/?my_group=true",
|
||||
dataType: "json",
|
||||
method: "get",
|
||||
contentType: "application/json",
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
if (isSuperAdmin)
|
||||
vm.groupList.push({id:0, name:"所有人", chose: false});
|
||||
for (var key in data.data) {
|
||||
data.data[key].chose = false;
|
||||
vm.groupList.push(data.data[key]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bsAlert(data.data);
|
||||
console.log(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
uploader("#uploader", "/api/admin/test_case_upload/", function (file, respond) {
|
||||
if (respond.code)
|
||||
bsAlert(respond.data);
|
||||
else {
|
||||
vm.problems[vm.editingProblemId-1].test_case_id = respond.data.test_case_id;
|
||||
vm.problems[vm.editingProblemId-1].uploadSuccess = true;
|
||||
vm.editTestCaseList = [];
|
||||
for (var i = 0; i < respond.data.file_list.input.length; i++) {
|
||||
vm.editTestCaseList.push({
|
||||
input: respond.data.file_list.input[i],
|
||||
output: respond.data.file_list.output[i]
|
||||
});
|
||||
}
|
||||
vm.problems[vm.editingProblemId-1].testCaseList = vm.editTestCaseList;
|
||||
bsAlert("测试数据添加成功!共添加"+vm.editTestCaseList.length +"组测试数据");
|
||||
}
|
||||
},
|
||||
function(){
|
||||
if (vm.editingProblemId == 0)
|
||||
{
|
||||
bsAlert("你还未指定一道题目!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
avalon.scan();
|
||||
|
||||
$("#contest_start_time").datetimepicker({
|
||||
format: "yyyy-mm-dd hh:ii",
|
||||
minuteStep: 5,
|
||||
weekStart: 1,
|
||||
language: "zh-CN"
|
||||
});
|
||||
$("#contest_end_time").datetimepicker({
|
||||
format: "yyyy-mm-dd hh:ii",
|
||||
minuteStep: 5,
|
||||
weekStart: 1,
|
||||
language: "zh-CN"
|
||||
});
|
||||
});
|
@ -1,242 +0,0 @@
|
||||
require(["jquery", "avalon", "editor", "uploader", "bs_alert", "datetimepicker",
|
||||
"validation",],
|
||||
function ($, avalon, editor, uploader, bs_alert) {
|
||||
avalon.vmodels.add_contest = null;
|
||||
$("#add-contest-form")
|
||||
.formValidation({
|
||||
framework: "bootstrap",
|
||||
fields: {
|
||||
name: {
|
||||
validators: {
|
||||
notEmpty: {
|
||||
message: "请填写比赛名称"
|
||||
},
|
||||
stringLength: {
|
||||
min: 1,
|
||||
max: 30,
|
||||
message: "名称不能超过30个字"
|
||||
}
|
||||
}
|
||||
},
|
||||
description: {
|
||||
validators: {
|
||||
notEmpty: {
|
||||
message: "请输入描述"
|
||||
}
|
||||
}
|
||||
},
|
||||
start_time: {
|
||||
validators: {
|
||||
notEmpty: {
|
||||
message: "请填写开始时间"
|
||||
|
||||
},
|
||||
date: {
|
||||
format: "YYYY-MM-DD h:m",
|
||||
message: "请输入一个正确的日期格式"
|
||||
}
|
||||
}
|
||||
},
|
||||
end_time: {
|
||||
validators: {
|
||||
notEmpty: {
|
||||
message: "请填写结束时间"
|
||||
},
|
||||
date: {
|
||||
format: "YYYY-MM-DD h:m",
|
||||
message: "请输入一个正确的日期格式"
|
||||
}
|
||||
}
|
||||
},
|
||||
password: {
|
||||
validators: {
|
||||
stringLength: {
|
||||
min: 0,
|
||||
max: 30,
|
||||
message: "密码不能超过10个字符"
|
||||
}
|
||||
}
|
||||
},
|
||||
"problem_name[]": {
|
||||
validators: {
|
||||
notEmpty: {
|
||||
message: "请输入题目名称"
|
||||
},
|
||||
stringLength: {
|
||||
min: 1,
|
||||
max: 30,
|
||||
message: "题目不能超过30个字符"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cpu[]": {
|
||||
validators: {
|
||||
notEmpty: {
|
||||
message: "请输入时间限制"
|
||||
},
|
||||
integer: {
|
||||
message: "时间限制用整数表示"
|
||||
},
|
||||
between: {
|
||||
inclusive: true,
|
||||
min: 1,
|
||||
max: 5000,
|
||||
message: "只能在1-5000之间"
|
||||
}
|
||||
}
|
||||
},
|
||||
"memory[]": {
|
||||
validators: {
|
||||
notEmpty: {
|
||||
message: "请输入内存"
|
||||
},
|
||||
integer: {
|
||||
message: "请输入一个合法的数字"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.on("success.form.fv", function (e) {
|
||||
e.preventDefault();
|
||||
var data = {
|
||||
title: vm.title, description: vm.description, start_time: vm.startTime, end_time: vm.endTime,
|
||||
password: vm.password, model: vm.model, open_rank: vm.openRank, problems: []
|
||||
};
|
||||
for (var i = 0; i < vm.problems.length; i++) {
|
||||
var problem = {
|
||||
title: vm.problems[i].title, description: vm.problems[i].description,
|
||||
cpu: vm.problems[i].cpu, memory: vm.problems[i].memory, samples: []
|
||||
};
|
||||
for (var j = 0; j < vm.problems[i].samples.length; j++) {
|
||||
problem.samples.push({
|
||||
input: vm.problems[i].samples[j].input,
|
||||
output: vm.problems[i].samples[j].output
|
||||
})
|
||||
}
|
||||
data.problems.push(problem);
|
||||
}
|
||||
console.log(data);
|
||||
});
|
||||
function make_id() {
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
for (var i = 0; i < 5; i++)
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
return text;
|
||||
}
|
||||
var upLoaderInited = false;
|
||||
editor("#editor");
|
||||
|
||||
var vm = avalon.define({
|
||||
$id: "add_contest",
|
||||
title: "",
|
||||
problemCount: 0,
|
||||
description: "",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
password: "",
|
||||
model: "",
|
||||
openRank: false,
|
||||
problems: [],
|
||||
problemNo: "-1",
|
||||
add_problem: function () {
|
||||
var problem_id = make_id();
|
||||
var problem = {
|
||||
id: problem_id,
|
||||
title: "",
|
||||
cpu: 1000,
|
||||
memory: 256,
|
||||
description: "",
|
||||
samples: [],
|
||||
visible: true,
|
||||
test_case_id: "",
|
||||
testCaseList: [],
|
||||
hint: "",
|
||||
difficulty: 0,
|
||||
uploadSuccess: false
|
||||
};
|
||||
vm.problems.push(problem);
|
||||
var id = vm.problems.length - 1;
|
||||
editor("#problem-" + problem_id + "-description");
|
||||
var hinteditor = editor("#problem-" + problem_id +"-hint");
|
||||
$("#add-contest-form").formValidation('addField', $('[name="problem_name[]"]'));
|
||||
$("#add-contest-form").formValidation('addField', $('[name="cpu[]"]'));
|
||||
$("#add-contest-form").formValidation('addField', $('[name="memory[]"]'));
|
||||
},
|
||||
del_problem: function (problem) {
|
||||
if (confirm("你确定要删除么?")) {
|
||||
vm.problems.remove(problem);
|
||||
}
|
||||
},
|
||||
toggle: function (item) {
|
||||
item.visible = !item.visible;
|
||||
},
|
||||
add_sample: function (problem) {
|
||||
problem.samples.push({id: make_id(), visible: true, input: "", output: ""});
|
||||
},
|
||||
del_sample: function (problem, sample) {
|
||||
if (confirm("你确定要删除么?")) {
|
||||
problem.samples.remove(sample);
|
||||
}
|
||||
},
|
||||
getBtnContent: function (item) {
|
||||
if (item.visible)
|
||||
return "折叠";
|
||||
return "展开";
|
||||
}
|
||||
});
|
||||
|
||||
uploader("#uploader", "/api/admin/test_case_upload/", function (file, respond) {
|
||||
if (respond.code)
|
||||
bs_alert(respond.data);
|
||||
else {
|
||||
var index = parseInt(vm.problemNo)-1;
|
||||
vm.problems[index].test_case_id = respond.data.test_case_id;
|
||||
vm.problems[index].uploadSuccess = true;
|
||||
vm.problems[index].testCaseList = [];
|
||||
for (var i = 0; i < respond.data.file_list.input.length; i++) {
|
||||
vm.problems[index].testCaseList.push({
|
||||
input: respond.data.file_list.input[i],
|
||||
output: respond.data.file_list.output[i]
|
||||
});
|
||||
}
|
||||
bs_alert("测试数据添加成功!共添加"+vm.problems[index].testCaseList.length +"组测试数据");
|
||||
}
|
||||
},
|
||||
function(){
|
||||
console.log(vm.problemNo);
|
||||
if (vm.problemNo == "-1")
|
||||
{
|
||||
bs_alert("你还未指定一道题目!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
);
|
||||
isUploaderInited = true;
|
||||
|
||||
avalon.scan();
|
||||
|
||||
$("#contest_start_time").datetimepicker({
|
||||
format: "yyyy-mm-dd hh:ii",
|
||||
minuteStep: 5,
|
||||
weekStart: 1,
|
||||
language: "zh-CN"
|
||||
});
|
||||
$("#contest_end_time").datetimepicker({
|
||||
format: "yyyy-mm-dd hh:ii",
|
||||
minuteStep: 5,
|
||||
weekStart: 1,
|
||||
language: "zh-CN"
|
||||
});
|
||||
$("#contest_start_time").datetimepicker()
|
||||
.on("hide", function (ev) {
|
||||
$("#add-contest-form")
|
||||
.formValidation("revalidateField", "start_time");
|
||||
});
|
||||
$("#contest_end_time").datetimepicker()
|
||||
.on("hide", function (ev) {
|
||||
$("#add-contest-form")
|
||||
.formValidation("revalidateField", "end_time");
|
||||
});
|
||||
});
|
137
static/src/js/app/admin/contest/contest_list.js
Normal file
137
static/src/js/app/admin/contest/contest_list.js
Normal file
@ -0,0 +1,137 @@
|
||||
require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker"], function ($, avalon, csrfTokenHeader, bsAlert, editor) {
|
||||
|
||||
avalon.ready(function () {
|
||||
if(avalon.vmodels.contestList){
|
||||
vm = avalon.vmodels.contestList;
|
||||
vm.editingContest = 0;
|
||||
}
|
||||
else {
|
||||
var vm = avalon.define({
|
||||
$id: "contestList",
|
||||
contestList: [],
|
||||
previousPage: 0,
|
||||
nextPage: 0,
|
||||
page: 1,
|
||||
totalPage: 1,
|
||||
group: "-1",
|
||||
groupList: [],
|
||||
keyword: "",
|
||||
editingContestId: 0,
|
||||
editTitle: "",
|
||||
editProblemList: [],
|
||||
editPassword: "",
|
||||
editStartTime: "",
|
||||
editEndTime: "",
|
||||
editMode: "",
|
||||
editShowRank: false,
|
||||
editShowSubmission: false,
|
||||
editProblemList: [],
|
||||
editingProblemId: 0,
|
||||
editSamples: [],
|
||||
editTestCaseList: [],
|
||||
editChoseGroupList: [],
|
||||
modelNameList: ["ACM", "AC总数", "分数"],
|
||||
contestTypeNameList: ["小组赛", "公开赛", "有密码保护的公开赛"],
|
||||
getNext: function () {
|
||||
if (!vm.nextPage)
|
||||
return;
|
||||
getPageData(vm.page + 1);
|
||||
},
|
||||
getPrevious: function () {
|
||||
if (!vm.previousPage)
|
||||
return;
|
||||
getPageData(vm.page - 1);
|
||||
},
|
||||
getBtnClass: function (btn) {
|
||||
if (btn == "next") {
|
||||
return vm.nextPage ? "btn btn-primary" : "btn btn-primary disabled";
|
||||
}
|
||||
else {
|
||||
return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled";
|
||||
}
|
||||
},
|
||||
getPage: function (page_index) {
|
||||
getPageData(page_index);
|
||||
},
|
||||
showEditContestArea: function (contestId) {
|
||||
if (contestId == vm.editingContestId)
|
||||
vm.editingContestId = 0;
|
||||
else {
|
||||
vm.editingContestId = contestId;
|
||||
vm.editTitle = vm.contestList[contestId-1].title;
|
||||
vm.editEndTime = vm.contestList[contestId-1].end_time;
|
||||
vm.editPassword = vm.contestList[contestId-1].password;
|
||||
vm.editStartTime = vm.contestList[contestId-1].start_time;
|
||||
vm.editMode = vm.contestList[contestId-1].mode;
|
||||
vm.editChoseGroupList = [];
|
||||
//= vm.contestList[contestId-1].group;//
|
||||
/*for (var key in vm.contestList[contestId-1].groups){
|
||||
var id = parseInt(vm.contestList[contestId-1].groups);
|
||||
for ()
|
||||
vm.editChoseGroupList.push({
|
||||
name:vm.groupList[vm.group].name,
|
||||
index:index,
|
||||
id:parseInt(vm.contestList[contestId-1].groups)
|
||||
});
|
||||
}*/
|
||||
vm.editShowRank = vm.contestList[contestId-1].show_rank;
|
||||
vm.editShowSubmission = vm.contestList[contestId-1].show_user_submission;
|
||||
//vm.editProblemList = vm.contestList[contestId-1].problems
|
||||
editor("#editor").setValue(vm.contestList[contestId-1].description);
|
||||
vm.editingProblemList = vm.contestList[contestId-1].problemList;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
getPageData(1);
|
||||
}
|
||||
|
||||
function getPageData(page) {
|
||||
|
||||
var url = "/api/admin/contest/?paging=true&page=" + page + "&page_size=10";
|
||||
if (vm.keyword != "")
|
||||
url += "&keyword=" + vm.keyword;
|
||||
$.ajax({
|
||||
url: url,
|
||||
dataType: "json",
|
||||
method: "get",
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
vm.contestList = data.data.results;
|
||||
vm.totalPage = data.data.total_page;
|
||||
vm.previousPage = data.data.previous_page;
|
||||
vm.nextPage = data.data.next_page;
|
||||
vm.page = page;
|
||||
}
|
||||
else {
|
||||
bsAlert(data.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var isSuperAdmin = true;
|
||||
$.ajax({ //用于获取该用户创建的所有小组的ajax请求
|
||||
beforeSend: csrfTokenHeader,
|
||||
url: "/api/admin/group/?my_group=true",
|
||||
dataType: "json",
|
||||
method: "get",
|
||||
contentType: "application/json",
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
if (isSuperAdmin)
|
||||
vm.groupList.push({id:0, name:"所有人", chose: false});
|
||||
for (var key in data.data) {
|
||||
data.data[key].chose = false;
|
||||
vm.groupList.push(data.data[key]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bsAlert(data.data);
|
||||
console.log(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
avalon.scan();
|
||||
});
|
@ -1,3 +1,44 @@
|
||||
from django.test import TestCase
|
||||
from account.models import User, REGULAR_USER
|
||||
from submission.models import Submission
|
||||
from rest_framework.test import APITestCase, APIClient
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
class SubmissionsListPageTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(username="gogoing", admin_type=REGULAR_USER)
|
||||
self.user2 = User.objects.create(username="cool", admin_type=REGULAR_USER)
|
||||
self.user2.set_password("666666")
|
||||
self.user.set_password("666666")
|
||||
self.user.save()
|
||||
# self.client.login(username="gogoing", password="666666")
|
||||
self.submission = Submission.objects.create(user_id=self.user.id,
|
||||
language=1,
|
||||
code='#include "stdio.h"\nint main(){\n\treturn 0;\n}',
|
||||
problem_id=1)
|
||||
|
||||
def test_visit_submissionsListPage_successfully(self):
|
||||
self.client.login(username="gogoing", password="666666")
|
||||
response = self.client.get('/my_submissions/1/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_visit_submissionsListPage_without_page_successfully(self):
|
||||
self.client.login(username="gogoing", password="666666")
|
||||
response = self.client.get('/my_submissions/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_submissionsListPage_does_not_exist(self):
|
||||
self.client.login(username="gogoing", password="666666")
|
||||
response = self.client.get('/my_submissions/5/')
|
||||
self.assertTemplateUsed(response, "utils/error.html")
|
||||
|
||||
def test_submissionsListPage_page_not_exist(self):
|
||||
self.client.login(username="gogoing", password="666666")
|
||||
response = self.client.get('/my_submissions/5/')
|
||||
self.assertTemplateUsed(response, "utils/error.html")
|
||||
|
||||
def test_submissionsListPage_have_no_submission(self):
|
||||
self.client.login(username="cool", password="666666")
|
||||
response = self.client.get('/my_submissions/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -15,6 +15,7 @@ from problem.models import Problem
|
||||
from utils.shortcuts import serializer_invalid_response, error_response, success_response, error_page, paginate
|
||||
from .models import Submission
|
||||
from .serializers import CreateSubmissionSerializer, SubmissionSerializer
|
||||
from django.core.paginator import Paginator
|
||||
|
||||
|
||||
class SubmissionAPIView(APIView):
|
||||
@ -72,7 +73,7 @@ def problem_my_submissions_list_page(request, problem_id):
|
||||
problem = Problem.objects.get(id=problem_id, visible=True)
|
||||
except Problem.DoesNotExist:
|
||||
return error_page(request, u"问题不存在")
|
||||
submissions = Submission.objects.filter(user_id=request.user.id, problem_id=problem.id).order_by("-create_time").\
|
||||
submissions = Submission.objects.filter(user_id=request.user.id, problem_id=problem.id).order_by("-create_time"). \
|
||||
values("id", "result", "create_time", "accepted_answer_time", "language")
|
||||
return render(request, "oj/problem/my_submissions_list.html",
|
||||
{"submissions": submissions, "problem": problem})
|
||||
@ -104,11 +105,34 @@ def my_submission(request, submission_id):
|
||||
{"submission": submission, "problem": problem, "info": info})
|
||||
|
||||
|
||||
|
||||
class SubmissionAdminAPIView(APIView):
|
||||
def get(self, request):
|
||||
problem_id = request.GET.get("problem_id", None)
|
||||
if not problem_id:
|
||||
return error_response(u"参数错误")
|
||||
submissions = Submission.objects.filter(problem_id=problem_id).order_by("-create_time")
|
||||
return paginate(request, submissions, SubmissionSerializer)
|
||||
return paginate(request, submissions, SubmissionSerializer)
|
||||
|
||||
|
||||
@login_required
|
||||
def my_submission_list_page(request, page=1):
|
||||
submissions = Submission.objects.filter(user_id=request.user.id). \
|
||||
values("id", "result", "create_time", "accepted_answer_time", "language")
|
||||
paginator = Paginator(submissions, 20)
|
||||
try:
|
||||
current_page = paginator.page(int(page))
|
||||
except Exception:
|
||||
return error_page(request, u"不存在的页码")
|
||||
previous_page = next_page = None
|
||||
try:
|
||||
previous_page = current_page.previous_page_number()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
next_page = current_page.next_page_number()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return render(request, "oj/submission/my_submissions_list.html",
|
||||
{"submissions": current_page, "page": int(page),
|
||||
"previous_page": previous_page, "next_page": next_page, "startId":int(page)*20-20})
|
||||
|
@ -6,7 +6,9 @@
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<input type="text" name="name" class="form-control" ms-duplex="title">
|
||||
<input type="text" name="name" class="form-control" ms-duplex="title"
|
||||
data-error="请填写比赛名称(名称不能超过50个字)" required>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
@ -15,6 +17,7 @@
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<textarea id="editor" placeholder="这里输入内容" autofocus ms-duplex="description"></textarea>
|
||||
<div class="help-block with-errors"></div>
|
||||
<small ms-visible="description==''" style="color:red">请填写比赛描述</small>
|
||||
</div>
|
||||
</div>
|
||||
@ -27,125 +30,150 @@
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="start_time" id="contest_start_time"
|
||||
ms-duplex="startTime">
|
||||
ms-duplex="startTime" data-error="请填写比赛开始时间" required>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="end_time" id="contest_end_time" ms-duplex="endTime">
|
||||
<input type="text" class="form-control" name="end_time" id="contest_end_time"
|
||||
ms-duplex="endTime" data-error="请填写比赛结束时间" required>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-6">
|
||||
<label>允许参加的用户</label>
|
||||
</div>
|
||||
<div class="col-md-6" >
|
||||
<label>密码保护</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label>
|
||||
模式
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label>
|
||||
结束前是否开放排名
|
||||
</label>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<select class="form-control" name="password" ms-duplex="group" ms-change="addGroup" value="-1">
|
||||
<option value="-1">请选择</option>
|
||||
<option ms-repeat="groupList" ms-attr-value="$index" ms-visible="!el.chose">{{el.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="password">
|
||||
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="password" ms-attr-readonly="!passwordUsable">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div ms-repeat="choseGroupList" class="group-tag" ms-click="unchose($index)">{{el.name}}</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label>排名方式</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label>结束前是否开放排名</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label>是否公开提交记录</label>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label><input type="radio" name="mode" ms-duplex-checked="model">
|
||||
<small>OI</small>
|
||||
</label>
|
||||
<label><input type="radio" name="mode">
|
||||
<label><input type="radio" name="mode" ms-duplex-string="mode" value="0">
|
||||
<small>ACM</small>
|
||||
</label>
|
||||
<label><input type="radio" name="mode" ms-duplex-string="mode" value="1">
|
||||
<small>AC数量</small>
|
||||
</label>
|
||||
<label><input type="radio" name="mode" ms-duplex-string="mode" value="2">
|
||||
<small>分数</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="text"><input type="checkbox" ms-duplex-checked="openRank">
|
||||
<label class="text"><input type="checkbox" ms-duplex-checked="showRank">
|
||||
<small>开放排名</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<label>添加题目</label>
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-sm" ms-click="add_problem()">添加</a>
|
||||
|
||||
<div class="col-md-12">
|
||||
<label>上传测试用例</label>
|
||||
<label>选择题号</label><select ms-duplex="problemNo">
|
||||
<option value="-1">未指定</option>
|
||||
<option ms-repeat="problems" ms-attr-value="$index+1">{{$index+1}}</option>
|
||||
</select>
|
||||
|
||||
<div id="uploader">
|
||||
<div>选择文件</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="text"><input type="checkbox" ms-duplex-checked="showSbumission">
|
||||
<small>允许查看提交记录</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="problem" ms-repeat-problem="problems">
|
||||
<label>添加题目</label>
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-sm" ms-click="add_problem()">添加</a>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>编号</th>
|
||||
<th>题目</th>
|
||||
<th>测试数据</th>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr ms-repeat="problems">
|
||||
<td>题目{{ $index+1 }}</td>
|
||||
<td>{{ el.title }}</td>
|
||||
<td>{{ el.testCaseList.length }}组</td>
|
||||
<td>
|
||||
<a href="javascript:void(0)"class="btn-sm btn-info" ms-click="showProblemEditArea($index+1)">编辑</a>
|
||||
<a href="javascript:void(0)"class="btn-sm btn-danger" ms-click="del_problem($index+1)">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
|
||||
<div class="problem" ms-visible="editingProblemId">
|
||||
<div class="panel panel-default problem-panel">
|
||||
<div class="panel-heading">
|
||||
<span class="panel-title">题目{{$index + 1}} </span>
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-sm" ms-click="toggle(problem)">
|
||||
{{ getBtnContent(problem)}}
|
||||
</a>
|
||||
<a href="javascript:void(0)" class="btn btn-danger btn-sm" ms-click="del_problem(problem)">
|
||||
删除
|
||||
</a>
|
||||
<span class="panel-title">题目{{editingProblemId}} </span>
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-sm" ms-click="hidden()">隐藏</a>
|
||||
<a href="javascript:void(0)" class="btn btn-danger btn-sm" ms-click="del_problem(editingProblemId)">删除</a>
|
||||
</div>
|
||||
<div class="panel-body" ms-visible="problem.visible">
|
||||
<div class="panel-body" >
|
||||
<div class="form-group col-md-12">
|
||||
<label>题目标题</label>
|
||||
<input type="text" name="problem_name[]" class="form-control" ms-duplex="problem.title">
|
||||
<input type="text" name="problemName" class="form-control" ms-duplex="problems[editingProblemId-1].title"data-error="请填写题目标题" required>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
<div class="form-group col-md-12">
|
||||
<label>题目描述</label>
|
||||
<textarea ms-attr-id="problem-{{ problem.id }}-description" placeholder="这里输入内容"
|
||||
ms-duplex="problem.description"></textarea>
|
||||
<small ms-visible="problem.description==''" style="color:red">请填写题目描述</small>
|
||||
<textarea id="problemDescriptionEditor" placeholder="这里输入内容"
|
||||
ms-duplex="problems[editingProblemId-1].description"></textarea>
|
||||
<small ms-visible="editDescription==''" style="color:red">请填写题目描述</small>
|
||||
</div>
|
||||
<div class="form-group col-md-12">
|
||||
<label>提示</label>
|
||||
<textarea ms-attr-id="problem-{{ problem.id }}-hint" placeholder="这里输入内容"
|
||||
ms-duplex="problem.hint"></textarea>
|
||||
<textarea id="problemHintEditor" placeholder="这里输入内容"
|
||||
ms-duplex="problems[editingProblemId-1].hint"></textarea>
|
||||
</div>
|
||||
<div class="col-md-3 form-group">
|
||||
<label>cpu</label>
|
||||
<input type="number" name="cpu[]" class="form-control" ms-duplex="problem.cpu">
|
||||
<input type="number" class="form-control" ms-duplex="problems[editingProblemId-1].timeLimit" data-error="请填写时间限制" required>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
<div class="col-md-3 form-group">
|
||||
<label>内存</label>
|
||||
<input type="number" name="memory[]" class="form-control" ms-duplex="problem.memory">
|
||||
<input type="number" class="form-control" ms-duplex="problems[editingProblemId-1].memoryLimit" data-error="请填写内存限制" required>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
<div class="col-md-3 form-group">
|
||||
<label>难度</label>
|
||||
<input type="number" name="difficulty[]" class="form-control"
|
||||
ms-duplex="problem.difficulty">
|
||||
<div class="col-md-3 form-group" ms-visible="mode==2">
|
||||
<label>分值</label>
|
||||
<input type="number" class="form-control" ms-duplex="problems[editingProblemId-1].score" data-error="请填写题目分值" required>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label>样例</label>
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-sm"
|
||||
ms-click="add_sample(problem)">添加</a>
|
||||
|
||||
<div class="sample">
|
||||
<div class="panel panel-default sample-panel" ms-repeat-sample="problem.samples">
|
||||
<div class="panel-heading">
|
||||
ms-click="add_sample()">添加</a>
|
||||
<div>
|
||||
<div class="panel panel-default sample-panel" ms-repeat-sample="editSamples">
|
||||
<div class="panel-heading">
|
||||
<span class="panel-title">样例{{$index + 1}}</span>
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-sm"
|
||||
ms-click="toggle(sample)">
|
||||
{{ getBtnContent(sample)}}
|
||||
</a>
|
||||
ms-click="toggle(sample)">{{ getBtnContent(sample)}}</a>
|
||||
<a href="javascript:void(0)" class="btn btn-danger btn-sm"
|
||||
ms-click="del_sample(problem, sample)">
|
||||
删除
|
||||
</a>
|
||||
ms-click="del_sample(sample)">删除</a>
|
||||
</div>
|
||||
<div class="panel-body row" ms-visible="sample.visible">
|
||||
<div class="col-md-6">
|
||||
@ -167,17 +195,15 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label>测试数据</label><br>
|
||||
<small class="text-info">请将所有测试用例打包在一个文件中上传,所有文件要在压缩包的根目录,且输入输出文件名要以从1开始连续数字标识要对应例如:<br>
|
||||
1.in 1.out 2.in 2.out
|
||||
</small>
|
||||
<table class="table table-striped" ms-visible="problem.uploadSuccess">
|
||||
|
||||
<label>测试数据</label>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<td>编号</td>
|
||||
<td>输入文件名</td>
|
||||
<td>输出文件名</td>
|
||||
</tr>
|
||||
<tr ms-repeat="problem.testCaseList">
|
||||
<tr ms-repeat="editTestCaseList">
|
||||
<td>{{$index}}</td>
|
||||
<td>{{ el.input }}</td>
|
||||
<td>{{ el.output }}</td>
|
||||
@ -189,6 +215,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label>上传测试测试数据</label>
|
||||
<div id="uploader">
|
||||
<div>选择文件</div>
|
||||
</div>
|
||||
<small class="text-info">请将所有测试用例打包在一个文件中上传,所有文件要在压缩包的根目录,且输入输出文件名要以从1开始连续数字标识要对应例如:<br>
|
||||
1.in 1.out 2.in 2.out
|
||||
</small><br>
|
||||
|
||||
<input type="submit" class="btn btn-success btn-lg" value="发布比赛">
|
||||
</div>
|
||||
</div>
|
||||
@ -196,5 +230,4 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/app/admin/contest/contest.js"></script>
|
||||
<link href="/static/css/tagEditor/jquery.tag-editor.css" rel="stylesheet">
|
||||
<script src="/static/js/app/admin/contest/add_contest.js"></script>
|
||||
|
168
template/admin/contest/contest_list.html
Normal file
168
template/admin/contest/contest_list.html
Normal file
@ -0,0 +1,168 @@
|
||||
<div ms-controller="contestList" class="col-md-9">
|
||||
<h1>比赛列表</h1>
|
||||
|
||||
<div class="right">
|
||||
<form class="form-inline" onsubmit="return false;">
|
||||
<div class="form-group-sm">
|
||||
<label>搜索</label>
|
||||
<input name="keyWord" class="form-control" placeholder="请输入关键词" ms-duplex="keyword">
|
||||
<input type="submit" value="搜索" class="btn btn-primary" ms-click="getPage(1)">
|
||||
</div>
|
||||
</form>
|
||||
<br>
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>比赛</th>
|
||||
<th>比赛类型</th>
|
||||
<th>公开排名</th>
|
||||
<th>开始时间</th>
|
||||
<th>结束时间</th>
|
||||
<th>创建时间</th>
|
||||
<th>创建者</th>
|
||||
</tr>
|
||||
<tr ms-repeat="contestList" ms-click="showEditContestArea($index+1)">
|
||||
<td>{{ el.id }}</td>
|
||||
<td>{{ el.title }}</td>
|
||||
<td>{{ contestTypeNameList[el.contest_type] }}</td>
|
||||
<td>{{ el.show_rank }}</td>
|
||||
<td>{{ el.start_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
|
||||
<td>{{ el.end_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
|
||||
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
|
||||
<td>{{ el.created_by.username }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="text-right">
|
||||
页数:{{ page }}/{{ totalPage }}
|
||||
<button ms-attr-class="getBtnClass('pre')" ms-click="getPrevious">上一页</button>
|
||||
<button ms-attr-class="getBtnClass('next')" ms-click="getNext">下一页</button>
|
||||
</div>
|
||||
<div ms-visible="editingContestId">
|
||||
<div class="col-md-12">
|
||||
<label>比赛名称</label>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<input type="text" name="name" class="form-control" ms-duplex="editTitle"
|
||||
data-error="请填写比赛名称(名称不能超过50个字)" required>
|
||||
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label>说明</label>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<textarea id="editor" placeholder="这里输入内容" autofocus ms-duplex="editDescription"></textarea>
|
||||
<div class="help-block with-errors"></div>
|
||||
<small ms-visible="description==''" style="color:red">请填写比赛描述</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label>开始时间</label>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label>结束时间</label>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="start_time" id="contest_start_time"
|
||||
ms-duplex="editStartTime" data-error="请填写比赛开始时间" required>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="end_time" id="contest_end_time"
|
||||
ms-duplex="editEndTime" data-error="请填写比赛结束时间" required>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label>允许参加的用户</label>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label>密码保护</label>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<select class="form-control" name="password" ms-duplex="group" ms-change="addGroup" value="-1">
|
||||
<option value="-1">请选择</option>
|
||||
<option ms-repeat="groupList" ms-attr-value="$index" ms-visible="!el.chose">{{el.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="editPassword"
|
||||
ms-attr-readonly="!passwordUsable">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div ms-repeat="choseGroupList" class="group-tag" ms-click="unchose($index)">{{el.name}}</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label>排名方式</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label>结束前是否开放排名</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label>是否公开提交记录</label>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label><input type="radio" name="mode" ms-duplex-string="editMode" value="0">
|
||||
<small>ACM</small>
|
||||
</label>
|
||||
<label><input type="radio" name="mode" ms-duplex-string="editMode" value="1">
|
||||
<small>AC数量</small>
|
||||
</label>
|
||||
<label><input type="radio" name="mode" ms-duplex-string="editMode" value="2">
|
||||
<small>分数</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="text"><input type="checkbox" ms-duplex-checked="editShowRank">
|
||||
<small>开放排名</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="text"><input type="checkbox" ms-duplex-checked="editShowSubmission">
|
||||
<small>允许查看提交记录</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label>添加题目</label>
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-sm" ms-click="add_problem()">添加</a>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>编号</th>
|
||||
<th>题目</th>
|
||||
<th>测试数据</th>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr ms-repeat="editProblemList">
|
||||
<td>题目{{ $index+1 }}</td>
|
||||
<td>{{ el.title }}</td>
|
||||
<td>{{ el.testCaseList.length }}组</td>
|
||||
<td>
|
||||
<a href="javascript:void(0)" class="btn-sm btn-info"
|
||||
ms-click="showProblemEditArea($index+1)">编辑</a>
|
||||
<a href="javascript:void(0)" class="btn-sm btn-danger" ms-click="del_problem($index+1)">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script src="/static/js/app/admin/contest/contest_list.js"></script>
|
12
template/oj/announcement/_announcement_panel.html
Normal file
12
template/oj/announcement/_announcement_panel.html
Normal file
@ -0,0 +1,12 @@
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||
公告
|
||||
</h3></div>
|
||||
<div class="panel-body">
|
||||
{% for item in announcements %}
|
||||
<p>{{ forloop.counter }}. <a href="/announcement/{{ item.id }}/" target="_blank">{{ item.title }}</a></p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
81
template/oj/contest/contest_list.html
Normal file
81
template/oj/contest/contest_list.html
Normal file
@ -0,0 +1,81 @@
|
||||
{% extends "oj_base.html" %}
|
||||
{% block body %}
|
||||
{% load contest %}
|
||||
<div class="container main">
|
||||
<div class="row">
|
||||
<div class="col-lg-9">
|
||||
<div class="row">
|
||||
<div class="right">
|
||||
<form class="form-inline" method="get">
|
||||
<div class="form-group-sm">
|
||||
<input name="keyword" class="form-control" placeholder="请输入关键词">
|
||||
<input type="submit" value="搜索" class="btn btn-primary">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>比赛名称</th>
|
||||
<th>开始时间</th>
|
||||
<th>比赛类型</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in contests %}
|
||||
<tr>
|
||||
<th scope="row"><a href="/contest/{{ item.id }}/">{{ item.id }}</a></th>
|
||||
<td><a href="/contest/{{ item.id }}/">{{ item.title }}</a></td>
|
||||
<td>{{ item.start_time }}</td>
|
||||
|
||||
{% ifequal item.contest_type 0 %}
|
||||
<td>小组赛</td>
|
||||
{% endifequal %}
|
||||
{% ifequal item.contest_type 1 %}
|
||||
<td>公开赛</td>
|
||||
{% endifequal %}
|
||||
{% ifequal item.contest_type 2 %}
|
||||
<td>公开赛(密码保护)</td>
|
||||
{% endifequal %}
|
||||
|
||||
<td class="{{ item|contest_status_color }}">{{ item|contest_status }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="form-group">
|
||||
<label>仅显示当前可参加的比赛
|
||||
<input id="join" type="checkbox" {% if join %}checked{% endif %} onchange="if(this.checked){location.href='/contests/?join=True'}else{location.href='/contests/'}">
|
||||
</label>
|
||||
</div>
|
||||
<nav>
|
||||
<ul class="pager">
|
||||
{% if previous_page %}
|
||||
<li class="previous">
|
||||
<a href="/contests/{{ previous_page }}/{% if keyword %}?keyword={{ keyword }}{% endif %}{% if join %}?join={{ join }}{% endif %}">
|
||||
<span aria-hidden="true">←</span> 上一页</a></li>
|
||||
{% endif %}
|
||||
{% if next_page %}
|
||||
<li class="next">
|
||||
<a href="/contests/{{ next_page }}/{% if keyword %}?keyword={{ keyword }}{% endif %}{% if join %}?join={{ join }}{% endif %}">下一页 <span
|
||||
aria-hidden="true">→</span></a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3">
|
||||
{% include "oj/announcement/_announcement_panel.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block js_block %}
|
||||
{% endblock %}
|
@ -55,18 +55,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||
公告
|
||||
</h3></div>
|
||||
<div class="panel-body">
|
||||
{% for item in announcements %}
|
||||
{{ forloop.counter }}. <a href="/announcement/{{ item.id }}/" target="_blank">{{ item.title }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% include "oj/announcement/_announcement_panel.html" %}
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
|
66
template/oj/submission/my_submissions_list.html
Normal file
66
template/oj/submission/my_submissions_list.html
Normal file
@ -0,0 +1,66 @@
|
||||
{% extends 'oj_base.html' %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
{% load submission %}
|
||||
<div class="container main">
|
||||
<ul class="nav nav-tabs nav-tabs-google">
|
||||
<li role="presentation">
|
||||
<a href="/submission/">提交</a></li>
|
||||
<li role="presentation" class="active">
|
||||
<a href="/submission/my_submissions/">我的提交</a></li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
{% if submissions %}
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr class="" success>
|
||||
<th>#</th>
|
||||
<th>提交时间</th>
|
||||
<th>结果</th>
|
||||
<th>运行时间</th>
|
||||
<th>语言</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in submissions %}
|
||||
<tr class="{{ item.result|translate_result_class }}">
|
||||
<th scope="row"><a href="/my_submission/{{ item.id }}/" id="id_{{ forloop.counter }}">
|
||||
{{ forloop.counter |add:startId }}</a></th>
|
||||
<td>{{ item.create_time }}</td>
|
||||
<td>{{ item.result|translate_result }}</td>
|
||||
<td>
|
||||
{% if item.accepted_answer_time %}
|
||||
{{ item.accepted_answer_time }}ms
|
||||
{% else %}
|
||||
--
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.language|translate_language }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>你还没有提交记录!</p>
|
||||
{% endif %}
|
||||
<nav>
|
||||
<ul class="pager">
|
||||
{% if previous_page %}
|
||||
<li class="previous"><a
|
||||
href="/my_submissions/{{ previous_page }}/{% if keyword %}?keyword={{ keyword }}{% endif %}{% if tag %}?tag={{ tag }}{% endif %}">
|
||||
<span aria-hidden="true">←</span> 上一页</a></li>
|
||||
{% endif %}
|
||||
{% if next_page %}
|
||||
<li class="next"><a
|
||||
href="/my_submissions/{{ next_page }}/{% if keyword %}?keyword={{ keyword }}{% endif %}{% if tag %}?tag={{ tag }}{% endif %}">下一页 <span
|
||||
aria-hidden="true">→</span></a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% endblock %}
|
29
utils/templatetags/contest.py
Normal file
29
utils/templatetags/contest.py
Normal file
@ -0,0 +1,29 @@
|
||||
# coding=utf-8
|
||||
import datetime
|
||||
from django.utils.timezone import localtime
|
||||
|
||||
|
||||
def get_contest_status(contest):
|
||||
now = datetime.datetime.now()
|
||||
if localtime(contest.start_time).replace(tzinfo=None) > now:
|
||||
return "没有开始"
|
||||
if localtime(contest.end_time).replace(tzinfo=None) < now:
|
||||
return "已经结束"
|
||||
return "正在进行"
|
||||
|
||||
|
||||
def get_contest_status_color(contest):
|
||||
now = datetime.datetime.now()
|
||||
if localtime(contest.start_time).replace(tzinfo=None) > now:
|
||||
return "info"
|
||||
if localtime(contest.end_time).replace(tzinfo=None) < now:
|
||||
return "warning"
|
||||
return "success"
|
||||
|
||||
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
register.filter("contest_status", get_contest_status)
|
||||
register.filter("contest_status_color", get_contest_status_color)
|
||||
|
Loading…
Reference in New Issue
Block a user