From 43be94cce63e1c396bdbce79eeb98dfc52889145 Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Mon, 21 Sep 2015 11:06:11 +0800 Subject: [PATCH 01/15] =?UTF-8?q?=E6=AF=94=E8=B5=9B=E5=A6=82=E6=9E=9C?= =?UTF-8?q?=E4=B8=8D=E6=98=AF=E6=AD=A3=E5=9C=A8=E8=BF=9B=E8=A1=8C=E7=9A=84?= =?UTF-8?q?=E7=8A=B6=E6=80=81=EF=BC=8C=E5=B0=B1=E4=B8=8D=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E6=AF=94=E8=B5=9B=E7=9A=84=E5=88=86=E6=95=B0=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E5=92=8C=E5=A2=9E=E5=8A=A0=E6=AF=94=E8=B5=9B=E9=A2=98=E7=9B=AE?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mq/scripts/mq.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mq/scripts/mq.py b/mq/scripts/mq.py index 567eebb1..b26a46fd 100644 --- a/mq/scripts/mq.py +++ b/mq/scripts/mq.py @@ -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) From 041fd2cb602c72299c1e96fb204be7b93d2fdc69 Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Mon, 21 Sep 2015 11:16:30 +0800 Subject: [PATCH 02/15] =?UTF-8?q?=E5=8E=BB=E9=99=A4=E9=83=A8=E5=88=86=20ma?= =?UTF-8?q?gic=20number?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contest/decorators.py | 10 +++++----- contest/models.py | 10 +++++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/contest/decorators.py b/contest/decorators.py index df73274a..600d98b8 100644 --- a/contest/decorators.py +++ b/contest/decorators.py @@ -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: diff --git a/contest/models.py b/contest/models.py index 7331ec27..e0484555 100644 --- a/contest/models.py +++ b/contest/models.py @@ -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 + return CONTEST_ENDED else: # 正在进行 返回0 - return 0 + return CONTEST_UNDERWAY class Meta: db_table = "contest" From 725d950e27f971cf2308049b938ec523b80abd5d Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Mon, 21 Sep 2015 12:28:22 +0800 Subject: [PATCH 03/15] =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=B8=8D=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E9=AA=8C=E8=AF=81=E7=A0=81=E7=9A=84=E6=97=B6=E5=80=99?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E9=AA=8C=E8=AF=81=E4=BC=9A=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E9=94=99=E8=AF=AF=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/src/js/app/oj/account/login.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/static/src/js/app/oj/account/login.js b/static/src/js/app/oj/account/login.js index 32343c95..22d47593 100644 --- a/static/src/js/app/oj/account/login.js +++ b/static/src/js/app/oj/account/login.js @@ -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); } } From cb0e7cf9382b94299b2df3b9c00d0e3d68aa98aa Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Mon, 21 Sep 2015 12:32:50 +0800 Subject: [PATCH 04/15] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=85=AC=E5=91=8A?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E4=B8=AD=E7=94=A8=E6=88=B7=E6=9D=83=E9=99=90?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E9=94=99=E8=AF=AF=E7=9A=84=E6=83=85=E5=86=B5?= =?UTF-8?q?=EF=BC=8C=E6=8A=BD=E5=8F=96=E4=BB=A3=E7=A0=81=E4=B8=BA=20decora?= =?UTF-8?q?tor=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- announcement/decorators.py | 43 +++++++++++++++++++++++++ announcement/models.py | 2 +- announcement/views.py | 22 +++---------- utils/templatetags/announcement_list.py | 1 + 4 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 announcement/decorators.py diff --git a/announcement/decorators.py b/announcement/decorators.py new file mode 100644 index 00000000..96df987d --- /dev/null +++ b/announcement/decorators.py @@ -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 diff --git a/announcement/models.py b/announcement/models.py index 1dfa6060..c3267f12 100644 --- a/announcement/models.py +++ b/announcement/models.py @@ -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) diff --git a/announcement/views.py b/announcement/views.py index 6790e8ce..3160d9ef 100644 --- a/announcement/views.py +++ b/announcement/views.py @@ -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): diff --git a/utils/templatetags/announcement_list.py b/utils/templatetags/announcement_list.py index acf38d9f..1b2f331e 100644 --- a/utils/templatetags/announcement_list.py +++ b/utils/templatetags/announcement_list.py @@ -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") From 60d727d7b23a07a71d6b8593fb773d22254cf0c5 Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Mon, 21 Sep 2015 13:41:14 +0800 Subject: [PATCH 05/15] =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/src/js/app/admin/admin.js | 3 +-- template/src/admin/admin.html | 39 ----------------------------- template/src/admin/index/index.html | 2 +- 3 files changed, 2 insertions(+), 42 deletions(-) diff --git a/static/src/js/app/admin/admin.js b/static/src/js/app/admin/admin.js index 878b9cad..d68dba11 100644 --- a/static/src/js/app/admin/admin.js +++ b/static/src/js/app/admin/admin.js @@ -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: "通用", diff --git a/template/src/admin/admin.html b/template/src/admin/admin.html index 0e470c6e..7020dae3 100644 --- a/template/src/admin/admin.html +++ b/template/src/admin/admin.html @@ -71,45 +71,6 @@ {{ item.name }} - diff --git a/template/src/admin/index/index.html b/template/src/admin/index/index.html index 61168280..257eab15 100644 --- a/template/src/admin/index/index.html +++ b/template/src/admin/index/index.html @@ -1 +1 @@ -

