Merge branch 'virusdefender-dev' into debug

* virusdefender-dev:
  增加前台题目的重新判题功能
  删除统计功能
  修复公告列表中用户权限判断错误的情况,抽取代码为 decorator。
  用户不需要验证码的时候刷新验证会导致错误,增加判断
  去除部分 magic number
  比赛如果不是正在进行的状态,就不创建比赛的分数记录和增加比赛题目计数器
  修改刷新时间
  增加题目页面倒计时的 js
  增加比赛倒计时的 api
  增加判题帮助
  修复判断验证码是否存在的时候,用户不存在导致的报错
This commit is contained in:
virusdefender 2015-09-21 13:57:17 +08:00
commit dc9476f585
21 changed files with 189 additions and 85 deletions

View File

@ -7,7 +7,7 @@ from .models import User
class UserLoginSerializer(serializers.Serializer): class UserLoginSerializer(serializers.Serializer):
username = serializers.CharField(max_length=30) username = serializers.CharField(max_length=30)
password = serializers.CharField(max_length=30) password = serializers.CharField(max_length=30)
captcha = serializers.CharField(required=False,min_length=4,max_length=4) captcha = serializers.CharField(required=False, min_length=4, max_length=4)
class UsernameCheckSerializer(serializers.Serializer): class UsernameCheckSerializer(serializers.Serializer):

View File

@ -26,17 +26,15 @@ class UserLoginAPIView(APIView):
serializer = UserLoginSerializer(data=request.data) serializer = UserLoginSerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
data = serializer.data data = serializer.data
user = User.objects.get(username=data["username"])
# 只有管理员才适用验证码登录
if user.admin_type > 0:
if not "captcha" in data:
return error_response(u"请填写验证码!")
captcha = Captcha(request)
if not captcha.check(data["captcha"]):
return error_response(u"验证码错误")
user = auth.authenticate(username=data["username"], password=data["password"]) user = auth.authenticate(username=data["username"], password=data["password"])
# 用户名或密码错误的话 返回None # 用户名或密码错误的话 返回None
if user: if user:
if user.admin_type > 0:
if "captcha" not in data:
return error_response(u"请填写验证码!")
captcha = Captcha(request)
if not captcha.check(data["captcha"]):
return error_response(u"验证码错误")
auth.login(request, user) auth.login(request, user)
return success_response(u"登录成功") return success_response(u"登录成功")
else: else:

View File

@ -0,0 +1,43 @@
# coding=utf-8
from functools import wraps
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from utils.shortcuts import error_response, error_page
from account.models import SUPER_ADMIN
from .models import Announcement
def check_user_announcement_permission(func):
@wraps(func)
def _check_user_announcement_permission(*args, **kwargs):
"""
这个函数检测当前用户能否查看这个公告
"""
# CBV 的情况第一个参数是self第二个参数是request
if len(args) == 2:
request = args[-1]
else:
request = args[0]
if "announcement_id" not in kwargs:
return error_page(request, u"参数错误")
announcement_id = kwargs["announcement_id"]
try:
announcement = Announcement.objects.get(id=announcement_id, visible=True)
except Announcement.DoesNotExist:
return error_page(request, u"公告不存在")
# 如果公告是只有部分小组可见的
if not announcement.is_global:
# 用户必须是登录状态的
if not request.user.is_authenticated():
return HttpResponseRedirect("/login/")
if not announcement.groups.filter(id__in=request.user.group_set.all()).exists():
return error_page(request, u"公告不存在")
return func(*args, **kwargs)
return _check_user_announcement_permission

View File

@ -18,7 +18,7 @@ class Announcement(models.Model):
last_update_time = models.DateTimeField(auto_now=True) last_update_time = models.DateTimeField(auto_now=True)
# 是否可见 false的话相当于删除 # 是否可见 false的话相当于删除
visible = models.BooleanField(default=True) visible = models.BooleanField(default=True)
# 公告可见范围 0是全局可见 1是部分小组可见,需要在下面的字段中存储可见的小组 # 公告可见范围 True 是全局可见 False 是部分小组可见,需要在下面的字段中存储可见的小组
is_global = models.BooleanField() is_global = models.BooleanField()
groups = models.ManyToManyField(Group) groups = models.ManyToManyField(Group)

View File

@ -10,27 +10,13 @@ from group.models import Group
from .models import Announcement from .models import Announcement
from .serializers import (CreateAnnouncementSerializer, AnnouncementSerializer, from .serializers import (CreateAnnouncementSerializer, AnnouncementSerializer,
EditAnnouncementSerializer) EditAnnouncementSerializer)
from .decorators import check_user_announcement_permission
@check_user_announcement_permission
def announcement_page(request, announcement_id): def announcement_page(request, announcement_id):
try: announcement = Announcement.objects.get(id=announcement_id, visible=True)
announcement = Announcement.objects.get(id=announcement_id, visible=True) return render(request, "oj/announcement/announcement.html", {"announcement": announcement})
except Announcement.DoesNotExist:
return error_page(request, u"公告不存在")
# 公开的公告
if announcement.is_global == 0:
return render(request, "oj/announcement/announcement.html", {"announcement": announcement})
else:
if not request.user.is_authenticated():
return error_page(request, u"公告不存在")
# 判断是不是在组里面
if request.user.admin_type == SUPER_ADMIN or request.user == announcement.created_by:
return render(request, "oj/announcement/announcement.html", {"announcement": announcement})
else:
if request.user.groups.filter(id__in=[item.id for item in announcement.groups.all()]).exists():
return render(request, "oj/announcement/announcement.html", {"announcement": announcement})
else:
return error_page(request, u"公告不存在")
class AnnouncementAdminAPIView(APIView): class AnnouncementAdminAPIView(APIView):

View File

@ -3,12 +3,12 @@ from functools import wraps
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render from django.shortcuts import render
from django.utils.timezone import now
from utils.shortcuts import error_response, error_page from utils.shortcuts import error_response, error_page
from account.models import SUPER_ADMIN from account.models import SUPER_ADMIN
from .models import Contest from .models import (Contest, PASSWORD_PROTECTED_CONTEST, PUBLIC_CONTEST, GROUP_CONTEST,
CONTEST_ENDED, CONTEST_NOT_START, CONTEST_UNDERWAY)
def check_user_contest_permission(func): def check_user_contest_permission(func):
@ -55,7 +55,7 @@ def check_user_contest_permission(func):
return func(*args, **kwargs) return func(*args, **kwargs)
# 有密码的公开赛 # 有密码的公开赛
if contest.contest_type == 2: if contest.contest_type == PASSWORD_PROTECTED_CONTEST:
# 没有输入过密码 # 没有输入过密码
if contest.id not in request.session.get("contests", []): if contest.id not in request.session.get("contests", []):
if request.is_ajax(): if request.is_ajax():
@ -65,7 +65,7 @@ def check_user_contest_permission(func):
{"reason": "password_protect", "show_tab": False, "contest": contest}) {"reason": "password_protect", "show_tab": False, "contest": contest})
# 指定小组参加的 # 指定小组参加的
if contest.contest_type == 0: if contest.contest_type == GROUP_CONTEST:
if not contest.groups.filter(id__in=request.user.group_set.all()).exists(): if not contest.groups.filter(id__in=request.user.group_set.all()).exists():
if request.is_ajax(): if request.is_ajax():
return error_response(u"只有指定小组的可以参加这场比赛") return error_response(u"只有指定小组的可以参加这场比赛")
@ -74,7 +74,7 @@ def check_user_contest_permission(func):
{"reason": "group_limited", "show_tab": False, "contest": contest}) {"reason": "group_limited", "show_tab": False, "contest": contest})
# 比赛没有开始 # 比赛没有开始
if contest.status == 1: if contest.status == CONTEST_NOT_START:
if request.is_ajax(): if request.is_ajax():
return error_response(u"比赛还没有开始") return error_response(u"比赛还没有开始")
else: else:

View File

@ -10,6 +10,10 @@ GROUP_CONTEST = 0
PUBLIC_CONTEST = 1 PUBLIC_CONTEST = 1
PASSWORD_PROTECTED_CONTEST = 2 PASSWORD_PROTECTED_CONTEST = 2
CONTEST_NOT_START = 1
CONTEST_ENDED = -1
CONTEST_UNDERWAY = 0
class Contest(models.Model): class Contest(models.Model):
title = models.CharField(max_length=40, unique=True) title = models.CharField(max_length=40, unique=True)
@ -44,13 +48,13 @@ class Contest(models.Model):
def status(self): def status(self):
if self.start_time > now(): if self.start_time > now():
# 没有开始 返回1 # 没有开始 返回1
return 1 return CONTEST_NOT_START
elif self.end_time < now(): elif self.end_time < now():
# 已经结束 返回0 # 已经结束 返回0
return -1 return CONTEST_ENDED
else: else:
# 正在进行 返回0 # 正在进行 返回0
return 0 return CONTEST_UNDERWAY
class Meta: class Meta:
db_table = "contest" db_table = "contest"

