mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2025-11-04 14:49:58 +08:00
Merge branch 'dev'
* dev: 修补部分没有判断比赛权限的问题;增加比赛调试模式的页面提示 增加部分注释,修补部分没有判断权限的问题 增加判断管理员权限的 decorator 只有管理员才能查看所有人的提交 修改逻辑 增加显示所有提交的调试功能 fix typo 增加比赛倒计时的功能 修复 https环境前端获取到的协议是 http 的问题 增加前台题目的重新判题功能 删除统计功能 修复公告列表中用户权限判断错误的情况,抽取代码为 decorator。 用户不需要验证码的时候刷新验证会导致错误,增加判断 去除部分 magic number 比赛如果不是正在进行的状态,就不创建比赛的分数记录和增加比赛题目计数器
This commit is contained in:
commit
317993dffc
@ -4,7 +4,7 @@ from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
|
||||
from utils.shortcuts import error_response, error_page
|
||||
from .models import User
|
||||
from .models import User, SUPER_ADMIN
|
||||
|
||||
|
||||
def login_required(func):
|
||||
@ -12,7 +12,10 @@ def login_required(func):
|
||||
def check(*args, **kwargs):
|
||||
# 在class based views 里面,args 有两个元素,一个是self, 第二个才是request,
|
||||
# 在function based views 里面,args 只有request 一个参数
|
||||
request = args[-1]
|
||||
if len(args) == 2:
|
||||
request = args[-1]
|
||||
else:
|
||||
request = args[0]
|
||||
if request.user.is_authenticated():
|
||||
return func(*args, **kwargs)
|
||||
if request.is_ajax():
|
||||
@ -25,7 +28,10 @@ def login_required(func):
|
||||
def admin_required(func):
|
||||
@wraps(func)
|
||||
def check(*args, **kwargs):
|
||||
request = args[-1]
|
||||
if len(args) == 2:
|
||||
request = args[-1]
|
||||
else:
|
||||
request = args[0]
|
||||
if request.user.is_authenticated() and request.user.admin_type:
|
||||
return func(*args, **kwargs)
|
||||
if request.is_ajax():
|
||||
@ -33,3 +39,20 @@ def admin_required(func):
|
||||
else:
|
||||
return error_page(request, u"需要管理员权限,如果没有登录,请先登录")
|
||||
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
|
||||
43
announcement/decorators.py
Normal file
43
announcement/decorators.py
Normal 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
|
||||
@ -18,7 +18,7 @@ class Announcement(models.Model):
|
||||
last_update_time = models.DateTimeField(auto_now=True)
|
||||
# 是否可见 false的话相当于删除
|
||||
visible = models.BooleanField(default=True)
|
||||
# 公告可见范围 0是全局可见 1是部分小组可见,需要在下面的字段中存储可见的小组
|
||||
# 公告可见范围 True 是全局可见 False 是部分小组可见,需要在下面的字段中存储可见的小组
|
||||
is_global = models.BooleanField()
|
||||
groups = models.ManyToManyField(Group)
|
||||
|
||||
|
||||
@ -10,27 +10,13 @@ from group.models import Group
|
||||
from .models import Announcement
|
||||
from .serializers import (CreateAnnouncementSerializer, AnnouncementSerializer,
|
||||
EditAnnouncementSerializer)
|
||||
from .decorators import check_user_announcement_permission
|
||||
|
||||
|
||||
@check_user_announcement_permission
|
||||
def announcement_page(request, announcement_id):
|
||||
try:
|
||||
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})
|
||||
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"公告不存在")
|
||||
announcement = Announcement.objects.get(id=announcement_id, visible=True)
|
||||
return render(request, "oj/announcement/announcement.html", {"announcement": announcement})
|
||||
|
||||
|
||||
class AnnouncementAdminAPIView(APIView):
|
||||
|
||||
@ -3,12 +3,12 @@ from functools import wraps
|
||||
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.utils.timezone import now
|
||||
|
||||
from utils.shortcuts import error_response, error_page
|
||||
|
||||
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):
|
||||
@ -55,7 +55,7 @@ def check_user_contest_permission(func):
|
||||
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 request.is_ajax():
|
||||
@ -65,7 +65,7 @@ def check_user_contest_permission(func):
|
||||
{"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 request.is_ajax():
|
||||
return error_response(u"只有指定小组的可以参加这场比赛")
|
||||
@ -74,7 +74,7 @@ def check_user_contest_permission(func):
|
||||
{"reason": "group_limited", "show_tab": False, "contest": contest})
|
||||
|
||||
# 比赛没有开始
|
||||
if contest.status == 1:
|
||||
if contest.status == CONTEST_NOT_START:
|
||||
if request.is_ajax():
|
||||
return error_response(u"比赛还没有开始")
|
||||
else:
|
||||
|
||||
@ -10,6 +10,10 @@ GROUP_CONTEST = 0
|
||||
PUBLIC_CONTEST = 1
|
||||
PASSWORD_PROTECTED_CONTEST = 2
|
||||
|
||||
CONTEST_NOT_START = 1
|
||||
CONTEST_ENDED = -1
|
||||
CONTEST_UNDERWAY = 0
|
||||
|
||||
|
||||
class Contest(models.Model):
|
||||
title = models.CharField(max_length=40, unique=True)
|
||||
@ -44,13 +48,13 @@ class Contest(models.Model):
|
||||
def status(self):
|
||||
if self.start_time > now():
|
||||
# 没有开始 返回1
|
||||
return 1
|
||||
return CONTEST_NOT_START
|
||||
elif self.end_time < now():
|
||||
# 已经结束 返回0
|
||||
return -1
|
||||
# 已经结束 返回-1
|
||||
return CONTEST_ENDED
|
||||
else:
|
||||
# 正在进行 返回0
|
||||
return 0
|
||||
return CONTEST_UNDERWAY
|
||||
|
||||
class Meta:
|
||||
db_table = "contest"
|
||||
|
||||
@ -16,7 +16,7 @@ from utils.shortcuts import (serializer_invalid_response, error_response,
|
||||
from account.models import SUPER_ADMIN, User
|
||||
from account.decorators import login_required
|
||||
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 .decorators import check_user_contest_permission
|
||||
from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer,
|
||||
@ -45,10 +45,10 @@ class ContestAdminAPIView(APIView):
|
||||
if data["contest_type"] in [PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST]:
|
||||
if request.user.admin_type != SUPER_ADMIN:
|
||||
return error_response(u"只有超级管理员才可创建公开赛")
|
||||
|
||||
if data["contest_type"] == PASSWORD_PROTECTED_CONTEST:
|
||||
if not data["password"]:
|
||||
return error_response(u"此比赛为有密码的公开赛,密码不可为空")
|
||||
|
||||
# 没有密码的公开赛 没有密码的小组赛
|
||||
elif data["contest_type"] == GROUP_CONTEST:
|
||||
if request.user.admin_type == SUPER_ADMIN:
|
||||
@ -58,7 +58,7 @@ class ContestAdminAPIView(APIView):
|
||||
if not groups.count():
|
||||
return error_response(u"请至少选择一个小组")
|
||||
if data["start_time"] >= data["end_time"]:
|
||||
return error_response(u"比赛的开始时间不能晚于或等于比赛结束的时间")
|
||||
return error_response(u"比赛的开始时间必须早于比赛结束的时间")
|
||||
try:
|
||||
contest = Contest.objects.create(title=data["title"], description=data["description"],
|
||||
mode=data["mode"], contest_type=data["contest_type"],
|
||||
@ -86,7 +86,10 @@ class ContestAdminAPIView(APIView):
|
||||
data = serializer.data
|
||||
groups = []
|
||||
try:
|
||||
# 超级管理员可以编辑所有的
|
||||
contest = Contest.objects.get(id=data["id"])
|
||||
if request.user.admin_type != SUPER_ADMIN:
|
||||
contest = contest.get(created_by=request.user)
|
||||
except Contest.DoesNotExist:
|
||||
return error_response(u"该比赛不存在!")
|
||||
try:
|
||||
@ -109,9 +112,8 @@ class ContestAdminAPIView(APIView):
|
||||
if not groups.count():
|
||||
return error_response(u"请至少选择一个小组")
|
||||
if data["start_time"] >= data["end_time"]:
|
||||
return error_response(u"比赛的开始时间不能晚于或等于比赛结束的时间")
|
||||
if request.user.admin_type != SUPER_ADMIN and request.user != contest.created_by:
|
||||
return error_response(u"你无权修改该比赛!")
|
||||
return error_response(u"比赛的开始时间必须早于比赛结束的时间")
|
||||
|
||||
contest.title = data["title"]
|
||||
contest.description = data["description"]
|
||||
contest.mode = data["mode"]
|
||||
@ -163,6 +165,8 @@ class ContestProblemAdminAPIView(APIView):
|
||||
data = serializer.data
|
||||
try:
|
||||
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:
|
||||
return error_response(u"比赛不存在")
|
||||
contest_problem = ContestProblem.objects.create(title=data["title"],
|
||||
@ -199,7 +203,7 @@ class ContestProblemAdminAPIView(APIView):
|
||||
return error_response(u"该比赛题目不存在!")
|
||||
contest = Contest.objects.get(id=contest_problem.contest_id)
|
||||
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.description = data["description"]
|
||||
contest_problem.input_description = data["input_description"]
|
||||
@ -227,29 +231,27 @@ class ContestProblemAdminAPIView(APIView):
|
||||
if contest_problem_id:
|
||||
try:
|
||||
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)
|
||||
except ContestProblem.DoesNotExist:
|
||||
return error_response(u"比赛题目不存在")
|
||||
if request.user.admin_type == SUPER_ADMIN:
|
||||
contest_problem = ContestProblem.objects.all().order_by("sort_index")
|
||||
else:
|
||||
contest_problem = ContestProblem.objects.filter(created_by=request.user).order_by("sort_index")
|
||||
|
||||
contest_problems = ContestProblem.objects.all().order_by("sort_index")
|
||||
if request.user.admin_type != SUPER_ADMIN:
|
||||
contest_problems = contest_problems.filter(created_by=request.user).order_by("sort_index")
|
||||
visible = request.GET.get("visible", None)
|
||||
if visible:
|
||||
contest_problem = contest_problem.filter(visible=(visible == "true"))
|
||||
contest_problems = contest_problems.filter(visible=(visible == "true"))
|
||||
keyword = request.GET.get("keyword", None)
|
||||
if keyword:
|
||||
contest_problem = contest_problem.filter(Q(title__contains=keyword) |
|
||||
contest_problems = contest_problems.filter(Q(title__contains=keyword) |
|
||||
Q(description__contains=keyword))
|
||||
contest_id = request.GET.get("contest_id", None)
|
||||
if contest_id:
|
||||
try:
|
||||
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")
|
||||
contest_problem = contest_problems.filter(contest__id=contest_id).order_by("sort_index")
|
||||
|
||||
return paginate(request, contest_problem, ContestProblemSerializer)
|
||||
return paginate(request, contest_problems, ContestProblemSerializer)
|
||||
|
||||
|
||||
class ContestPasswordVerifyAPIView(APIView):
|
||||
@ -306,12 +308,23 @@ def contest_problem_page(request, contest_id, contest_problem_id):
|
||||
pass
|
||||
|
||||
# 已经结束
|
||||
if contest.status == -1:
|
||||
if contest.status == CONTEST_ENDED:
|
||||
show_warning = True
|
||||
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,
|
||||
"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
|
||||
@ -319,10 +332,7 @@ def contest_problems_list_page(request, contest_id):
|
||||
"""
|
||||
比赛所有题目的列表页
|
||||
"""
|
||||
try:
|
||||
contest = Contest.objects.get(id=contest_id)
|
||||
except Contest.DoesNotExist:
|
||||
return error_page(request, u"比赛不存在")
|
||||
contest = Contest.objects.get(id=contest_id)
|
||||
contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index")
|
||||
submissions = ContestSubmission.objects.filter(user=request.user, contest=contest)
|
||||
state = {}
|
||||
@ -463,15 +473,12 @@ 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))
|
||||
return success_response({"start": int((contest.start_time - now()).total_seconds() * 1000),
|
||||
"end": int((contest.end_time - now()).total_seconds() * 1000),
|
||||
"status": contest.status})
|
||||
|
||||
|
||||
@ -57,6 +57,9 @@ class MessageQueue(object):
|
||||
# 能运行到这里的都是比赛题目
|
||||
try:
|
||||
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)
|
||||
except Contest.DoesNotExist:
|
||||
logger.warning("Submission contest does not exist, submission_id: " + submission_id)
|
||||
|
||||
@ -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/')]
|
||||
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
|
||||
@ -17,7 +17,8 @@ from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView,
|
||||
from admin.views import AdminTemplateView
|
||||
|
||||
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 monitor.views import QueueLengthMonitorAPIView
|
||||
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/(?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"),
|
||||
@ -119,4 +119,5 @@ urlpatterns = [
|
||||
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"),
|
||||
]
|
||||
|
||||
28
problem/decorators.py
Normal file
28
problem/decorators.py
Normal 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
|
||||
@ -4,6 +4,7 @@ import re
|
||||
import os
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.db.models import Q, Count
|
||||
@ -13,19 +14,23 @@ from rest_framework.views import APIView
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
from announcement.models import Announcement
|
||||
from account.models import SUPER_ADMIN
|
||||
from account.decorators import super_admin_required
|
||||
from utils.shortcuts import (serializer_invalid_response, error_response,
|
||||
success_response, paginate, rand_str, error_page)
|
||||
from .serizalizers import (CreateProblemSerializer, EditProblemSerializer, ProblemSerializer,
|
||||
ProblemTagSerializer, CreateProblemTagSerializer)
|
||||
from .models import Problem, ProblemTag
|
||||
import logging
|
||||
from .decorators import check_user_problem_permission
|
||||
|
||||
|
||||
logger = logging.getLogger("app_info")
|
||||
|
||||
|
||||
def problem_page(request, problem_id):
|
||||
"""
|
||||
前台题目详情页
|
||||
"""
|
||||
try:
|
||||
problem = Problem.objects.get(id=problem_id, visible=True)
|
||||
except Problem.DoesNotExist:
|
||||
@ -34,11 +39,15 @@ def problem_page(request, problem_id):
|
||||
|
||||
|
||||
class ProblemTagAdminAPIView(APIView):
|
||||
"""
|
||||
获取所有标签的列表
|
||||
"""
|
||||
def get(self, request):
|
||||
return success_response(ProblemTagSerializer(ProblemTag.objects.all(), many=True).data)
|
||||
|
||||
|
||||
class ProblemAdminAPIView(APIView):
|
||||
@super_admin_required
|
||||
def post(self, request):
|
||||
"""
|
||||
题目发布json api接口
|
||||
@ -72,6 +81,7 @@ class ProblemAdminAPIView(APIView):
|
||||
else:
|
||||
return serializer_invalid_response(serializer)
|
||||
|
||||
@check_user_problem_permission
|
||||
def put(self, request):
|
||||
"""
|
||||
题目编辑json api接口
|
||||
@ -82,11 +92,7 @@ class ProblemAdminAPIView(APIView):
|
||||
serializer = EditProblemSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
data = serializer.data
|
||||
try:
|
||||
problem = Problem.objects.get(id=data["id"])
|
||||
except Problem.DoesNotExist:
|
||||
return error_response(u"该题目不存在!")
|
||||
|
||||
problem = Problem.objects.get(id=data["id"])
|
||||
problem.title = data["title"]
|
||||
problem.description = data["description"]
|
||||
problem.input_description = data["input_description"]
|
||||
@ -123,23 +129,36 @@ class ProblemAdminAPIView(APIView):
|
||||
problem_id = request.GET.get("problem_id", None)
|
||||
if problem_id:
|
||||
try:
|
||||
# 普通管理员只能获取自己创建的题目
|
||||
# 超级管理员可以获取全部的题目
|
||||
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)
|
||||
except Problem.DoesNotExist:
|
||||
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)
|
||||
if visible:
|
||||
problem = problem.filter(visible=(visible == "true"))
|
||||
problems = problems.filter(visible=(visible == "true"))
|
||||
keyword = request.GET.get("keyword", None)
|
||||
if keyword:
|
||||
problem = problem.filter(Q(title__contains=keyword) |
|
||||
problems = problems.filter(Q(title__contains=keyword) |
|
||||
Q(description__contains=keyword))
|
||||
|
||||
return paginate(request, problem, ProblemSerializer)
|
||||
return paginate(request, problems, ProblemSerializer)
|
||||
|
||||
|
||||
class TestCaseUploadAPIView(APIView):
|
||||
"""
|
||||
上传题目的测试用例
|
||||
"""
|
||||
def _is_legal_test_case_file_name(self, file_name):
|
||||
# 正整数开头的 .in 或者.out 结尾的
|
||||
regex = r"^[1-9]\d*\.(in|out)$"
|
||||
@ -237,6 +256,9 @@ class TestCaseUploadAPIView(APIView):
|
||||
|
||||
|
||||
def problem_list_page(request, page=1):
|
||||
"""
|
||||
前台的问题列表
|
||||
"""
|
||||
# 正常情况
|
||||
problems = Problem.objects.filter(visible=True)
|
||||
|
||||
|
||||
14
static/src/css/contest/countdown.css
Normal file
14
static/src/css/contest/countdown.css
Normal 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;
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
@import url("bootstrap/todc-bootstrap.min.css");
|
||||
@import url("codeMirror/codemirror.css");
|
||||
@import url("global.css");
|
||||
@import url("contest/countdown.css");
|
||||
|
||||
#language-selector {
|
||||
width: 130px;
|
||||
|
||||
@ -24,8 +24,7 @@ require(["jquery", "avalon", "bootstrap"], function ($, avalon) {
|
||||
var superAdminNav = [
|
||||
{ name: "首页",
|
||||
children: [{name: "主页", hash: "#index/index"},
|
||||
{name: "监控", hash: "#monitor/monitor"},
|
||||
{name: "统计", hash: "#statistics/statistics"}]
|
||||
{name: "监控", hash: "#monitor/monitor"}]
|
||||
},
|
||||
{
|
||||
name: "通用",
|
||||
|
||||
@ -51,6 +51,20 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
|
||||
},
|
||||
showProblemListPage: function(){
|
||||
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("重判任务提交成功");
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -33,7 +33,9 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
|
||||
location.href = "/";
|
||||
}
|
||||
else {
|
||||
refresh_captcha();
|
||||
if(applied_captcha) {
|
||||
refresh_captcha();
|
||||
}
|
||||
bsAlert(data.data);
|
||||
}
|
||||
}
|
||||
|
||||
40
static/src/js/app/oj/contest/contestCountdown.js
Normal file
40
static/src/js/app/oj/contest/contestCountdown.js
Normal 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();
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -142,9 +142,6 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"],
|
||||
if(!data.code){
|
||||
time = data.data;
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
time = new Date().getTime();
|
||||
}
|
||||
});
|
||||
return time;
|
||||
@ -153,13 +150,16 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"],
|
||||
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() + "分钟");
|
||||
if(time["status"] == 0){
|
||||
var minutes = parseInt(time["end"] / (1000 * 60));
|
||||
if(minutes == 0){
|
||||
bsAlert("比赛即将结束");
|
||||
}
|
||||
else if(minutes > 0 && minutes <= 5){
|
||||
bsAlert("比赛还剩" + minutes.toString() + "分钟");
|
||||
}
|
||||
}
|
||||
|
||||
}, 1000 * 60);
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ var require = {
|
||||
baseUrl: "/static/js/",
|
||||
paths: {
|
||||
jquery: "lib/jquery/jquery",
|
||||
jcountdown: "lib/jcountdown/jcountdown",
|
||||
avalon: "lib/avalon/avalon",
|
||||
editor: "utils/editor",
|
||||
uploader: "utils/uploader",
|
||||
|
||||
727
static/src/js/lib/jcountdown/jcountdown.js
Normal file
727
static/src/js/lib/jcountdown/jcountdown.js
Normal 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 );
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
@ -26,3 +26,7 @@ class SubmissionhareSerializer(serializers.Serializer):
|
||||
submission_id = serializers.CharField(max_length=40)
|
||||
|
||||
|
||||
class SubmissionRejudgeSerializer(serializers.Serializer):
|
||||
submission_id = serializers.CharField(max_length=40)
|
||||
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ from rest_framework.views import APIView
|
||||
from judge.judger_controller.tasks import judge
|
||||
from judge.judger_controller.settings import redis_config
|
||||
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 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 .models import Submission
|
||||
from .serializers import CreateSubmissionSerializer, SubmissionSerializer, SubmissionhareSerializer
|
||||
from .serializers import (CreateSubmissionSerializer, SubmissionSerializer,
|
||||
SubmissionhareSerializer, SubmissionRejudgeSerializer)
|
||||
|
||||
|
||||
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)
|
||||
filter = None
|
||||
if language:
|
||||
@ -172,14 +179,21 @@ def my_submission_list_page(request, page=1):
|
||||
submissions = submissions.filter(result=int(result))
|
||||
filter = {"name": "result", "content": result}
|
||||
|
||||
# 为 submission 查询题目 因为提交页面经常会有重复的题目,缓存一下查询结果
|
||||
cache_result = {}
|
||||
# 因为提交页面经常会有重复的题目和用户,缓存一下查询结果
|
||||
cache_result = {"problem": {}, "user": {}}
|
||||
for item in submissions:
|
||||
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)
|
||||
cache_result[problem_id] = problem.title
|
||||
item["title"] = cache_result[problem_id]
|
||||
cache_result["problem"][problem_id] = problem.title
|
||||
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)
|
||||
try:
|
||||
@ -196,13 +210,10 @@ def my_submission_list_page(request, page=1):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 右侧的公告列表
|
||||
announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
|
||||
|
||||
return render(request, "oj/submission/my_submissions_list.html",
|
||||
{"submissions": current_page, "page": int(page),
|
||||
"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):
|
||||
@ -219,3 +230,31 @@ class SubmissionShareAPIView(APIView):
|
||||
return success_response(submission.shared)
|
||||
else:
|
||||
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)
|
||||
@ -71,45 +71,6 @@
|
||||
<a ms-attr-href="el.hash">{{ item.name }}</a>
|
||||
</li>
|
||||
</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>
|
||||
</div>
|
||||
<!-- admin left end -->
|
||||
|
||||
@ -1 +1 @@
|
||||
<h1>Hello world</h1>
|
||||
<h1>Online Judge Admin</h1>
|
||||
@ -22,6 +22,7 @@
|
||||
<td>{{ results[el.result] }}</td>
|
||||
<td>
|
||||
<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>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
{% load contest %}
|
||||
<h2 class="text-center">{{ contest.title }}</h2>
|
||||
|
||||
<div id="timer" class="text-center"></div>
|
||||
|
||||
<hr>
|
||||
<div>
|
||||
<table class="table table-bordered">
|
||||
|
||||
@ -20,4 +20,26 @@
|
||||
</ul>
|
||||
{% include "oj/contest/_contest_header.html" %}
|
||||
</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 %}
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
<div class="problem-detail">{{ contest_problem.hint|safe }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% ifequal contest.status 0 %}
|
||||
{% if show_submit_code_area %}
|
||||
<div>
|
||||
<label>选择语言</label>
|
||||
<div>
|
||||
@ -86,7 +86,7 @@
|
||||
</button>
|
||||
<img src="/static/img/loading.gif" id="loading-gif">
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% endif %}
|
||||
{% if show_warning %}
|
||||
<div class="alert alert-success" role="alert">{{ warning }}</div>
|
||||
{% endif %}
|
||||
|
||||
@ -39,5 +39,25 @@
|
||||
</div>
|
||||
{% 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/contestPassword.js"></script>
|
||||
<script src="/static/js/app/oj/contest/contestCountdown.js" defer="defer"></script>
|
||||
{% endblock %}
|
||||
@ -27,7 +27,9 @@
|
||||
<tbody>
|
||||
{% for item in submissions %}
|
||||
<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.language|translate_language }}
|
||||
|
||||
@ -52,6 +52,7 @@
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<a href="/submission/{{ item.id }}/">{{ forloop.counter |add:start_id }}</a>
|
||||
{% if show_all %}({{ item.user.username }}){% endif %}
|
||||
</th>
|
||||
<td>
|
||||
<a href="/problem/{{ item.problem_id }}/">{{ item.title }}</a>
|
||||
|
||||
@ -3,6 +3,7 @@ from django import template
|
||||
|
||||
from announcement.models import Announcement
|
||||
|
||||
|
||||
def public_announcement_list():
|
||||
return Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user