Hello world

\ No newline at end of file +

Online Judge Admin

\ No newline at end of file From 5f23bff3c5a9040c4292fdd0a3c231e02eb07a71 Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Mon, 21 Sep 2015 13:56:54 +0800 Subject: [PATCH 06/15] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=89=8D=E5=8F=B0?= =?UTF-8?q?=E9=A2=98=E7=9B=AE=E7=9A=84=E9=87=8D=E6=96=B0=E5=88=A4=E9=A2=98?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- oj/urls.py | 4 ++- .../js/app/admin/problem/submissionList.js | 14 +++++++++ submission/serializers.py | 4 +++ submission/views.py | 30 ++++++++++++++++++- .../src/admin/problem/submission_list.html | 1 + 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/oj/urls.py b/oj/urls.py index 6f16acf8..5b65e4c0 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -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 @@ -119,4 +120,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"), ] diff --git a/static/src/js/app/admin/problem/submissionList.js b/static/src/js/app/admin/problem/submissionList.js index 1af2e6ce..e8d00bb0 100644 --- a/static/src/js/app/admin/problem/submissionList.js +++ b/static/src/js/app/admin/problem/submissionList.js @@ -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("重判任务提交成功"); + } + } + + }) } }); } diff --git a/submission/serializers.py b/submission/serializers.py index 8fd62787..3a5308cd 100644 --- a/submission/serializers.py +++ b/submission/serializers.py @@ -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) + + diff --git a/submission/views.py b/submission/views.py index 6eb69fae..396cdfe8 100644 --- a/submission/views.py +++ b/submission/views.py @@ -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") @@ -219,3 +220,30 @@ 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") + else: + return serializer_invalid_response(serializer) \ No newline at end of file diff --git a/template/src/admin/problem/submission_list.html b/template/src/admin/problem/submission_list.html index 28eaebd4..e0880af5 100644 --- a/template/src/admin/problem/submission_list.html +++ b/template/src/admin/problem/submission_list.html @@ -22,6 +22,7 @@ {{ results[el.result] }} 详情 + 重判 From 30df3eadb81339c6046c21851768bb47f0edb16a Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Mon, 21 Sep 2015 23:10:26 +0800 Subject: [PATCH 07/15] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20https=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E5=89=8D=E7=AB=AF=E8=8E=B7=E5=8F=96=E5=88=B0=E7=9A=84?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E6=98=AF=20http=20=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- oj/server_settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/oj/server_settings.py b/oj/server_settings.py index 77ca1fd0..9f8f084e 100644 --- a/oj/server_settings.py +++ b/oj/server_settings.py @@ -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') From 371b9625c207c50a258a27f7fc884ea963841bf6 Mon Sep 17 00:00:00 2001 From: hohoTT <609029365@qq.com> Date: Tue, 22 Sep 2015 12:56:10 +0800 Subject: [PATCH 08/15] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=AF=94=E8=B5=9B?= =?UTF-8?q?=E5=80=92=E8=AE=A1=E6=97=B6=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contest/models.py | 2 +- contest/views.py | 11 +- oj/urls.py | 1 - static/src/css/contest/countdown.css | 14 + static/src/css/oj.css | 1 + .../src/js/app/oj/contest/contestCountdown.js | 40 + static/src/js/app/oj/problem/problem.js | 18 +- static/src/js/config.js | 1 + static/src/js/lib/jcountdown/jcountdown.js | 727 ++++++++++++++++++ template/src/oj/contest/_contest_header.html | 2 + template/src/oj/contest/contest_index.html | 24 +- .../src/oj/contest/no_contest_permission.html | 20 + 12 files changed, 842 insertions(+), 19 deletions(-) create mode 100644 static/src/css/contest/countdown.css create mode 100644 static/src/js/app/oj/contest/contestCountdown.js create mode 100644 static/src/js/lib/jcountdown/jcountdown.js diff --git a/contest/models.py b/contest/models.py index 7331ec27..f67636ca 100644 --- a/contest/models.py +++ b/contest/models.py @@ -46,7 +46,7 @@ class Contest(models.Model): # 没有开始 返回1 return 1 elif self.end_time < now(): - # 已经结束 返回0 + # 已经结束 返回-1 return -1 else: # 正在进行 返回0 diff --git a/contest/views.py b/contest/views.py index b38161a5..1869bb5f 100644 --- a/contest/views.py +++ b/contest/views.py @@ -463,15 +463,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}) + diff --git a/oj/urls.py b/oj/urls.py index 6f16acf8..1ebed5a3 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -88,7 +88,6 @@ urlpatterns = [ url(r'^contests/$', "contest.views.contest_list_page", name="contest_list_page"), url(r'^contests/(?P\d+)/$', "contest.views.contest_list_page", name="contest_list_page"), - url(r'^contest/(?P\d+)/$', "contest.views.contest_page", name="contest_page"), url(r'^problem/(?P\d+)/$', "problem.views.problem_page", name="problem_page"), diff --git a/static/src/css/contest/countdown.css b/static/src/css/contest/countdown.css new file mode 100644 index 00000000..7f7fafd4 --- /dev/null +++ b/static/src/css/contest/countdown.css @@ -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; +} \ No newline at end of file diff --git a/static/src/css/oj.css b/static/src/css/oj.css index cecc0b50..07c13899 100644 --- a/static/src/css/oj.css +++ b/static/src/css/oj.css @@ -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; diff --git a/static/src/js/app/oj/contest/contestCountdown.js b/static/src/js/app/oj/contest/contestCountdown.js new file mode 100644 index 00000000..97807d3a --- /dev/null +++ b/static/src/js/app/oj/contest/contestCountdown.js @@ -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(); + }); + } +}); \ No newline at end of file diff --git a/static/src/js/app/oj/problem/problem.js b/static/src/js/app/oj/problem/problem.js index b018f523..8f76fbec 100644 --- a/static/src/js/app/oj/problem/problem.js +++ b/static/src/js/app/oj/problem/problem.js @@ -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); } diff --git a/static/src/js/config.js b/static/src/js/config.js index ce6e5085..e318d973 100644 --- a/static/src/js/config.js +++ b/static/src/js/config.js @@ -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", diff --git a/static/src/js/lib/jcountdown/jcountdown.js b/static/src/js/lib/jcountdown/jcountdown.js new file mode 100644 index 00000000..370c8fd6 --- /dev/null +++ b/static/src/js/lib/jcountdown/jcountdown.js @@ -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 ); + } + }); + }; + +}); diff --git a/template/src/oj/contest/_contest_header.html b/template/src/oj/contest/_contest_header.html index bcf4cabe..3fcac447 100644 --- a/template/src/oj/contest/_contest_header.html +++ b/template/src/oj/contest/_contest_header.html @@ -1,6 +1,8 @@ {% load contest %}