View File

@ -7,6 +7,8 @@ from django.db import IntegrityError
from django.utils import dateparse 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 rest_framework.views import APIView from rest_framework.views import APIView
from utils.shortcuts import (serializer_invalid_response, error_response, from utils.shortcuts import (serializer_invalid_response, error_response,
@ -454,3 +456,22 @@ def contest_rank_page(request, contest_id):
"auto_refresh": request.GET.get("auto_refresh", None) == "true", "auto_refresh": request.GET.get("auto_refresh", None) == "true",
"show_real_name": request.GET.get("show_real_name", None) == "true", "show_real_name": request.GET.get("show_real_name", None) == "true",
"real_time_rank": contest.real_time_rank}) "real_time_rank": contest.real_time_rank})
class ContestTimeAPIView(APIView):
"""
获取比赛开始或者结束的倒计时返回毫秒数字
"""
def get(self, request):
t = request.GET.get("type", "start")
contest_id = request.GET.get("contest_id", -1)
try:
contest = Contest.objects.get(id=contest_id)
except Contest.DoesNotExist:
return error_response(u"比赛不存在")
if t == "start":
# 距离开始还有多长时间
return success_response(int((contest.start_time - now()).total_seconds() * 1000))
else:
# 距离结束还有多长时间
return success_response(int((contest.end_time - now()).total_seconds() * 1000))

View File

@ -57,6 +57,9 @@ class MessageQueue(object):
# 能运行到这里的都是比赛题目 # 能运行到这里的都是比赛题目
try: try:
contest = Contest.objects.get(id=submission.contest_id) contest = Contest.objects.get(id=submission.contest_id)
if contest.status != 0:
logger.info("Contest debug mode, id: " + str(contest.id) + ", submission id: " + submission_id)
continue
contest_problem = ContestProblem.objects.get(contest=contest, id=submission.problem_id) contest_problem = ContestProblem.objects.get(contest=contest, id=submission.problem_id)
except Contest.DoesNotExist: except Contest.DoesNotExist:
logger.warning("Submission contest does not exist, submission_id: " + submission_id) logger.warning("Submission contest does not exist, submission_id: " + submission_id)

View File

@ -8,7 +8,8 @@ from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterA
from announcement.views import AnnouncementAdminAPIView from announcement.views import AnnouncementAdminAPIView
from contest.views import ContestAdminAPIView, ContestProblemAdminAPIView, ContestPasswordVerifyAPIView from contest.views import (ContestAdminAPIView, ContestProblemAdminAPIView,
ContestPasswordVerifyAPIView, ContestTimeAPIView)
from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView, from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView,
JoinGroupAPIView, JoinGroupRequestAdminAPIView) JoinGroupAPIView, JoinGroupRequestAdminAPIView)
@ -16,7 +17,8 @@ from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView,
from admin.views import AdminTemplateView from admin.views import AdminTemplateView
from problem.views import TestCaseUploadAPIView, ProblemTagAdminAPIView, ProblemAdminAPIView from problem.views import TestCaseUploadAPIView, ProblemTagAdminAPIView, ProblemAdminAPIView
from submission.views import SubmissionAPIView, SubmissionAdminAPIView, SubmissionShareAPIView from submission.views import (SubmissionAPIView, SubmissionAdminAPIView,
SubmissionShareAPIView, SubmissionRejudgeAdminAPIView)
from contest_submission.views import ContestSubmissionAPIView, ContestSubmissionAdminAPIView from contest_submission.views import ContestSubmissionAPIView, ContestSubmissionAdminAPIView
from monitor.views import QueueLengthMonitorAPIView from monitor.views import QueueLengthMonitorAPIView
from utils.views import SimditorImageUploadAPIView from utils.views import SimditorImageUploadAPIView
@ -25,7 +27,6 @@ from contest_submission.views import contest_problem_my_submissions_list_page
urlpatterns = [ urlpatterns = [
url(r'^install/$', "install.views.install"),
url("^$", "account.views.index_page", name="index_page"), url("^$", "account.views.index_page", name="index_page"),
url(r'^docs/', include('rest_framework_swagger.urls')), url(r'^docs/', include('rest_framework_swagger.urls')),
url(r'^admin/$', TemplateView.as_view(template_name="admin/admin.html"), name="admin_spa_page"), url(r'^admin/$', TemplateView.as_view(template_name="admin/admin.html"), name="admin_spa_page"),
@ -117,4 +118,7 @@ urlpatterns = [
url(r'^captcha/$', "utils.captcha.views.show_captcha", name="show_captcha"), url(r'^captcha/$', "utils.captcha.views.show_captcha", name="show_captcha"),
url(r'^api/account_security_check/$', AccountSecurityAPIView.as_view(), name="account_security_check"), url(r'^api/account_security_check/$', AccountSecurityAPIView.as_view(), name="account_security_check"),
url(r'^api/contest/time/$', ContestTimeAPIView.as_view(), name="contest_time_api_view"),
url(r'^api/admin/rejudge/$', SubmissionRejudgeAdminAPIView.as_view(), name="submission_rejudge_api"),
] ]

View File

@ -24,8 +24,7 @@ require(["jquery", "avalon", "bootstrap"], function ($, avalon) {
var superAdminNav = [ var superAdminNav = [
{ name: "首页", { name: "首页",
children: [{name: "主页", hash: "#index/index"}, children: [{name: "主页", hash: "#index/index"},
{name: "监控", hash: "#monitor/monitor"}, {name: "监控", hash: "#monitor/monitor"}]
{name: "统计", hash: "#statistics/statistics"}]
}, },
{ {
name: "通用", name: "通用",

View File

@ -51,6 +51,20 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
}, },
showProblemListPage: function(){ showProblemListPage: function(){
vm.$fire("up!showProblemListPage"); vm.$fire("up!showProblemListPage");
},
rejudge: function(submission_id){
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/admin/rejudge/",
method: "post",
data: {"submission_id": submission_id},
success: function(data){
if(!data.code){
bsAlert("重判任务提交成功");
}
}
})
} }
}); });
} }

