Merge branch 'dev'

* dev:
  修补部分没有判断比赛权限的问题;增加比赛调试模式的页面提示
  增加部分注释,修补部分没有判断权限的问题
  增加判断管理员权限的 decorator
  只有管理员才能查看所有人的提交
  修改逻辑
  增加显示所有提交的调试功能
  fix typo
  增加比赛倒计时的功能
  修复 https环境前端获取到的协议是 http 的问题
  增加前台题目的重新判题功能
  删除统计功能
  修复公告列表中用户权限判断错误的情况,抽取代码为 decorator。
  用户不需要验证码的时候刷新验证会导致错误,增加判断
  去除部分 magic number
  比赛如果不是正在进行的状态,就不创建比赛的分数记录和增加比赛题目计数器
This commit is contained in:
virusdefender 2015-09-22 16:21:18 +08:00
commit 317993dffc
33 changed files with 1116 additions and 146 deletions

View File

@ -4,7 +4,7 @@ from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render from django.shortcuts import render
from utils.shortcuts import error_response, error_page from utils.shortcuts import error_response, error_page
from .models import User from .models import User, SUPER_ADMIN
def login_required(func): def login_required(func):
@ -12,7 +12,10 @@ def login_required(func):
def check(*args, **kwargs): def check(*args, **kwargs):
# 在class based views 里面args 有两个元素一个是self, 第二个才是request # 在class based views 里面args 有两个元素一个是self, 第二个才是request
# 在function based views 里面args 只有request 一个参数 # 在function based views 里面args 只有request 一个参数
if len(args) == 2:
request = args[-1] request = args[-1]
else:
request = args[0]
if request.user.is_authenticated(): if request.user.is_authenticated():
return func(*args, **kwargs) return func(*args, **kwargs)
if request.is_ajax(): if request.is_ajax():
@ -25,7 +28,10 @@ def login_required(func):
def admin_required(func): def admin_required(func):
@wraps(func) @wraps(func)
def check(*args, **kwargs): def check(*args, **kwargs):
if len(args) == 2:
request = args[-1] request = args[-1]
else:
request = args[0]
if request.user.is_authenticated() and request.user.admin_type: if request.user.is_authenticated() and request.user.admin_type:
return func(*args, **kwargs) return func(*args, **kwargs)
if request.is_ajax(): if request.is_ajax():
@ -33,3 +39,20 @@ def admin_required(func):
else: else:
return error_page(request, u"需要管理员权限,如果没有登录,请先登录") return error_page(request, u"需要管理员权限,如果没有登录,请先登录")
return check return check
def super_admin_required(func):
@wraps(func)
def check(*args, **kwargs):
if len(args) == 2:
request = args[-1]
else:
request = args[0]
if request.user.is_authenticated() and request.user.admin_type == SUPER_ADMIN:
return func(*args, **kwargs)
if request.is_ajax():
return error_response(u"需要超级管理员权限")
else:
return error_page(request, u"需要超级管理员权限")
return check

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)
except Announcement.DoesNotExist:
return error_page(request, u"公告不存在")
# 公开的公告
if announcement.is_global == 0:
return render(request, "oj/announcement/announcement.html", {"announcement": announcement}) 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 # 已经结束 返回-1
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

