Merge branch 'virusdefender-dev'

* virusdefender-dev:
  bootstrap 更新打包
  require.js 加载的 js 也加版本号
  修复导航栏在小屏幕上折叠后无法展开的问题
  增加比赛结果缓存
  优化比赛的前台显示样式;删除公告,没有提交的时候不显示表格的表头;修改部分 typo
  增强判题和 docker 安全性
  使用关联查询提供性能
  修复 ajax 提交 json 中文乱码的问题
  update security
  修复不能显示真实姓名的 bug
This commit is contained in:
virusdefender
2015-09-29 21:30:41 +08:00
20 changed files with 267 additions and 35 deletions

View File

@@ -1,6 +1,7 @@
# coding=utf-8 # coding=utf-8
import json import json
import datetime import datetime
import redis
from django.shortcuts import render from django.shortcuts import render
from django.db import IntegrityError from django.db import IntegrityError
@@ -8,6 +9,7 @@ from django.utils import dateparse
from django.db.models import Q, Sum from django.db.models import Q, Sum
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.utils.timezone import now from django.utils.timezone import now
from django.conf import settings
from rest_framework.views import APIView from rest_framework.views import APIView
@@ -334,7 +336,7 @@ def contest_problems_list_page(request, contest_id):
比赛所有题目的列表页 比赛所有题目的列表页
""" """
contest = Contest.objects.get(id=contest_id) contest = Contest.objects.get(id=contest_id)
contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index") contest_problems = ContestProblem.objects.filter(contest=contest).select_related("contest").order_by("sort_index")
return render(request, "oj/contest/contest_problems_list.html", {"contest_problems": contest_problems, return render(request, "oj/contest/contest_problems_list.html", {"contest_problems": contest_problems,
"contest": {"id": contest_id}}) "contest": {"id": contest_id}})
@@ -384,7 +386,18 @@ def contest_list_page(request, page=1):
def contest_rank_page(request, contest_id): def contest_rank_page(request, contest_id):
contest = Contest.objects.get(id=contest_id) contest = Contest.objects.get(id=contest_id)
contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index") contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index")
rank = ContestRank.objects.filter(contest_id=contest_id).order_by("-total_ac_number", "total_time") r = redis.Redis(host=settings.REDIS_CACHE["host"], port=settings.REDIS_CACHE["port"], db=settings.REDIS_CACHE["db"])
cache_key = str(contest_id) + "_rank_cache"
rank = r.get(cache_key)
if not rank:
rank = ContestRank.objects.filter(contest_id=contest_id).\
select_related("user").\
order_by("-total_ac_number", "total_time").\
values("id", "user__id", "user__username", "user__real_name", "contest_id", "submission_info",
"total_submission_number", "total_ac_number", "total_time")
r.set(cache_key, json.dumps([dict(item) for item in rank]))
else:
rank = json.loads(rank)
return render(request, "oj/contest/contest_rank.html", return render(request, "oj/contest/contest_rank.html",
{"rank": rank, "contest": contest, {"rank": rank, "contest": contest,
"contest_problems": contest_problems, "contest_problems": contest_problems,

View File

@@ -62,6 +62,8 @@ class JudgeClient(object):
" --max-real-time " + str(self._max_real_time / 1000.0 * 2) + \ " --max-real-time " + str(self._max_real_time / 1000.0 * 2) + \
" --max-memory " + str(self._max_memory * 1000 * 1000) + \ " --max-memory " + str(self._max_memory * 1000 * 1000) + \
" --network false" + \ " --network false" + \
" --remount-dev true " + \
" --reset-env true " + \
" --syscalls '" + self._language["syscalls"] + "'" + \ " --syscalls '" + self._language["syscalls"] + "'" + \
" --max-nprocess 20" + \ " --max-nprocess 20" + \
" --uid " + str(lrun_uid) + \ " --uid " + str(lrun_uid) + \

View File

@@ -13,9 +13,10 @@ def judge(submission_id, time_limit, memory_limit, test_case_id):
try: try:
command = "%s run --privileged --rm " \ command = "%s run --privileged --rm " \
"--link mysql " \ "--link mysql " \
"-v %s:/var/judger/test_case/ " \ "-v %s:/var/judger/test_case/:ro " \
"-v %s:/var/judger/code/ " \ "-v %s:/var/judger/code/:ro " \
"-v %s:/var/judger/code/log/ " \ "-v %s:/var/judger/code/log/ " \
"--device /dev/null:/dev/null " \
"%s " \ "%s " \
"python judge/judger/run.py " \ "python judge/judger/run.py " \
"--solution_id %s --time_limit %s --memory_limit %s --test_case_id %s" % \ "--solution_id %s --time_limit %s --memory_limit %s --test_case_id %s" % \

View File

@@ -91,7 +91,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
$.ajax({ $.ajax({
beforeSend: csrfTokenHeader, beforeSend: csrfTokenHeader,
url: "/api/admin/announcement/", url: "/api/admin/announcement/",
contentType: "application/json", contentType: "application/json;charset=UTF-8",
dataType: "json", dataType: "json",
method: "put", method: "put",
data: JSON.stringify({ data: JSON.stringify({
@@ -209,7 +209,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
$.ajax({ $.ajax({
beforeSend: csrfTokenHeader, beforeSend: csrfTokenHeader,
url: "/api/admin/announcement/", url: "/api/admin/announcement/",
contentType: "application/json", contentType: "application/json;charset=UTF-8",
data: JSON.stringify({ data: JSON.stringify({
title: title, title: title,
content: content, content: content,

View File

@@ -46,7 +46,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
beforeSend: csrfTokenHeader, beforeSend: csrfTokenHeader,
url: "/api/admin/contest/", url: "/api/admin/contest/",
dataType: "json", dataType: "json",
contentType: "application/json", contentType: "application/json;charset=UTF-8",
data: JSON.stringify(ajaxData), data: JSON.stringify(ajaxData),
method: "post", method: "post",
success: function (data) { success: function (data) {

View File

@@ -48,10 +48,9 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
beforeSend: csrfTokenHeader, beforeSend: csrfTokenHeader,
url: "/api/admin/contest/", url: "/api/admin/contest/",
dataType: "json", dataType: "json",
contentType: "application/json", contentType: "application/json;charset=UTF-8",
data: JSON.stringify(ajaxData), data: JSON.stringify(ajaxData),
method: "put", method: "put",
contentType: "application/json",
success: function (data) { success: function (data) {
if (!data.code) { if (!data.code) {
bsAlert("修改成功!"); bsAlert("修改成功!");
@@ -237,7 +236,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
dataType: "json", dataType: "json",
data: JSON.stringify(ajaxData), data: JSON.stringify(ajaxData),
method: "post", method: "post",
contentType: "application/json", contentType: "application/json;charset=UTF-8",
success: function (data) { success: function (data) {
if (!data.code) { if (!data.code) {
bsAlert("题目添加成功!题目现在处于隐藏状态,请到题目列表手动修改,并添加分类和难度信息!"); bsAlert("题目添加成功!题目现在处于隐藏状态,请到题目列表手动修改,并添加分类和难度信息!");

View File

@@ -66,7 +66,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
dataType: "json", dataType: "json",
data: JSON.stringify(ajaxData), data: JSON.stringify(ajaxData),
method: method, method: method,
contentType: "application/json", contentType: "application/json;charset=UTF-8",
success: function (data) { success: function (data) {
if (!data.code) { if (!data.code) {
bsAlert("题目编辑成功!"); bsAlert("题目编辑成功!");

View File

@@ -45,7 +45,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "validator"], function ($,
url: "/api/admin/group_member/", url: "/api/admin/group_member/",
method: "put", method: "put",
data: JSON.stringify({group_id: relation.group, members: [relation.user.id]}), data: JSON.stringify({group_id: relation.group, members: [relation.user.id]}),
contentType: "application/json", contentType: "application/json;charset=UTF-8",
success: function (data) { success: function (data) {
vm.memberList.remove(relation); vm.memberList.remove(relation);
bsAlert(data.data); bsAlert(data.data);

View File

@@ -59,7 +59,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
dataType: "json", dataType: "json",
data: JSON.stringify(ajaxData), data: JSON.stringify(ajaxData),
method: "post", method: "post",
contentType: "application/json", contentType: "application/json;charset=UTF-8",
success: function (data) { success: function (data) {
if (!data.code) { if (!data.code) {
bsAlert("题目添加成功!"); bsAlert("题目添加成功!");

View File

@@ -60,7 +60,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
dataType: "json", dataType: "json",
data: JSON.stringify(ajaxData), data: JSON.stringify(ajaxData),
method: "put", method: "put",
contentType: "application/json", contentType: "application/json;charset=UTF-8",
success: function (data) { success: function (data) {
if (!data.code) { if (!data.code) {
bsAlert("题目编辑成功!"); bsAlert("题目编辑成功!");

View File

@@ -9,14 +9,14 @@ require(["jquery", "csrfToken", "bsAlert"], function ($, csrfTokenHeader, bsAler
} }
var groupId = window.location.pathname.split("/")[2]; var groupId = window.location.pathname.split("/")[2];
data = {group_id: groupId,message:message} var data = {group_id: groupId,message:message};
$.ajax({ $.ajax({
url: "/api/group_join/", url: "/api/group_join/",
method: "post", method: "post",
dataType: "json", dataType: "json",
beforeSend: csrfTokenHeader, beforeSend: csrfTokenHeader,
data: JSON.stringify(data), data: JSON.stringify(data),
contentType: "application/json", contentType: "application/json;charset=UTF-8",
success: function (data) { success: function (data) {
if (data.code) { if (data.code) {
bsAlert(data.data); bsAlert(data.data);

View File

@@ -222,7 +222,7 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"],
url: url, url: url,
method: "post", method: "post",
data: JSON.stringify(data), data: JSON.stringify(data),
contentType: "application/json", contentType: "application/json;charset=UTF-8",
success: function (data) { success: function (data) {
if (!data.code) { if (!data.code) {
submissionId = data.data.submission_id; submissionId = data.data.submission_id;

View File

@@ -37,6 +37,7 @@
modal: "lib/bootstrap/modal", modal: "lib/bootstrap/modal",
dropdown: "lib/bootstrap/dropdown", dropdown: "lib/bootstrap/dropdown",
transition: "lib/bootstrap/transition", transition: "lib/bootstrap/transition",
collapse: "lib/bootstrap/collapse",
//百度webuploader -> uploader //百度webuploader -> uploader
webUploader: "lib/webuploader/webuploader", webUploader: "lib/webuploader/webuploader",

View File

@@ -1,4 +1,5 @@
var require = { var require = {
urlArgs: "v=2",
// RequireJS 通过一个相对的路径 baseUrl来加载所有代码。baseUrl通常被设置成data-main属性指定脚本的同级目录。 // RequireJS 通过一个相对的路径 baseUrl来加载所有代码。baseUrl通常被设置成data-main属性指定脚本的同级目录。
baseUrl: "/static/js/", baseUrl: "/static/js/",
paths: { paths: {
@@ -38,6 +39,7 @@ var require = {
modal: "lib/bootstrap/modal", modal: "lib/bootstrap/modal",
dropdown: "lib/bootstrap/dropdown", dropdown: "lib/bootstrap/dropdown",
transition: "lib/bootstrap/transition", transition: "lib/bootstrap/transition",
collapse: "lib/bootstrap/collapse",
//百度webuploader -> uploader //百度webuploader -> uploader
webUploader: "lib/webuploader/webuploader", webUploader: "lib/webuploader/webuploader",

View File

@@ -1 +1 @@
require(["jquery", "modal", "dropdown", "transition"]); require(["jquery", "modal", "dropdown", "transition", "collapse"]);

View File

@@ -0,0 +1,214 @@
define([ 'jquery', './transition' ], function ( jQuery ) {
/* ========================================================================
* Bootstrap: collapse.js v3.3.5
* http://getbootstrap.com/javascript/#collapse
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// COLLAPSE PUBLIC CLASS DEFINITION
// ================================
var Collapse = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, Collapse.DEFAULTS, options)
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
'[data-toggle="collapse"][data-target="#' + element.id + '"]')
this.transitioning = null
if (this.options.parent) {
this.$parent = this.getParent()
} else {
this.addAriaAndCollapsedClass(this.$element, this.$trigger)
}
if (this.options.toggle) this.toggle()
}
Collapse.VERSION = '3.3.5'
Collapse.TRANSITION_DURATION = 350
Collapse.DEFAULTS = {
toggle: true
}
Collapse.prototype.dimension = function () {
var hasWidth = this.$element.hasClass('width')
return hasWidth ? 'width' : 'height'
}
Collapse.prototype.show = function () {
if (this.transitioning || this.$element.hasClass('in')) return
var activesData
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
if (actives && actives.length) {
activesData = actives.data('bs.collapse')
if (activesData && activesData.transitioning) return
}
var startEvent = $.Event('show.bs.collapse')
this.$element.trigger(startEvent)
if (startEvent.isDefaultPrevented()) return
if (actives && actives.length) {
Plugin.call(actives, 'hide')
activesData || actives.data('bs.collapse', null)
}
var dimension = this.dimension()
this.$element
.removeClass('collapse')
.addClass('collapsing')[dimension](0)
.attr('aria-expanded', true)
this.$trigger
.removeClass('collapsed')
.attr('aria-expanded', true)
this.transitioning = 1
var complete = function () {
this.$element
.removeClass('collapsing')
.addClass('collapse in')[dimension]('')
this.transitioning = 0
this.$element
.trigger('shown.bs.collapse')
}
if (!$.support.transition) return complete.call(this)
var scrollSize = $.camelCase(['scroll', dimension].join('-'))
this.$element
.one('bsTransitionEnd', $.proxy(complete, this))
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
}
Collapse.prototype.hide = function () {
if (this.transitioning || !this.$element.hasClass('in')) return
var startEvent = $.Event('hide.bs.collapse')
this.$element.trigger(startEvent)
if (startEvent.isDefaultPrevented()) return
var dimension = this.dimension()
this.$element[dimension](this.$element[dimension]())[0].offsetHeight
this.$element
.addClass('collapsing')
.removeClass('collapse in')
.attr('aria-expanded', false)
this.$trigger
.addClass('collapsed')
.attr('aria-expanded', false)
this.transitioning = 1
var complete = function () {
this.transitioning = 0
this.$element
.removeClass('collapsing')
.addClass('collapse')
.trigger('hidden.bs.collapse')
}
if (!$.support.transition) return complete.call(this)
this.$element
[dimension](0)
.one('bsTransitionEnd', $.proxy(complete, this))
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)
}
Collapse.prototype.toggle = function () {
this[this.$element.hasClass('in') ? 'hide' : 'show']()
}
Collapse.prototype.getParent = function () {
return $(this.options.parent)
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
.each($.proxy(function (i, element) {
var $element = $(element)
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
}, this))
.end()
}
Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
var isOpen = $element.hasClass('in')
$element.attr('aria-expanded', isOpen)
$trigger
.toggleClass('collapsed', !isOpen)
.attr('aria-expanded', isOpen)
}
function getTargetFromTrigger($trigger) {
var href
var target = $trigger.attr('data-target')
|| (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
return $(target)
}
// COLLAPSE PLUGIN DEFINITION
// ==========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.collapse')
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.collapse
$.fn.collapse = Plugin
$.fn.collapse.Constructor = Collapse
// COLLAPSE NO CONFLICT
// ====================
$.fn.collapse.noConflict = function () {
$.fn.collapse = old
return this
}
// COLLAPSE DATA-API
// =================
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
var $this = $(this)
if (!$this.attr('data-target')) e.preventDefault()
var $target = getTargetFromTrigger($this)
var data = $target.data('bs.collapse')
var option = data ? 'toggle' : $this.data()
Plugin.call($target, option)
})
}(jQuery);
});

View File

@@ -29,7 +29,7 @@
</div> </div>
<div class="col-md-9 col-lg-9"> <div class="col-md-12 col-lg-12">
<div> <div>
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
@@ -60,9 +60,7 @@
</div> </div>
</div> </div>
<div class="col-md-3 col-lg-3">
{% include "oj/announcement/_announcement_panel.html" %}
</div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -39,8 +39,8 @@
<th class="text-center">AC / 总提交</th> <th class="text-center">AC / 总提交</th>
<th class="text-center">用时 + 罚时</th> <th class="text-center">用时 + 罚时</th>
{% for item in contest_problems %} {% for item in contest_problems %}
<th class="text-center"><a <th class="text-center">
href="/contest/{{ contest.id }}/problem/{{ item.id }}/">{{ item.sort_index }}</a> <a href="/contest/{{ contest.id }}/problem/{{ item.id }}/">{{ item.sort_index }}</a>
</th> </th>
{% endfor %} {% endfor %}
</tr> </tr>
@@ -50,9 +50,9 @@
<tr> <tr>
<th scope="row">{{ forloop.counter }}</th> <th scope="row">{{ forloop.counter }}</th>
<td> <td>
{{ item.user.username }} {{ item.user__username }}
{% if show_real_name %} {% if show_real_name %}
{{ item.real_name }} {{ item.user__real_name }}
{% endif %} {% endif %}
</td> </td>
<td>{{ item.total_ac_number }} / {{ item.total_submission_number }}</td> <td>{{ item.total_ac_number }} / {{ item.total_submission_number }}</td>

View File

@@ -24,8 +24,10 @@
</ul> </ul>
</div> </div>
<table class="table table-bordered"> <table class="table table-bordered">
{% if submissions %}
<thead> <thead>
<tr class="" success> <tr>
<th>#</th> <th>#</th>
<th>题目名称</th> <th>题目名称</th>
<th>用户</th> <th>用户</th>
@@ -65,7 +67,6 @@
</th> </th>
</tr> </tr>
</thead> </thead>
{% if submissions %}
<tbody> <tbody>
{% for item in submissions %} {% for item in submissions %}
<tr> <tr>

View File

@@ -1,6 +1,5 @@
# coding=utf-8 # coding=utf-8
import datetime import json
from django.utils.timezone import now
def get_contest_status(contest): def get_contest_status(contest):
@@ -34,10 +33,11 @@ def get_the_formatted_time(seconds):
def get_submission_class(rank, problem): def get_submission_class(rank, problem):
if str(problem.id) not in rank.submission_info: submission_info = json.loads(rank["submission_info"])
if str(problem.id) not in submission_info:
return "" return ""
else: else:
submission = rank.submission_info[str(problem.id)] submission = submission_info[str(problem.id)]
if submission["is_ac"]: if submission["is_ac"]:
_class = "alert-success" _class = "alert-success"
if submission["is_first_ac"]: if submission["is_first_ac"]:
@@ -48,10 +48,11 @@ def get_submission_class(rank, problem):
def get_submission_content(rank, problem): def get_submission_content(rank, problem):
if str(problem.id) not in rank.submission_info: submission_info = json.loads(rank["submission_info"])
if str(problem.id) not in submission_info:
return "" return ""
else: else:
submission = rank.submission_info[str(problem.id)] submission = submission_info[str(problem.id)]
if submission["is_ac"]: if submission["is_ac"]:
r = get_the_formatted_time(submission["ac_time"]) r = get_the_formatted_time(submission["ac_time"])
if submission["error_number"]: if submission["error_number"]: