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:
virusdefender 2015-08-22 20:49:42 +08:00
commit 9977e156b1
16 changed files with 964 additions and 336 deletions

View File

@ -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:
@ -275,3 +280,46 @@ def contest_page(request, contest_id):
return render(request, "oj/contest/contest_no_privilege.html", {"contenst": contest, "reason": result["reason"]})
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})

View File

@ -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"),

View 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;
}

View File

@ -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;

View 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"
});
});

View File

@ -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");
});
});

View 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();
});

View File

@ -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)

View File

@ -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):
@ -104,7 +105,6 @@ 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)
@ -112,3 +112,27 @@ class SubmissionAdminAPIView(APIView):
return error_response(u"参数错误")
submissions = Submission.objects.filter(problem_id=problem_id).order_by("-create_time")
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})

View File

@ -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">
<label>允许参加的用户</label>
</div>
<div class="col-md-6" >
<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 class="col-md-3">
<label>
结束前是否开放排名
</label>
</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-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">
<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 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>
</div>
<div class="col-md-12">
<div class="problem" ms-repeat-problem="problems">
<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">
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>

View 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 }}&nbsp;&nbsp;
<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>

View 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 }}.&nbsp;&nbsp;<a href="/announcement/{{ item.id }}/" target="_blank">{{ item.title }}</a></p>
{% endfor %}
</div>
</div>

View 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">&larr;</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">&rarr;</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 %}

View File

@ -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 }}.&nbsp;&nbsp;<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">

View 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">&larr;</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">&rarr;</span></a></li>
{% endif %}
</ul>
</nav>
</div>
{% endblock %}

View 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)