@ -16,7 +16,7 @@ from utils.shortcuts import (serializer_invalid_response, error_response,
from account.models import SUPER_ADMIN, User from account.models import SUPER_ADMIN, User
from account.decorators import login_required from account.decorators import login_required
from group.models import Group from group.models import Group
from .models import Contest, ContestProblem, ContestSubmission from .models import Contest, ContestProblem, ContestSubmission, CONTEST_ENDED, CONTEST_NOT_START, CONTEST_UNDERWAY
from .models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST from .models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST
from .decorators import check_user_contest_permission from .decorators import check_user_contest_permission
from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer, from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer,
@ -45,10 +45,10 @@ class ContestAdminAPIView(APIView):
if data["contest_type"] in [PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST]: if data["contest_type"] in [PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST]:
if request.user.admin_type != SUPER_ADMIN: if request.user.admin_type != SUPER_ADMIN:
return error_response(u"只有超级管理员才可创建公开赛") return error_response(u"只有超级管理员才可创建公开赛")
if data["contest_type"] == PASSWORD_PROTECTED_CONTEST: if data["contest_type"] == PASSWORD_PROTECTED_CONTEST:
if not data["password"]: if not data["password"]:
return error_response(u"此比赛为有密码的公开赛,密码不可为空") return error_response(u"此比赛为有密码的公开赛,密码不可为空")
# 没有密码的公开赛 没有密码的小组赛 # 没有密码的公开赛 没有密码的小组赛
elif data["contest_type"] == GROUP_CONTEST: elif data["contest_type"] == GROUP_CONTEST:
if request.user.admin_type == SUPER_ADMIN: if request.user.admin_type == SUPER_ADMIN:
@ -58,7 +58,7 @@ class ContestAdminAPIView(APIView):
if not groups.count(): if not groups.count():
return error_response(u"请至少选择一个小组") return error_response(u"请至少选择一个小组")
if data["start_time"] >= data["end_time"]: if data["start_time"] >= data["end_time"]:
return error_response(u"比赛的开始时间不能晚于或等于比赛结束的时间") return error_response(u"比赛的开始时间必须早于比赛结束的时间")
try: try:
contest = Contest.objects.create(title=data["title"], description=data["description"], contest = Contest.objects.create(title=data["title"], description=data["description"],
mode=data["mode"], contest_type=data["contest_type"], mode=data["mode"], contest_type=data["contest_type"],
@ -86,7 +86,10 @@ class ContestAdminAPIView(APIView):
data = serializer.data data = serializer.data
groups = [] groups = []
try: try:
# 超级管理员可以编辑所有的
contest = Contest.objects.get(id=data["id"]) contest = Contest.objects.get(id=data["id"])
if request.user.admin_type != SUPER_ADMIN:
contest = contest.get(created_by=request.user)
except Contest.DoesNotExist: except Contest.DoesNotExist:
return error_response(u"该比赛不存在!") return error_response(u"该比赛不存在!")
try: try:
@ -109,9 +112,8 @@ class ContestAdminAPIView(APIView):
if not groups.count(): if not groups.count():
return error_response(u"请至少选择一个小组") return error_response(u"请至少选择一个小组")
if data["start_time"] >= data["end_time"]: if data["start_time"] >= data["end_time"]:
return error_response(u"比赛的开始时间不能晚于或等于比赛结束的时间") return error_response(u"比赛的开始时间必须早于比赛结束的时间")
if request.user.admin_type != SUPER_ADMIN and request.user != contest.created_by:
return error_response(u"你无权修改该比赛!")
contest.title = data["title"] contest.title = data["title"]
contest.description = data["description"] contest.description = data["description"]
contest.mode = data["mode"] contest.mode = data["mode"]
@ -163,6 +165,8 @@ class ContestProblemAdminAPIView(APIView):
data = serializer.data data = serializer.data
try: try:
contest = Contest.objects.get(id=data["contest_id"]) contest = Contest.objects.get(id=data["contest_id"])
if request.user.admin_type != SUPER_ADMIN:
contest = contest.get(created_by=request.user)
except Contest.DoesNotExist: except Contest.DoesNotExist:
return error_response(u"比赛不存在") return error_response(u"比赛不存在")
contest_problem = ContestProblem.objects.create(title=data["title"], contest_problem = ContestProblem.objects.create(title=data["title"],
@ -199,7 +203,7 @@ class ContestProblemAdminAPIView(APIView):
return error_response(u"该比赛题目不存在!") return error_response(u"该比赛题目不存在!")
contest = Contest.objects.get(id=contest_problem.contest_id) contest = Contest.objects.get(id=contest_problem.contest_id)
if request.user.admin_type != SUPER_ADMIN and contest.created_by != request.user: if request.user.admin_type != SUPER_ADMIN and contest.created_by != request.user:
return error_response(u"你无权修改该题目!") return error_response(u"比赛不存在")
contest_problem.title = data["title"] contest_problem.title = data["title"]
contest_problem.description = data["description"] contest_problem.description = data["description"]
contest_problem.input_description = data["input_description"] contest_problem.input_description = data["input_description"]
@ -227,29 +231,27 @@ class ContestProblemAdminAPIView(APIView):
if contest_problem_id: if contest_problem_id:
try: try:
contest_problem = ContestProblem.objects.get(id=contest_problem_id) contest_problem = ContestProblem.objects.get(id=contest_problem_id)
if request.user.admin_type != SUPER_ADMIN:
contest_problem = contest_problem.get(created_by=request.user)
return success_response(ContestProblemSerializer(contest_problem).data) return success_response(ContestProblemSerializer(contest_problem).data)
except ContestProblem.DoesNotExist: except ContestProblem.DoesNotExist:
return error_response(u"比赛题目不存在") return error_response(u"比赛题目不存在")
if request.user.admin_type == SUPER_ADMIN:
contest_problem = ContestProblem.objects.all().order_by("sort_index") contest_problems = ContestProblem.objects.all().order_by("sort_index")
else: if request.user.admin_type != SUPER_ADMIN:
contest_problem = ContestProblem.objects.filter(created_by=request.user).order_by("sort_index") contest_problems = contest_problems.filter(created_by=request.user).order_by("sort_index")
visible = request.GET.get("visible", None) visible = request.GET.get("visible", None)
if visible: if visible:
contest_problem = contest_problem.filter(visible=(visible == "true")) contest_problems = contest_problems.filter(visible=(visible == "true"))
keyword = request.GET.get("keyword", None) keyword = request.GET.get("keyword", None)
if keyword: if keyword:
contest_problem = contest_problem.filter(Q(title__contains=keyword) | contest_problems = contest_problems.filter(Q(title__contains=keyword) |
Q(description__contains=keyword)) Q(description__contains=keyword))
contest_id = request.GET.get("contest_id", None) contest_id = request.GET.get("contest_id", None)
if contest_id: if contest_id:
try: contest_problem = contest_problems.filter(contest__id=contest_id).order_by("sort_index")
contest = Contest.objects.get(id=contest_id)
except Contest.DoesNotExist:
return error_response(u"该比赛不存在!")
contest_problem = contest_problem.filter(contest=contest).order_by("sort_index")
return paginate(request, contest_problem, ContestProblemSerializer) return paginate(request, contest_problems, ContestProblemSerializer)
class ContestPasswordVerifyAPIView(APIView): class ContestPasswordVerifyAPIView(APIView):
@ -306,12 +308,23 @@ def contest_problem_page(request, contest_id, contest_problem_id):
pass pass
# 已经结束 # 已经结束
if contest.status == -1: if contest.status == CONTEST_ENDED:
show_warning = True show_warning = True
warning = u"比赛已经结束" warning = u"比赛已经结束"
elif contest.status == CONTEST_NOT_START:
show_warning = True
warning = u"比赛没有开始,您是管理员,可以提交和测试题目,但是目前的提交不会计入排名。"
show_submit_code_area = False
if contest.status == CONTEST_UNDERWAY:
show_submit_code_area = True
if request.user.admin_type == SUPER_ADMIN or request.user == contest.created_by:
show_submit_code_area = True
return render(request, "oj/contest/contest_problem.html", {"contest_problem": contest_problem, "contest": contest, return render(request, "oj/contest/contest_problem.html", {"contest_problem": contest_problem, "contest": contest,
"samples": json.loads(contest_problem.samples), "samples": json.loads(contest_problem.samples),
"show_warning": show_warning, "warning": warning}) "show_warning": show_warning, "warning": warning,
"show_submit_code_area": show_submit_code_area})
@check_user_contest_permission @check_user_contest_permission
@ -319,10 +332,7 @@ def contest_problems_list_page(request, contest_id):
""" """
比赛所有题目的列表页 比赛所有题目的列表页
""" """
try:
contest = Contest.objects.get(id=contest_id) contest = Contest.objects.get(id=contest_id)
except Contest.DoesNotExist:
return error_page(request, u"比赛不存在")
contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index") contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index")
submissions = ContestSubmission.objects.filter(user=request.user, contest=contest) submissions = ContestSubmission.objects.filter(user=request.user, contest=contest)
state = {} state = {}
@ -463,15 +473,12 @@ class ContestTimeAPIView(APIView):
获取比赛开始或者结束的倒计时返回毫秒数字 获取比赛开始或者结束的倒计时返回毫秒数字
""" """
def get(self, request): def get(self, request):
t = request.GET.get("type", "start")
contest_id = request.GET.get("contest_id", -1) contest_id = request.GET.get("contest_id", -1)
try: try:
contest = Contest.objects.get(id=contest_id) contest = Contest.objects.get(id=contest_id)
except Contest.DoesNotExist: except Contest.DoesNotExist:
return error_response(u"比赛不存在") return error_response(u"比赛不存在")
if t == "start": return success_response({"start": int((contest.start_time - now()).total_seconds() * 1000),
# 距离开始还有多长时间 "end": int((contest.end_time - now()).total_seconds() * 1000),
return success_response(int((contest.start_time - now()).total_seconds() * 1000)) "status": contest.status})
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

@ -41,3 +41,5 @@ STATICFILES_DIRS = [os.path.join(BASE_DIR, "static/release/"), os.path.join(BASE
# 模板文件夹 # 模板文件夹
TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'template/release/')] TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'template/release/')]
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

View File

@ -17,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
@ -88,7 +89,6 @@ urlpatterns = [
url(r'^contests/$', "contest.views.contest_list_page", name="contest_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'^contests/(?P<page>\d+)/$', "contest.views.contest_list_page", name="contest_list_page"),
url(r'^contest/(?P<contest_id>\d+)/$', "contest.views.contest_page", name="contest_page"),
url(r'^problem/(?P<problem_id>\d+)/$', "problem.views.problem_page", name="problem_page"), url(r'^problem/(?P<problem_id>\d+)/$', "problem.views.problem_page", name="problem_page"),
@ -119,4 +119,5 @@ urlpatterns = [
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/contest/time/$', ContestTimeAPIView.as_view(), name="contest_time_api_view"),
url(r'^api/admin/rejudge/$', SubmissionRejudgeAdminAPIView.as_view(), name="submission_rejudge_api"),
] ]

28
problem/decorators.py Normal file
View File

@ -0,0 +1,28 @@
# coding=utf-8
from functools import wraps
from account.models import SUPER_ADMIN
from utils.shortcuts import error_response
from .models import Problem
def check_user_problem_permission(func):
@wraps(func)
def check(*args, **kwargs):
# 在class based views 里面args 有两个元素一个是self, 第二个才是request
# 在function based views 里面args 只有request 一个参数
if len(args) == 2:
request = args[-1]
else:
request = args[0]
# 这是在后台使用的url middleware 已经确保用户是登录状态的了
if request.user.admin_type == SUPER_ADMIN:
return func(*args, **kwargs)
try:
Problem.objects.get(id=request.data.get("problem_id", -1), created_by=request.user)
except Problem.DoesNotExist:
return error_response(u"问题不存在")
return func(*args, **kwargs)
return check

View File

@ -4,6 +4,7 @@ import re
import os import os
import hashlib import hashlib
import json import json
import logging
from django.shortcuts import render from django.shortcuts import render
from django.db.models import Q, Count from django.db.models import Q, Count
@ -13,19 +14,23 @@ from rest_framework.views import APIView
from django.conf import settings from django.conf import settings
from account.models import SUPER_ADMIN
from announcement.models import Announcement from account.decorators import super_admin_required
from utils.shortcuts import (serializer_invalid_response, error_response, from utils.shortcuts import (serializer_invalid_response, error_response,
success_response, paginate, rand_str, error_page) success_response, paginate, rand_str, error_page)
from .serizalizers import (CreateProblemSerializer, EditProblemSerializer, ProblemSerializer, from .serizalizers import (CreateProblemSerializer, EditProblemSerializer, ProblemSerializer,
ProblemTagSerializer, CreateProblemTagSerializer) ProblemTagSerializer, CreateProblemTagSerializer)
from .models import Problem, ProblemTag from .models import Problem, ProblemTag
import logging from .decorators import check_user_problem_permission
logger = logging.getLogger("app_info") logger = logging.getLogger("app_info")
def problem_page(request, problem_id): def problem_page(request, problem_id):
"""
前台题目详情页
"""
try: try:
problem = Problem.objects.get(id=problem_id, visible=True) problem = Problem.objects.get(id=problem_id, visible=True)
except Problem.DoesNotExist: except Problem.DoesNotExist:
@ -34,11 +39,15 @@ def problem_page(request, problem_id):
class ProblemTagAdminAPIView(APIView): class ProblemTagAdminAPIView(APIView):
"""
获取所有标签的列表
"""
def get(self, request): def get(self, request):
return success_response(ProblemTagSerializer(ProblemTag.objects.all(), many=True).data) return success_response(ProblemTagSerializer(ProblemTag.objects.all(), many=True).data)
class ProblemAdminAPIView(APIView): class ProblemAdminAPIView(APIView):
@super_admin_required
def post(self, request): def post(self, request):
""" """
题目发布json api接口 题目发布json api接口
@ -72,6 +81,7 @@ class ProblemAdminAPIView(APIView):
else: else:
return serializer_invalid_response(serializer) return serializer_invalid_response(serializer)
@check_user_problem_permission
def put(self, request): def put(self, request):
""" """
题目编辑json api接口 题目编辑json api接口
@ -82,11 +92,7 @@ class ProblemAdminAPIView(APIView):
serializer = EditProblemSerializer(data=request.data) serializer = EditProblemSerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
data = serializer.data data = serializer.data
try:
problem = Problem.objects.get(id=data["id"]) problem = Problem.objects.get(id=data["id"])
except Problem.DoesNotExist:
return error_response(u"该题目不存在!")
problem.title = data["title"] problem.title = data["title"]
problem.description = data["description"] problem.description = data["description"]
problem.input_description = data["input_description"] problem.input_description = data["input_description"]
@ -123,23 +129,36 @@ class ProblemAdminAPIView(APIView):
problem_id = request.GET.get("problem_id", None) problem_id = request.GET.get("problem_id", None)
if problem_id: if problem_id:
try: try:
# 普通管理员只能获取自己创建的题目
# 超级管理员可以获取全部的题目
problem = Problem.objects.get(id=problem_id) problem = Problem.objects.get(id=problem_id)
if request.user.admin_type != SUPER_ADMIN:
problem = problem.get(created_by=request.user)
return success_response(ProblemSerializer(problem).data) return success_response(ProblemSerializer(problem).data)
except Problem.DoesNotExist: except Problem.DoesNotExist:
return error_response(u"题目不存在") return error_response(u"题目不存在")
problem = Problem.objects.all().order_by("-create_time")
# 获取问题列表
problems = Problem.objects.all().order_by("-create_time")
if request.user.admin_type != SUPER_ADMIN:
problems = problems.filter(created_by=request.user)
visible = request.GET.get("visible", None) visible = request.GET.get("visible", None)
if visible: if visible:
problem = problem.filter(visible=(visible == "true")) problems = problems.filter(visible=(visible == "true"))
keyword = request.GET.get("keyword", None) keyword = request.GET.get("keyword", None)
if keyword: if keyword:
problem = problem.filter(Q(title__contains=keyword) | problems = problems.filter(Q(title__contains=keyword) |
Q(description__contains=keyword)) Q(description__contains=keyword))
return paginate(request, problem, ProblemSerializer) return paginate(request, problems, ProblemSerializer)
class TestCaseUploadAPIView(APIView): class TestCaseUploadAPIView(APIView):
"""
上传题目的测试用例
"""
def _is_legal_test_case_file_name(self, file_name): def _is_legal_test_case_file_name(self, file_name):
# 正整数开头的 .in 或者.out 结尾的 # 正整数开头的 .in 或者.out 结尾的
regex = r"^[1-9]\d*\.(in|out)$" regex = r"^[1-9]\d*\.(in|out)$"
@ -237,6 +256,9 @@ class TestCaseUploadAPIView(APIView):
def problem_list_page(request, page=1): def problem_list_page(request, page=1):
"""
前台的问题列表
"""
# 正常情况 # 正常情况
problems = Problem.objects.filter(visible=True) problems = Problem.objects.filter(visible=True)

View File

@ -0,0 +1,14 @@
#timer {
margin: 20px auto;
}
.timer-section {
display: inline-block;
}
.timer-section span {
background-color: #ffffff;
padding: 5px 4px;
border-radius: 5px;
}
.time {
font-weight: bold;
}

View File

@ -2,6 +2,7 @@
@import url("bootstrap/todc-bootstrap.min.css"); @import url("bootstrap/todc-bootstrap.min.css");
@import url("codeMirror/codemirror.css"); @import url("codeMirror/codemirror.css");
@import url("global.css"); @import url("global.css");
@import url("contest/countdown.css");
#language-selector { #language-selector {
width: 130px; width: 130px;

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 {
if(applied_captcha) {
refresh_captcha(); refresh_captcha();
}
bsAlert(data.data); bsAlert(data.data);
} }
} }