View File

@ -33,7 +33,9 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
location.href = "/"; location.href = "/";
} }
else { else {
refresh_captcha(); if(applied_captcha) {
refresh_captcha();
}
bsAlert(data.data); bsAlert(data.data);
} }
} }

View File

@ -86,7 +86,7 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"],
function getResult() { function getResult() {
if (counter++ > 10) { if (counter++ > 10) {
hideLoading(); hideLoading();
bsAlert("抱歉,服务器可能出现了故障,请稍后到我的提交列表中查看"); bsAlert("抱歉,服务器正在紧张判题中,请稍后到我的提交列表中查看");
counter = 0; counter = 0;
return; return;
} }
@ -130,6 +130,39 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"],
} }
} }
function getServerTime(){
var contestId = location.pathname.split("/")[2];
var time = 0;
$.ajax({
url: "/api/contest/time/?contest_id=" + contestId + "&type=end",
dataType: "json",
method: "get",
async: false,
success: function(data){
if(!data.code){
time = data.data;
}
},
error: function(){
time = new Date().getTime();
}
});
return time;
}
if(location.href.indexOf("contest") > -1) {
setInterval(function () {
var time = getServerTime();
var minutes = parseInt(time / (1000 * 60));
if(minutes == 0){
bsAlert("比赛即将结束");
}
else if(minutes > 0 && minutes <= 5){
bsAlert("比赛还剩" + minutes.toString() + "分钟");
}
}, 1000 * 60);
}
$("#submit-code-button").click(function () { $("#submit-code-button").click(function () {
var code = codeEditor.getValue(); var code = codeEditor.getValue();

View File

@ -26,3 +26,7 @@ class SubmissionhareSerializer(serializers.Serializer):
submission_id = serializers.CharField(max_length=40) submission_id = serializers.CharField(max_length=40)
class SubmissionRejudgeSerializer(serializers.Serializer):
submission_id = serializers.CharField(max_length=40)

View File

@ -19,7 +19,8 @@ from announcement.models import Announcement
from utils.shortcuts import serializer_invalid_response, error_response, success_response, error_page, paginate from utils.shortcuts import serializer_invalid_response, error_response, success_response, error_page, paginate
from .models import Submission from .models import Submission
from .serializers import CreateSubmissionSerializer, SubmissionSerializer, SubmissionhareSerializer from .serializers import (CreateSubmissionSerializer, SubmissionSerializer,
SubmissionhareSerializer, SubmissionRejudgeSerializer)
logger = logging.getLogger("app_info") logger = logging.getLogger("app_info")
@ -219,3 +220,30 @@ class SubmissionShareAPIView(APIView):
return success_response(submission.shared) return success_response(submission.shared)
else: else:
return serializer_invalid_response(serializer) return serializer_invalid_response(serializer)
class SubmissionRejudgeAdminAPIView(APIView):
def post(self, request):
serializer = SubmissionRejudgeSerializer(data=request.data)
if serializer.is_valid():
submission_id = serializer.data["submission_id"]
try:
submission = Submission.objects.get(id=submission_id)
except Submission.DoesNotExist:
return error_response(u"提交不存在")
# 目前只考虑前台公开题目的重新判题
try:
problem = Problem.objects.get(id=submission.problem_id)
except Problem.DoesNotExist:
return error_response(u"题目不存在")
try:
judge.delay(submission_id, problem.time_limit, problem.memory_limit, problem.test_case_id)
except Exception as e:
logger.error(e)
return error_response(u"提交判题任务失败")
# 增加redis 中判题队列长度的计数器
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
r.incr("judge_queue_length")
else:
return serializer_invalid_response(serializer)

View File

@ -71,45 +71,6 @@
<a ms-attr-href="el.hash">{{ item.name }}</a> <a ms-attr-href="el.hash">{{ item.name }}</a>
</li> </li>
</div> </div>
<!--<li class="list-group-header">首页</li>
<li class="list-group-item" id="li-index-index">
<a href="#index/index">主页</a>
</li>
<li class="list-group-item" id="li-monitor-monitor">
<a href="#monitor/monitor">监控</a>
</li>
<li class="list-group-item" id="li-statistics-statistics">
<a href="#statistics/statistics">统计</a>
</li>
<li class="list-group-header">通用</li>
<li class="list-group-item" id="li-announcement-announcement">
<a href="#announcement/announcement">公告管理</a>
</li>
<li class="list-group-item" id="li-user-user_list">
<a href="#user/user_list">用户管理</a>
</li>
<li class="list-group-header">题目管理</li>
<li class="list-group-item" id="li-problem-problem_list">
<a href="#problem/problem_list">题目列表</a>
</li>
<li class="list-group-item" id="li-problem-add_problem">
<a href="#problem/add_problem">创建题目</a>
</li>
<li class="list-group-header">比赛管理</li>
<li class="list-group-item" id="li-contest-contest_list">
<a href="#contest/contest_list">比赛列表</a>
</li>
<li class="list-group-item" id="li-contest-add_contest">
<a href="#contest/add_contest">创建比赛</a>
</li>
<li class="list-group-header">小组管理</li>
<li class="list-group-item" id="li-group-group">
<a href="#group/group">小组列表</a>
</li>
<li class="list-group-item" id="li-group-join_group_request_list">
<a href="#group/join_group_request_list">加入小组请求</a>
</li>-->
</ul> </ul>
</div> </div>
<!-- admin left end --> <!-- admin left end -->

View File

@ -1 +1 @@
<h1>Hello world</h1> <h1>Online Judge Admin</h1>

View File

@ -22,6 +22,7 @@
<td>{{ results[el.result] }}</td> <td>{{ results[el.result] }}</td>
<td> <td>
<a class="btn btn-info" ms-attr-href="'/submission/' + el.id + '/'" target="_blank">详情</a> <a class="btn btn-info" ms-attr-href="'/submission/' + el.id + '/'" target="_blank">详情</a>
<a class="btn btn-primary" ms-click="rejudge(el.id)">重判</a>
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -45,7 +45,9 @@ java -cp {exe_path} Main</pre>
<li>C/C++ 的64位整数类型请使用 <code>long long</code> 声明,使用 <code>cin/cout</code><code>%lld</code> 输入输出。 <li>C/C++ 的64位整数类型请使用 <code>long long</code> 声明,使用 <code>cin/cout</code><code>%lld</code> 输入输出。
使用<code>__int64</code>会导致编译错误。</li> 使用<code>__int64</code>会导致编译错误。</li>
<li>程序执行时间指CPU时间占用内存按执行过程中内存消耗的峰值计有多组测试数据时以最大的时间和内存消耗为准</li> <li>程序执行时间指CPU时间占用内存按执行过程中内存消耗的峰值计有多组测试数据时以最大的时间和内存消耗为准</li>
<li>判题的时候会去除你的输出的最后的换行和空格,然后与去除最后的换行和空格的答案做比较,如果不一致就是 Wrong Answer。
其余的行末空格和空行不去除,看清楚题目的要求。没有格式错误。
</li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -3,6 +3,7 @@ from django import template
from announcement.models import Announcement from announcement.models import Announcement
def public_announcement_list(): def public_announcement_list():
return Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time") return Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")