diff --git a/contest/models.py b/contest/models.py index 1e4f45a..2708bcf 100644 --- a/contest/models.py +++ b/contest/models.py @@ -120,6 +120,9 @@ class ContestRank(models.Model): # key 是比赛题目的id submission_info = JSONField(default={}) + class Meta: + db_table = "contest_rank" + def update_rank(self, submission): if not submission.contest_id or submission.contest_id != self.contest_id: raise ValueError("Error submission type") diff --git a/contest/views.py b/contest/views.py index ade1407..162b9b8 100644 --- a/contest/views.py +++ b/contest/views.py @@ -1,6 +1,7 @@ # coding=utf-8 import json import datetime +import redis from django.shortcuts import render from django.db import IntegrityError @@ -8,6 +9,7 @@ from django.utils import dateparse from django.db.models import Q, Sum from django.core.paginator import Paginator from django.utils.timezone import now +from django.conf import settings 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_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, "contest": {"id": contest_id}}) @@ -384,7 +386,18 @@ def contest_list_page(request, page=1): def contest_rank_page(request, contest_id): contest = Contest.objects.get(id=contest_id) 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", {"rank": rank, "contest": contest, "contest_problems": contest_problems, diff --git a/group/models.py b/group/models.py index b399246..a2db074 100644 --- a/group/models.py +++ b/group/models.py @@ -37,5 +37,6 @@ class JoinGroupRequest(models.Model): # 是否处理 status = models.BooleanField(default=False) accepted = models.BooleanField(default=False) + class Meta: db_table = "join_group_request" diff --git a/judge/judger/client.py b/judge/judger/client.py index 53164e5..7c217aa 100644 --- a/judge/judger/client.py +++ b/judge/judger/client.py @@ -62,6 +62,8 @@ class JudgeClient(object): " --max-real-time " + str(self._max_real_time / 1000.0 * 2) + \ " --max-memory " + str(self._max_memory * 1000 * 1000) + \ " --network false" + \ + " --remount-dev true " + \ + " --reset-env true " + \ " --syscalls '" + self._language["syscalls"] + "'" + \ " --max-nprocess 20" + \ " --uid " + str(lrun_uid) + \ diff --git a/judge/judger_controller/tasks.py b/judge/judger_controller/tasks.py index 2de71d7..2219d96 100644 --- a/judge/judger_controller/tasks.py +++ b/judge/judger_controller/tasks.py @@ -13,9 +13,10 @@ def judge(submission_id, time_limit, memory_limit, test_case_id): try: command = "%s run --privileged --rm " \ "--link mysql " \ - "-v %s:/var/judger/test_case/ " \ - "-v %s:/var/judger/code/ " \ + "-v %s:/var/judger/test_case/:ro " \ + "-v %s:/var/judger/code/:ro " \ "-v %s:/var/judger/code/log/ " \ + "--device /dev/null:/dev/null " \ "%s " \ "python judge/judger/run.py " \ "--solution_id %s --time_limit %s --memory_limit %s --test_case_id %s" % \ diff --git a/mq/scripts/mq.py b/mq/scripts/mq.py index 47231da..5e16edf 100644 --- a/mq/scripts/mq.py +++ b/mq/scripts/mq.py @@ -39,20 +39,21 @@ class MessageQueue(object): logger.warning("Submission user does not exist, submission_id: " + submission_id) continue - if submission.result == result["accepted"] and not submission.contest_id: + if not submission.contest_id: # 更新普通题目的 ac 计数器 - try: - problem = Problem.objects.get(id=submission.problem_id) - problem.total_accepted_number += 1 - problem.save() - except Problem.DoesNotExist: - logger.warning("Submission problem does not exist, submission_id: " + submission_id) - continue + if submission.result == result["accepted"]: + try: + problem = Problem.objects.get(id=submission.problem_id) + problem.total_accepted_number += 1 + problem.save() + except Problem.DoesNotExist: + logger.warning("Submission problem does not exist, submission_id: " + submission_id) + continue - problems_status = user.problems_status - problems_status["problems"][str(problem.id)] = 1 - user.problems_status = problems_status - user.save() + problems_status = user.problems_status + problems_status["problems"][str(problem.id)] = 1 + user.problems_status = problems_status + user.save() # 普通题目的话,到这里就结束了 continue diff --git a/problem/models.py b/problem/models.py index adb7dd4..331f003 100644 --- a/problem/models.py +++ b/problem/models.py @@ -45,6 +45,7 @@ class AbstractProblem(models.Model): total_accepted_number = models.IntegerField(default=0) class Meta: + db_table = "problem" abstract = True diff --git a/static/src/js/app/admin/announcement/announcement.js b/static/src/js/app/admin/announcement/announcement.js index 4c67ef2..df73769 100644 --- a/static/src/js/app/admin/announcement/announcement.js +++ b/static/src/js/app/admin/announcement/announcement.js @@ -91,7 +91,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"], $.ajax({ beforeSend: csrfTokenHeader, url: "/api/admin/announcement/", - contentType: "application/json", + contentType: "application/json;charset=UTF-8", dataType: "json", method: "put", data: JSON.stringify({ @@ -209,7 +209,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"], $.ajax({ beforeSend: csrfTokenHeader, url: "/api/admin/announcement/", - contentType: "application/json", + contentType: "application/json;charset=UTF-8", data: JSON.stringify({ title: title, content: content, diff --git a/static/src/js/app/admin/contest/addContest.js b/static/src/js/app/admin/contest/addContest.js index 8aa5354..e2059b8 100644 --- a/static/src/js/app/admin/contest/addContest.js +++ b/static/src/js/app/admin/contest/addContest.js @@ -46,7 +46,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date beforeSend: csrfTokenHeader, url: "/api/admin/contest/", dataType: "json", - contentType: "application/json", + contentType: "application/json;charset=UTF-8", data: JSON.stringify(ajaxData), method: "post", success: function (data) { diff --git a/static/src/js/app/admin/contest/contestList.js b/static/src/js/app/admin/contest/contestList.js index cfaf664..4951979 100644 --- a/static/src/js/app/admin/contest/contestList.js +++ b/static/src/js/app/admin/contest/contestList.js @@ -48,10 +48,9 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker", beforeSend: csrfTokenHeader, url: "/api/admin/contest/", dataType: "json", - contentType: "application/json", + contentType: "application/json;charset=UTF-8", data: JSON.stringify(ajaxData), method: "put", - contentType: "application/json", success: function (data) { if (!data.code) { bsAlert("修改成功!"); @@ -237,7 +236,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker", dataType: "json", data: JSON.stringify(ajaxData), method: "post", - contentType: "application/json", + contentType: "application/json;charset=UTF-8", success: function (data) { if (!data.code) { bsAlert("题目添加成功!题目现在处于隐藏状态,请到题目列表手动修改,并添加分类和难度信息!"); diff --git a/static/src/js/app/admin/contest/editProblem.js b/static/src/js/app/admin/contest/editProblem.js index 51a668d..81c6931 100644 --- a/static/src/js/app/admin/contest/editProblem.js +++ b/static/src/js/app/admin/contest/editProblem.js @@ -66,7 +66,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE dataType: "json", data: JSON.stringify(ajaxData), method: method, - contentType: "application/json", + contentType: "application/json;charset=UTF-8", success: function (data) { if (!data.code) { bsAlert("题目编辑成功!"); diff --git a/static/src/js/app/admin/group/groupDetail.js b/static/src/js/app/admin/group/groupDetail.js index c06f9ce..de4a0ff 100644 --- a/static/src/js/app/admin/group/groupDetail.js +++ b/static/src/js/app/admin/group/groupDetail.js @@ -45,7 +45,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "validator"], function ($, url: "/api/admin/group_member/", method: "put", data: JSON.stringify({group_id: relation.group, members: [relation.user.id]}), - contentType: "application/json", + contentType: "application/json;charset=UTF-8", success: function (data) { vm.memberList.remove(relation); bsAlert(data.data); diff --git a/static/src/js/app/admin/problem/addProblem.js b/static/src/js/app/admin/problem/addProblem.js index 103396b..e67e9a7 100644 --- a/static/src/js/app/admin/problem/addProblem.js +++ b/static/src/js/app/admin/problem/addProblem.js @@ -59,7 +59,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE dataType: "json", data: JSON.stringify(ajaxData), method: "post", - contentType: "application/json", + contentType: "application/json;charset=UTF-8", success: function (data) { if (!data.code) { bsAlert("题目添加成功!"); diff --git a/static/src/js/app/admin/problem/editProblem.js b/static/src/js/app/admin/problem/editProblem.js index e3f235c..9036178 100644 --- a/static/src/js/app/admin/problem/editProblem.js +++ b/static/src/js/app/admin/problem/editProblem.js @@ -60,7 +60,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE dataType: "json", data: JSON.stringify(ajaxData), method: "put", - contentType: "application/json", + contentType: "application/json;charset=UTF-8", success: function (data) { if (!data.code) { bsAlert("题目编辑成功!"); diff --git a/static/src/js/app/oj/group/group.js b/static/src/js/app/oj/group/group.js index c36448e..d937142 100644 --- a/static/src/js/app/oj/group/group.js +++ b/static/src/js/app/oj/group/group.js @@ -9,14 +9,14 @@ require(["jquery", "csrfToken", "bsAlert"], function ($, csrfTokenHeader, bsAler } var groupId = window.location.pathname.split("/")[2]; - data = {group_id: groupId,message:message} + var data = {group_id: groupId,message:message}; $.ajax({ url: "/api/group_join/", method: "post", dataType: "json", beforeSend: csrfTokenHeader, data: JSON.stringify(data), - contentType: "application/json", + contentType: "application/json;charset=UTF-8", success: function (data) { if (data.code) { bsAlert(data.data); diff --git a/static/src/js/app/oj/problem/problem.js b/static/src/js/app/oj/problem/problem.js index 8f76fbe..4b5e639 100644 --- a/static/src/js/app/oj/problem/problem.js +++ b/static/src/js/app/oj/problem/problem.js @@ -222,7 +222,7 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"], url: url, method: "post", data: JSON.stringify(data), - contentType: "application/json", + contentType: "application/json;charset=UTF-8", success: function (data) { if (!data.code) { submissionId = data.data.submission_id; diff --git a/static/src/js/build.js b/static/src/js/build.js index 3acf938..6baa25c 100644 --- a/static/src/js/build.js +++ b/static/src/js/build.js @@ -37,6 +37,7 @@ modal: "lib/bootstrap/modal", dropdown: "lib/bootstrap/dropdown", transition: "lib/bootstrap/transition", + collapse: "lib/bootstrap/collapse", //百度webuploader -> uploader webUploader: "lib/webuploader/webuploader", diff --git a/static/src/js/config.js b/static/src/js/config.js index e318d97..eeae134 100644 --- a/static/src/js/config.js +++ b/static/src/js/config.js @@ -1,4 +1,5 @@ var require = { + urlArgs: "v=2", // RequireJS 通过一个相对的路径 baseUrl来加载所有代码。baseUrl通常被设置成data-main属性指定脚本的同级目录。 baseUrl: "/static/js/", paths: { @@ -38,6 +39,7 @@ var require = { modal: "lib/bootstrap/modal", dropdown: "lib/bootstrap/dropdown", transition: "lib/bootstrap/transition", + collapse: "lib/bootstrap/collapse", //百度webuploader -> uploader webUploader: "lib/webuploader/webuploader", diff --git a/static/src/js/lib/bootstrap/bootstrap.js b/static/src/js/lib/bootstrap/bootstrap.js index fb914ec..d5573df 100644 --- a/static/src/js/lib/bootstrap/bootstrap.js +++ b/static/src/js/lib/bootstrap/bootstrap.js @@ -1 +1 @@ -require(["jquery", "modal", "dropdown", "transition"]); \ No newline at end of file +require(["jquery", "modal", "dropdown", "transition", "collapse"]); \ No newline at end of file diff --git a/static/src/js/lib/bootstrap/collapse.js b/static/src/js/lib/bootstrap/collapse.js new file mode 100644 index 0000000..de0392e --- /dev/null +++ b/static/src/js/lib/bootstrap/collapse.js @@ -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); + +}); \ No newline at end of file diff --git a/template/src/oj/contest/contest_problems_list.html b/template/src/oj/contest/contest_problems_list.html index bca4c08..c0cb58a 100644 --- a/template/src/oj/contest/contest_problems_list.html +++ b/template/src/oj/contest/contest_problems_list.html @@ -29,7 +29,7 @@ -
+
@@ -60,9 +60,7 @@ -
- {% include "oj/announcement/_announcement_panel.html" %} -
+ {% endblock %} diff --git a/template/src/oj/contest/contest_rank.html b/template/src/oj/contest/contest_rank.html index 4b7f939..99eda73 100644 --- a/template/src/oj/contest/contest_rank.html +++ b/template/src/oj/contest/contest_rank.html @@ -39,8 +39,8 @@ {% for item in contest_problems %} - {% endfor %} @@ -50,9 +50,9 @@ diff --git a/template/src/oj/contest/submissions_list.html b/template/src/oj/contest/submissions_list.html index bee1e91..1bdfdeb 100644 --- a/template/src/oj/contest/submissions_list.html +++ b/template/src/oj/contest/submissions_list.html @@ -24,8 +24,10 @@
AC / 总提交 用时 + 罚时{{ item.sort_index }} + + {{ item.sort_index }}
{{ forloop.counter }} - {{ item.user.username }} + {{ item.user__username }} {% if show_real_name %} - ({{ item.real_name }}) + ({{ item.user__real_name }}) {% endif %} {{ item.total_ac_number }} / {{ item.total_submission_number }}
- - + + {% if submissions %} + + @@ -65,7 +67,6 @@ - {% if submissions %} {% for item in submissions %} diff --git a/utils/templatetags/contest.py b/utils/templatetags/contest.py index 2667922..cc02908 100644 --- a/utils/templatetags/contest.py +++ b/utils/templatetags/contest.py @@ -1,6 +1,5 @@ # coding=utf-8 -import datetime -from django.utils.timezone import now +import json def get_contest_status(contest): @@ -34,10 +33,11 @@ def get_the_formatted_time(seconds): 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 "" else: - submission = rank.submission_info[str(problem.id)] + submission = submission_info[str(problem.id)] if submission["is_ac"]: _class = "alert-success" if submission["is_first_ac"]: @@ -48,10 +48,11 @@ def get_submission_class(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 "" else: - submission = rank.submission_info[str(problem.id)] + submission = submission_info[str(problem.id)] if submission["is_ac"]: r = get_the_formatted_time(submission["ac_time"]) if submission["error_number"]: diff --git a/utils/xss_filter.py b/utils/xss_filter.py index 825fbc9..337f0a5 100644 --- a/utils/xss_filter.py +++ b/utils/xss_filter.py @@ -38,7 +38,7 @@ class XssHtml(HTMLParser): 'p', 'div', 'em', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'ul', 'ol', 'tr', 'th', 'td', 'hr', 'li', 'u', 'embed', 's', 'table', 'thead', 'tbody', - 'caption', 'small', 'q', 'sup', 'sub'] + 'caption', 'small', 'q', 'sup', 'sub', 'font'] common_attrs = ["style", "class", "name"] nonend_tags = ["img", "hr", "br", "embed"] tags_own_attrs = { @@ -46,6 +46,7 @@ class XssHtml(HTMLParser): "a": ["href", "target", "rel", "title"], "embed": ["src", "width", "height", "type", "allowfullscreen", "loop", "play", "wmode", "menu"], "table": ["border", "cellpadding", "cellspacing"], + "font": ["color"] } def __init__(self, allows=[]):
# 题目名称 用户