View File

@ -0,0 +1,40 @@
require(["jquery", "jcountdown"], function($, jcountdown){
function getServerTime(){
var contestId = location.pathname.split("/")[2];
var time = 0;
$.ajax({
url: "/api/contest/time/?contest_id=" + contestId,
dataType: "json",
method: "get",
async: false,
success: function(data){
if(!data.code){
time = data.data;
}
}
});
return time;
}
var time = getServerTime();
if(time["status"] == 1){
countdown(time["start"])
}
else if(time["status"] == 0){
countdown(time["end"])
}
function countdown(t){
$("#timer").countdown({
serverDiff: t,
date: "september 21, 2015 21:59",
yearsAndMonths: false,
template: $('#template').html()
}).on("countComplete", function(){
location.reload();
});
}
});

View File

@ -142,9 +142,6 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"],
if(!data.code){ if(!data.code){
time = data.data; time = data.data;
} }
},
error: function(){
time = new Date().getTime();
} }
}); });
return time; return time;
@ -153,13 +150,16 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"],
if(location.href.indexOf("contest") > -1) { if(location.href.indexOf("contest") > -1) {
setInterval(function () { setInterval(function () {
var time = getServerTime(); var time = getServerTime();
var minutes = parseInt(time / (1000 * 60)); if(time["status"] == 0){
var minutes = parseInt(time["end"] / (1000 * 60));
if(minutes == 0){ if(minutes == 0){
bsAlert("比赛即将结束"); bsAlert("比赛即将结束");
} }
else if(minutes > 0 && minutes <= 5){ else if(minutes > 0 && minutes <= 5){
bsAlert("比赛还剩" + minutes.toString() + "分钟"); bsAlert("比赛还剩" + minutes.toString() + "分钟");
} }
}
}, 1000 * 60); }, 1000 * 60);
} }