{{ contest.title }}

+
+
diff --git a/template/src/oj/contest/contest_index.html b/template/src/oj/contest/contest_index.html index 7b977a03..0c98523d 100644 --- a/template/src/oj/contest/contest_index.html +++ b/template/src/oj/contest/contest_index.html @@ -20,4 +20,26 @@ {% include "oj/contest/_contest_header.html" %} -{% endblock %} \ No newline at end of file +{% endblock %} +{% block js_block %} + + +{% endblock %} diff --git a/template/src/oj/contest/no_contest_permission.html b/template/src/oj/contest/no_contest_permission.html index dedc81fd..f7418894 100644 --- a/template/src/oj/contest/no_contest_permission.html +++ b/template/src/oj/contest/no_contest_permission.html @@ -39,5 +39,25 @@ {% endblock %} {% block js_block %} + + {% endblock %} \ No newline at end of file From 0718b4c521213248a07456205ac4996237425703 Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Tue, 22 Sep 2015 13:09:01 +0800 Subject: [PATCH 09/15] fix typo --- submission/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/submission/views.py b/submission/views.py index 396cdfe8..70ee700e 100644 --- a/submission/views.py +++ b/submission/views.py @@ -245,5 +245,6 @@ class SubmissionRejudgeAdminAPIView(APIView): # 增加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) \ No newline at end of file From 5ac16a3c7f5dbad73e2851a0123be7c23a1f8f92 Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Tue, 22 Sep 2015 14:28:46 +0800 Subject: [PATCH 10/15] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E6=89=80=E6=9C=89=E6=8F=90=E4=BA=A4=E7=9A=84=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- submission/views.py | 30 ++++++++++++------- .../src/oj/problem/my_submissions_list.html | 5 +++- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/submission/views.py b/submission/views.py index 70ee700e..88c6d0dc 100644 --- a/submission/views.py +++ b/submission/views.py @@ -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 @@ -162,7 +162,11 @@ 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") + values("id", "user_id", "problem_id", "result", "create_time", "accepted_answer_time", "language").order_by("-create_time") + + # 显示所有人的提交 这是管理员的调试功能 + show_all = request.GET.get("show_all", False) == "true" + language = request.GET.get("language", None) filter = None if language: @@ -173,14 +177,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: @@ -197,13 +208,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): diff --git a/template/src/oj/problem/my_submissions_list.html b/template/src/oj/problem/my_submissions_list.html index dd915a17..4f69d6fd 100644 --- a/template/src/oj/problem/my_submissions_list.html +++ b/template/src/oj/problem/my_submissions_list.html @@ -27,7 +27,10 @@ {% for item in submissions %} - +
{{ forloop.counter }} + {{ forloop.counter }} + {% if show_all %}{{ item.user.username }}{% endif %} + {{ item.create_time }} {{ item.language|translate_language }} From f0a70c3b0942ebaae6805686c69e3f36aaf86008 Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Tue, 22 Sep 2015 14:37:45 +0800 Subject: [PATCH 11/15] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- submission/views.py | 8 +++++--- template/src/oj/problem/my_submissions_list.html | 1 - template/src/oj/submission/my_submissions_list.html | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/submission/views.py b/submission/views.py index 88c6d0dc..c0487e51 100644 --- a/submission/views.py +++ b/submission/views.py @@ -161,11 +161,13 @@ def my_submission_list_page(request, page=1): """ 我的所有提交的列表页 """ - submissions = Submission.objects.filter(user_id=request.user.id, contest_id__isnull=True). \ - values("id", "user_id", "problem_id", "result", "create_time", "accepted_answer_time", "language").order_by("-create_time") - # 显示所有人的提交 这是管理员的调试功能 show_all = request.GET.get("show_all", False) == "true" + 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 diff --git a/template/src/oj/problem/my_submissions_list.html b/template/src/oj/problem/my_submissions_list.html index 4f69d6fd..faa9d628 100644 --- a/template/src/oj/problem/my_submissions_list.html +++ b/template/src/oj/problem/my_submissions_list.html @@ -29,7 +29,6 @@
{{ forloop.counter }} - {% if show_all %}{{ item.user.username }}{% endif %} {{ item.create_time }} diff --git a/template/src/oj/submission/my_submissions_list.html b/template/src/oj/submission/my_submissions_list.html index 1e9975fd..251c5e8c 100644 --- a/template/src/oj/submission/my_submissions_list.html +++ b/template/src/oj/submission/my_submissions_list.html @@ -52,6 +52,7 @@
{{ forloop.counter |add:start_id }} + {% if show_all %}({{ item.user.username }}){% endif %} {{ item.title }} From a57544db1d096a566fa3da09cf29fdca523b50b7 Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Tue, 22 Sep 2015 16:17:53 +0800 Subject: [PATCH 12/15] =?UTF-8?q?=E5=8F=AA=E6=9C=89=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=91=98=E6=89=8D=E8=83=BD=E6=9F=A5=E7=9C=8B=E6=89=80=E6=9C=89?= =?UTF-8?q?=E4=BA=BA=E7=9A=84=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- submission/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submission/views.py b/submission/views.py index c0487e51..05dbd1ed 100644 --- a/submission/views.py +++ b/submission/views.py @@ -162,7 +162,7 @@ def my_submission_list_page(request, page=1): 我的所有提交的列表页 """ # 显示所有人的提交 这是管理员的调试功能 - show_all = request.GET.get("show_all", False) == "true" + 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: From 172d45eb78092f8a2f229af7e2a7eb3c4201042d Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Tue, 22 Sep 2015 16:18:32 +0800 Subject: [PATCH 13/15] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=91=98=E6=9D=83=E9=99=90=E7=9A=84=20decora?= =?UTF-8?q?tor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- account/decorators.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/account/decorators.py b/account/decorators.py index 4113dd01..1181c6a5 100644 --- a/account/decorators.py +++ b/account/decorators.py @@ -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 \ No newline at end of file From 707e38ac79e0260a60c7a8d1b23a2282fc924797 Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Tue, 22 Sep 2015 16:19:08 +0800 Subject: [PATCH 14/15] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=EF=BC=8C=E4=BF=AE=E8=A1=A5=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E5=88=A4=E6=96=AD=E6=9D=83=E9=99=90=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- problem/decorators.py | 28 ++++++++++++++++++++++++++ problem/views.py | 46 ++++++++++++++++++++++++++++++++----------- 2 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 problem/decorators.py diff --git a/problem/decorators.py b/problem/decorators.py new file mode 100644 index 00000000..762c54a7 --- /dev/null +++ b/problem/decorators.py @@ -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 diff --git a/problem/views.py b/problem/views.py index dbe125ad..8dc42d63 100644 --- a/problem/views.py +++ b/problem/views.py @@ -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) From 389c1905a6f4dd1a75c3e4d6ed901ad332cb1188 Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Tue, 22 Sep 2015 16:19:40 +0800 Subject: [PATCH 15/15] =?UTF-8?q?=E4=BF=AE=E8=A1=A5=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E5=88=A4=E6=96=AD=E6=AF=94=E8=B5=9B=E6=9D=83?= =?UTF-8?q?=E9=99=90=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=9B=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=AF=94=E8=B5=9B=E8=B0=83=E8=AF=95=E6=A8=A1=E5=BC=8F=E7=9A=84?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contest/views.py | 60 ++++++++++++-------- template/src/oj/contest/contest_problem.html | 4 +- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/contest/views.py b/contest/views.py index 1869bb5f..8e0a2ac4 100644 --- a/contest/views.py +++ b/contest/views.py @@ -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 = {} diff --git a/template/src/oj/contest/contest_problem.html b/template/src/oj/contest/contest_problem.html index 64cff29a..0b07c566 100644 --- a/template/src/oj/contest/contest_problem.html +++ b/template/src/oj/contest/contest_problem.html @@ -60,7 +60,7 @@
{{ contest_problem.hint|safe }}
{% endif %} - {% ifequal contest.status 0 %} + {% if show_submit_code_area %}
@@ -86,7 +86,7 @@
- {% endifequal %} + {% endif %} {% if show_warning %} {% endif %}