View File

@ -3,6 +3,7 @@ var require = {
baseUrl: "/static/js/", baseUrl: "/static/js/",
paths: { paths: {
jquery: "lib/jquery/jquery", jquery: "lib/jquery/jquery",
jcountdown: "lib/jcountdown/jcountdown",
avalon: "lib/avalon/avalon", avalon: "lib/avalon/avalon",
editor: "utils/editor", editor: "utils/editor",
uploader: "utils/uploader", uploader: "utils/uploader",

View File

@ -0,0 +1,727 @@
/*! jCountdown jQuery Plugin - v2.0.0 - 2015-06-28
* https://github.com/tomgrohl/jCountdown/
* Copyright (c) 2015 Tom Ellis; Licensed MIT */
!function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else {
factory(root.jQuery);
}
}(this, function($) {
$.fn.countdown = function( method /*, options*/ ) {
var msPerHr = 3600000,
secPerYear = 31557600,
secPerMonth = 2629800,
secPerWeek = 604800,
secPerDay = 86400,
secPerHr = 3600,
secPerMin = 60,
secPerSec = 1,
rTemplateTokens = /%y|%m|%w|%d|%h|%i|%s|%ty|%tm|%tw|%td|%th|%ti|%ts/g,
rDigitGlobal = /\d/g,
localNumber = function( numToConvert, settings ) {
var arr = numToConvert.toString().match(rDigitGlobal),
localeNumber = "";
$.each( arr, function(i,num) {
num = Number(num);
localeNumber += (""+ settings.digits[num]) || ""+num;
});
return localeNumber;
},
dateNow = function( $this ) {
var now = new Date(), // Default to local time
settings = $this.data("jcdData");
if ( !settings ) {
return new Date();
}
if ( settings.offset !== null ) {
now = getTimezoneDate( settings.offset );
}
now.setMilliseconds(0);
return now;
},
getTimezoneDate = function( offset ) {
// Returns date now based on timezone/offset
var hrs,
dateMS,
curHrs,
tmpDate = new Date();
if ( offset !== null ) {
hrs = offset * msPerHr;
curHrs = tmpDate.getTime() - ( ( -tmpDate.getTimezoneOffset() / 60 ) * msPerHr ) + hrs;
dateMS = tmpDate.setTime( curHrs );
}
return new Date( dateMS );
},
addEventHandlers = function($element, options) {
// Add event handlers where set
if ( options.onStart ) {
$element.on("start.jcdevt", options.onStart );
}
if ( options.onChange ) {
$element.on("change.jcdevt", options.onChange );
}
if ( options.onComplete ) {
$element.on("complete.jcdevt", options.onComplete );
}
if ( options.onPause ) {
$element.on("pause.jcdevt", options.onPause );
}
if ( options.onResume ) {
$element.on("resume.jcdevt", options.onResume );
}
if ( options.onLocaleChange ) {
$element.on("locale.jcdevt", options.onLocaleChange );
}
},
generateTemplate = function( settings ) {
var template = settings.template,
yearsLeft = settings.yearsLeft,
monthsLeft = settings.monthsLeft,
weeksLeft = settings.weeksLeft,
daysLeft = settings.daysLeft,
hrsLeft = settings.hrsLeft,
minsLeft = settings.minsLeft,
secLeft = settings.secLeft,
hideYears = false,
hideMonths = false,
hideWeeks = false,
hideDays = false,
hideHours = false,
hideMins = false;
if (settings.isRTL) {
template = settings.rtlTemplate;
}
if ( settings.omitZero ) {
if ( settings.yearsAndMonths ) {
if( !settings.yearsLeft ) {
hideYears = true;
}
if( !settings.monthsLeft ) {
hideMonths = true;
}
}
if ( settings.weeks && ( ( settings.yearsAndMonths && hideMonths && !settings.weeksLeft ) || ( !settings.yearsAndMonths && !settings.weeksLeft ) ) ) {
hideWeeks = true;
}
if ( hideWeeks && !daysLeft ) {
hideDays = true;
}
if ( hideDays && !hrsLeft ) {
hideHours = true;
}
if ( hideHours && !minsLeft ) {
hideMins = true;
}
}
if ( settings.leadingZero ) {
if ( yearsLeft < 10 ) {
yearsLeft = "0" + yearsLeft;
}
if ( monthsLeft < 10 ) {
monthsLeft = "0" + monthsLeft;
}
if ( weeksLeft < 10 ) {
weeksLeft = "0" + weeksLeft;
}
if ( daysLeft < 10 ) {
daysLeft = "0" + daysLeft;
}
if ( hrsLeft < 10 ) {
hrsLeft = "0" + hrsLeft;
}
if ( minsLeft < 10 ) {
minsLeft = "0" + minsLeft;
}
if ( secLeft < 10 ) {
secLeft = "0" + secLeft;
}
}
yearsLeft = localNumber( yearsLeft, settings );
monthsLeft = localNumber( monthsLeft, settings );
weeksLeft = localNumber( weeksLeft, settings );
daysLeft = localNumber( daysLeft, settings );
hrsLeft = localNumber( hrsLeft, settings );
minsLeft = localNumber( minsLeft, settings );
secLeft = localNumber( secLeft, settings );
if ( settings.yearsAndMonths ) {
if ( !settings.omitZero || !hideYears ) {
template = template.replace('%y', yearsLeft);
template = template.replace('%ty', (yearsLeft == 1 && settings.yearSingularText) ? settings.yearSingularText : settings.yearText);
}
//Only hide months if years is at 0 as well as months
if ( !settings.omitZero || ( !hideYears && monthsLeft ) || ( !hideYears && !hideMonths ) ) {
template = template.replace('%m', monthsLeft);
template = template.replace('%tm', (monthsLeft == 1 && settings.monthSingularText) ? settings.monthSingularText : settings.monthText);
}
}
if ( settings.weeks && !hideWeeks ) {
template = template.replace('%w', weeksLeft);
template = template.replace('%tw', (weeksLeft == 1 && settings.weekSingularText) ? settings.weekSingularText : settings.weekText);
}
if ( !hideDays ) {
template = template.replace('%d', daysLeft);
template = template.replace('%td', (daysLeft == 1 && settings.daySingularText) ? settings.daySingularText : settings.dayText);
}
if ( !hideHours ) {
template = template.replace('%h', hrsLeft);
template = template.replace('%th', (hrsLeft == 1 && settings.hourSingularText) ? settings.hourSingularText : settings.hourText);
}
if ( !hideMins ) {
template = template.replace('%i', minsLeft);
template = template.replace('%ti', (minsLeft == 1 && settings.minSingularText) ? settings.minSingularText : settings.minText);
}
template = template.replace('%s', secLeft);
template = template.replace('%ts', (secLeft == 1 && settings.secSingularText) ? settings.secSingularText : settings.secText);
// Remove un-used tokens
template = template.replace(rTemplateTokens,'');
return template;
},
updateCallback = function() {
var $this = this,
template,
now,
date,
timeLeft,
yearsLeft = 0,
monthsLeft = 0,
weeksLeft = 0,
daysLeft,
hrsLeft,
minsLeft,
secLeft,
time = "",
diff,
extractSection = function( numSecs ) {
var amount;
amount = Math.floor( diff / numSecs );
diff -= amount * numSecs;
return amount;
},
settings = $this.data("jcdData");
// No settings so return
if ( !settings ) {
return false;
}
now = dateNow( $this );
if ( null !== settings.serverDiff ) {
date = new Date( settings.serverDiff + settings.clientdateNow.getTime() );
}
else {
date = settings.dateObj;
}
date.setMilliseconds(0);
timeLeft = ( settings.direction === "down" ) ? date.getTime() - now.getTime() : now.getTime() - date.getTime();
diff = Math.round( timeLeft / 1000 );
daysLeft = extractSection( secPerDay );
hrsLeft = extractSection( secPerHr );
minsLeft = extractSection( secPerMin );
secLeft = extractSection( secPerSec );
if ( settings.yearsAndMonths ) {
//Add days back on so we can calculate years easier
diff += ( daysLeft * secPerDay );
yearsLeft = extractSection( secPerYear );
monthsLeft = extractSection( secPerMonth );
daysLeft = extractSection( secPerDay );
}
if ( settings.weeks ) {
//Add days back on so we can calculate weeks easier
diff += ( daysLeft * secPerDay );
weeksLeft = extractSection( secPerWeek );
daysLeft = extractSection( secPerDay );
}
/**
* The following 3 option should never be used together!
* MAKE them work for any time
*/
if ( settings.hoursOnly || settings.minsOnly || settings.secsOnly )
{
if ( settings.yearsAndMonths ) {
//Add years, months, weeks and days back on so we can calculate dates easier
diff += ( yearsLeft * secPerYear );
diff += ( monthsLeft * secPerMonth );
yearsLeft = monthsLeft = 0;
}
if ( settings.weeks ) {
diff += ( weeksLeft * secPerWeek );
weeksLeft = 0;
}
}
// Assumes you are using dates within a month ( ~ 30 days )
// as years and months aren't taken into account
if ( settings.hoursOnly ) {
// Add days back on
diff += ( daysLeft * secPerDay );
// Add hours back on
diff += ( hrsLeft * secPerHr );
hrsLeft = extractSection( secPerHr );
}
// Assumes you are only using dates in the near future ( <= 24 hours )
// as years and months aren't taken into account
if ( settings.minsOnly ) {
// Add days back on
diff += ( daysLeft * secPerDay );
daysLeft = 0;
// Add hours back on
diff += ( hrsLeft * secPerHr );
hrsLeft = 0;
diff += ( minsLeft * secPerMin );
minsLeft = extractSection( secPerMin );
}
// Assumes you are only using dates in the near future ( <= 60 minutes )
// as years, months and days aren't taken into account
if ( settings.secsOnly ) {
// Add days back on
diff += ( daysLeft * secPerDay );
daysLeft = 0;
// Add hours back on
diff += ( hrsLeft * secPerHr );
hrsLeft = 0;
// Add minutes back on
diff += ( minsLeft * secPerMin );
minsLeft = 0;
// Add seconds back on
diff += secLeft;
secLeft = extractSection( secPerSec );
}
settings.yearsLeft = yearsLeft;
settings.monthsLeft = monthsLeft;
settings.weeksLeft = weeksLeft;
settings.daysLeft = daysLeft;
settings.hrsLeft = hrsLeft;
settings.minsLeft = minsLeft;
settings.secLeft = secLeft;
$this.data("jcdData", settings);
// Check if the countdown has finished
if ( !( settings.direction === "down" && ( now < date || settings.minus ) ) || !( settings.direction === "up" && ( date < now || settings.minus ) ) ) {
//settings.yearsLeft = settings.monthsLeft = settings.weeksLeft = settings.daysLeft = settings.hrsLeft = settings.minsLeft = settings.secLeft = 0;
//settings.hasCompleted = true;
}
// We've got the time sections set so we can now do the templating
if ( ( settings.direction === "down" && ( now < date || settings.minus ) ) || ( settings.direction === "up" && ( date < now || settings.minus ) ) ) {
time = generateTemplate( settings );
}
else {
settings.yearsLeft = settings.monthsLeft = settings.weeksLeft = settings.daysLeft = settings.hrsLeft = settings.minsLeft = settings.secLeft = 0;
time = generateTemplate( settings );
settings.hasCompleted = true;
}
$this.html( time ).triggerMulti("change.jcdevt,countChange", [settings]);
if ( settings.hasCompleted ) {
$this.triggerMulti("complete.jcdevt,countComplete");
clearInterval( settings.timer );
}
},
methods = {
init: function( options ) {
var opts = $.extend( {}, $.fn.countdown.defaults, options ),
testDate,
testString,
settings = {};
return this.each(function() {
var $this = $(this),
intervalCallback;
// If this element already has a countdown timer
// just change the settings
if ( $this.data("jcdData") ) {
$this.countdown("changeSettings", options, true);
opts = $this.data("jcdData");
}
if ( opts.date === null && opts.dataAttr === null ) {
$.error("No Date passed to jCountdown. date option is required.");
return true;
}
if ( opts.date ) {
testString = opts.date;
} else {
testString = $this.data(opts.dataAttr);
}
testDate = new Date(testString);
if( testDate.toString() === "Invalid Date" ) {
$.error("Invalid Date passed to jCountdown: " + testString);
}
// Setup any event handlers
addEventHandlers($this, options);
// Create a settings object based off the plugin options
settings = $.extend({}, opts);
// Cache DOM elements for templating
settings.dom = {};
settings.dom.$time = $("<"+settings.timeWrapElement+">").addClass(settings.timeWrapClass);
settings.dom.$text = $("<"+settings.textWrapElement+">").addClass(settings.textWrapClass);
settings.clientdateNow = new Date();
settings.clientdateNow.setMilliseconds(0);
settings.originalHTML = $this.html();
settings.dateObj = new Date( testString );
settings.dateObj.setMilliseconds(0);
settings.hasCompleted = false;
settings.timer = 0;
settings.yearsLeft = settings.monthsLeft = settings.weeksLeft = settings.daysLeft = settings.hrsLeft = settings.minsLeft = settings.secLeft = 0;
settings.difference = null;
// Create a callback for the interval
intervalCallback = $.proxy(updateCallback, $this);
settings.timer = setInterval(intervalCallback, settings.updateTime);
$this.data("jcdData", settings).triggerMulti("start.jcdevt,countStart", [settings]);
intervalCallback();
});
},
changeSettings: function( options, internal ) {
// Like resume but with resetting/changing options
return this.each(function() {
var $this = $(this),
settings,
testDate,
func = $.proxy( updateCallback, $this );
if ( !$this.data("jcdData") ) {
return true;
}
settings = $.extend( {}, $this.data("jcdData"), options );
if ( options.hasOwnProperty("date") ) {
testDate = new Date(options.date);
if ( testDate.toString() === "Invalid Date" ) {
$.error("Invalid Date passed to jCountdown: " + options.date);
}
}
settings.completed = false;
settings.dateObj = new Date( options.date );
// Clear the timer, as it might not be needed
clearInterval( settings.timer );
$this.off(".jcdevt").data("jcdData", settings);
// As this can be accessed via the init method as well,
// we need to check how this method is being accessed
if ( !internal ) {
addEventHandlers($this, settings);
settings.timer = setInterval( func, settings.updateTime );
$this.data("jcdData", settings);
func(); //Needs to run straight away when changing settings
}
settings = null;
});
},
resume: function() {
// Resumes a countdown timer
return this.each(function() {
var $this = $(this),
settings = $this.data("jcdData"),
func = $.proxy( updateCallback, $this );
if ( !settings ) {
return true;
}
$this.data("jcdData", settings).triggerMulti("resume.jcdevt,countResume", [settings] );
// We only want to resume a countdown that hasn't finished
if ( !settings.hasCompleted ) {
settings.timer = setInterval( func, settings.updateTime );
if ( settings.stopwatch && settings.direction === "up" ) {
var t = dateNow( $this ).getTime() - settings.pausedAt.getTime(),
d = new Date();
d.setTime( settings.dateObj.getTime() + t );
settings.dateObj = d; //This is internal date
}
func();
}
});
},
pause: function() {
// Pause a countdown timer
return this.each(function() {
var $this = $(this),
settings = $this.data("jcdData");
if ( !settings ) {
return true;
}
if ( settings.stopwatch ) {
settings.pausedAt = dateNow( $this );
}
// Clear interval (Will be started on resume)
clearInterval( settings.timer );
// Trigger pause event handler
$this.data("jcdData", settings).triggerMulti("pause.jcdevt,countPause", [settings] );
});
},
complete: function() {
return this.each(function() {
var $this = $(this),
settings = $this.data("jcdData");
if ( !settings ) {
return true;
}
// Clear timer
clearInterval( settings.timer );
settings.hasCompleted = true;
// Update setting, trigger complete event handler, then unbind all events
// We don"t delete the settings in case they need to be checked later on
$this.data("jcdData", settings).triggerMulti("complete.jcdevt,countComplete", [settings]);
});
},
destroy: function() {
return this.each(function() {
var $this = $(this),
settings = $this.data("jcdData");
if ( !settings ) {
return true;
}
// Clear timer
clearInterval( settings.timer );
// Unbind all events, remove data and put DOM Element back to its original state (HTML wise)
$this.off(".jcdevt").removeData("jcdData").html( settings.originalHTML );
});
},
getSettings: function( name ) {
var $this = $(this),
settings = $this.data("jcdData");
// If an individual setting is required
if ( name && settings ) {
// If it exists, return it
if ( settings.hasOwnProperty( name ) ) {
return settings[name];
}
return undefined;
}
// Return all settings or undefined
return settings;
},
changeLocale: function( locale ) {
var $this = $(this),
settings = $this.data("jcdData");
// If no locale exists error and return false
if ( !$.fn.countdown.locale[locale] ) {
$.error("Locale '" + locale + "' does not exist");
return false;
}
$.extend( settings, $.fn.countdown.locale[locale] );
$this.data("jcdData", settings).triggerMulti("locale.jcdevt,localeChange", [settings]);
return true;
}
};
if( methods[ method ] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) );
} else if ( typeof method === "object" || !method ) {
return methods.init.apply( this, arguments );
} else {
$.error("Method "+ method +" does not exist in the jCountdown Plugin");
}
};
// Expose defaults so we can easily override settings
// Useful for locale plugins
$.fn.countdown.defaults = {
date: null,
dataAttr: null,
updateTime: 1000,
yearText: 'years',
monthText: 'months',
weekText: 'weeks',
dayText: '天',
hourText: '小时',
minText: '分',
secText: '秒',
yearSingularText: 'year',
monthSingularText: 'month',
weekSingularText: 'week',
daySingularText: 'day',
hourSingularText: 'hour',
minSingularText: 'min',
secSingularText: 'sec',
digits : [0,1,2,3,4,5,6,7,8,9],
isRTL: false,
minus: false,
onStart: null,
onChange: null,
onComplete: null,
onResume: null,
onPause: null,
onLocaleChange: null,
leadingZero: false,
offset: null,
serverDiff:null,
hoursOnly: false,
minsOnly: false,
secsOnly: false,
weeks: false,
hours: false,
yearsAndMonths: false,
direction: "down",
stopwatch: false,
omitZero: false,
rtlTemplate: '%ts %s : %ti %i : %th %h : %tm %m : %ty %y',
template: '%y %ty : %m %tm : %h %th : %i %ti : %s %ts'
};
// Locale cache
$.fn.countdown.locale = [];
// Store default english locale so we can switch easier
$.fn.countdown.locale.en = {
yearText: 'years',
monthText: 'months',
weekText: 'weeks',
dayText: 'days',
hourText: 'hours',
minText: 'mins',
secText: 'sec',
yearSingularText: 'year',
monthSingularText: 'month',
weekSingularText: 'week',
daySingularText: 'day',
hourSingularText: 'hour',
minSingularText: 'min',
secSingularText: 'sec',
isRTL: false
};
//
$.fn.triggerMulti = function( eventTypes, extraParameters ) {
var events = eventTypes.split(",");
return this.each(function() {
var $this = $(this);
for ( var i = 0; i < events.length; i++) {
$this.trigger( events[i], extraParameters );
}
});
};
});

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

@ -11,7 +11,7 @@ from rest_framework.views import APIView
from judge.judger_controller.tasks import judge from judge.judger_controller.tasks import judge
from judge.judger_controller.settings import redis_config from judge.judger_controller.settings import redis_config
from account.decorators import login_required from account.decorators import login_required
from account.models import SUPER_ADMIN from account.models import SUPER_ADMIN, User
from problem.models import Problem from problem.models import Problem
from contest.models import ContestProblem, Contest from contest.models import ContestProblem, Contest
@ -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")
@ -160,8 +161,14 @@ def my_submission_list_page(request, page=1):
""" """
我的所有提交的列表页 我的所有提交的列表页
""" """
submissions = Submission.objects.filter(user_id=request.user.id, contest_id__isnull=True). \ # 显示所有人的提交 这是管理员的调试功能
values("id", "problem_id", "result", "create_time", "accepted_answer_time", "language").order_by("-create_time") show_all = request.GET.get("show_all", False) == "true" and request.user.admin_type == SUPER_ADMIN
if show_all:
submissions = Submission.objects.filter(contest_id__isnull=True)
else:
submissions = Submission.objects.filter(user_id=request.user.id, contest_id__isnull=True)
submissions = submissions.values("id", "user_id", "problem_id", "result", "create_time", "accepted_answer_time", "language").order_by("-create_time")
language = request.GET.get("language", None) language = request.GET.get("language", None)
filter = None filter = None
if language: if language:
@ -172,14 +179,21 @@ def my_submission_list_page(request, page=1):
submissions = submissions.filter(result=int(result)) submissions = submissions.filter(result=int(result))
filter = {"name": "result", "content": result} filter = {"name": "result", "content": result}
# 为 submission 查询题目 因为提交页面经常会有重复的题目,缓存一下查询结果 # 因为提交页面经常会有重复的题目和用户,缓存一下查询结果
cache_result = {} cache_result = {"problem": {}, "user": {}}
for item in submissions: for item in submissions:
problem_id = item["problem_id"] problem_id = item["problem_id"]
if problem_id not in cache_result: if problem_id not in cache_result["problem"]:
problem = Problem.objects.get(id=problem_id) problem = Problem.objects.get(id=problem_id)
cache_result[problem_id] = problem.title cache_result["problem"][problem_id] = problem.title
item["title"] = cache_result[problem_id] item["title"] = cache_result["problem"][problem_id]
if show_all:
user_id = item["user_id"]
if user_id not in cache_result["user"]:
user = User.objects.get(id=user_id)
cache_result["user"][user_id] = user
item["user"] = cache_result["user"][user_id]
paginator = Paginator(submissions, 20) paginator = Paginator(submissions, 20)
try: try:
@ -196,13 +210,10 @@ def my_submission_list_page(request, page=1):
except Exception: except Exception:
pass pass
# 右侧的公告列表
announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
return render(request, "oj/submission/my_submissions_list.html", return render(request, "oj/submission/my_submissions_list.html",
{"submissions": current_page, "page": int(page), {"submissions": current_page, "page": int(page),
"previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20, "previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20,
"announcements": announcements, "filter": filter}) "filter": filter, "show_all": show_all})
class SubmissionShareAPIView(APIView): class SubmissionShareAPIView(APIView):
@ -219,3 +230,31 @@ 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")
return success_response(u"任务提交成功")
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

@ -1,6 +1,8 @@
{% load contest %} {% load contest %}
<h2 class="text-center">{{ contest.title }}</h2> <h2 class="text-center">{{ contest.title }}</h2>
<div id="timer" class="text-center"></div>
<hr> <hr>
<div> <div>
<table class="table table-bordered"> <table class="table table-bordered">

View File

@ -21,3 +21,25 @@
{% include "oj/contest/_contest_header.html" %} {% include "oj/contest/_contest_header.html" %}
</div> </div>
{% endblock %} {% endblock %}
{% block js_block %}
<script type="x-template" id="template">
{% if contest.status %}
<strong><span>距离比赛开始还有:</span></strong>
{% else %}
<strong><span>距离比赛结束还有:</span></strong>
{% endif %}
<div class="day-timer timer-section">
<span class="time">%d</span> <span>%td</span>
</div>
<div class="hour-timer timer-section">
<span class="time">%h</span> : <span>%th</span>
</div>
<div class="min-timer timer-section">
<span class="time">%i</span> : <span>%ti</span>
</div>
<div class="second-timer timer-section">
<span class="time">%s</span> : <span>%ts</span>
</div>
</script>
<script src="/static/js/app/oj/contest/contestCountdown.js" defer="defer"></script>
{% endblock %}

View File

@ -60,7 +60,7 @@
<div class="problem-detail">{{ contest_problem.hint|safe }}</div> <div class="problem-detail">{{ contest_problem.hint|safe }}</div>
</div> </div>
{% endif %} {% endif %}
{% ifequal contest.status 0 %} {% if show_submit_code_area %}
<div> <div>
<label>选择语言</label> <label>选择语言</label>
<div> <div>
@ -86,7 +86,7 @@
</button> </button>
<img src="/static/img/loading.gif" id="loading-gif"> <img src="/static/img/loading.gif" id="loading-gif">
</div> </div>
{% endifequal %} {% endif %}
{% if show_warning %} {% if show_warning %}
<div class="alert alert-success" role="alert">{{ warning }}</div> <div class="alert alert-success" role="alert">{{ warning }}</div>
{% endif %} {% endif %}

View File

@ -39,5 +39,25 @@
</div> </div>
{% endblock %} {% endblock %}
{% block js_block %} {% block js_block %}
<script type="x-template" id="template">
{% if contest.status %}
<strong><span>距离比赛开始还有:</span></strong>
{% else %}
<strong><span>距离比赛结束还有:</span></strong>
{% endif %}
<div class="day-timer timer-section">
<span class="time">%d</span> <span>%td</span>
</div>
<div class="hour-timer timer-section">
<span class="time">%h</span> : <span>%th</span>
</div>
<div class="min-timer timer-section">
<span class="time">%i</span> : <span>%ti</span>
</div>
<div class="second-timer timer-section">
<span class="time">%s</span> : <span>%ts</span>
</div>
</script>
<script src="/static/js/app/oj/contest/contestPassword.js"></script> <script src="/static/js/app/oj/contest/contestPassword.js"></script>
<script src="/static/js/app/oj/contest/contestCountdown.js" defer="defer"></script>
{% endblock %} {% endblock %}

View File

@ -27,7 +27,9 @@
<tbody> <tbody>
{% for item in submissions %} {% for item in submissions %}
<tr> <tr>
<th scope="row"><a href="/submission/{{ item.id }}/">{{ forloop.counter }}</a></th> <th scope="row">
<a href="/submission/{{ item.id }}/">{{ forloop.counter }}</a>
</th>
<td>{{ item.create_time }}</td> <td>{{ item.create_time }}</td>
<td> <td>
{{ item.language|translate_language }} {{ item.language|translate_language }}

View File

@ -52,6 +52,7 @@
<tr> <tr>
<th scope="row"> <th scope="row">
<a href="/submission/{{ item.id }}/">{{ forloop.counter |add:start_id }}</a> <a href="/submission/{{ item.id }}/">{{ forloop.counter |add:start_id }}</a>
{% if show_all %}{{ item.user.username }}{% endif %}
</th> </th>
<td> <td>
<a href="/problem/{{ item.problem_id }}/">{{ item.title }}</a> <a href="/problem/{{ item.problem_id }}/">{{ item.title }}</a>

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