diff --git a/contest/models.py b/contest/models.py index 0ece5ff..3611c3a 100644 --- a/contest/models.py +++ b/contest/models.py @@ -9,19 +9,17 @@ from group.models import Group class Contest(models.Model): title = models.CharField(max_length=40, unique=True) description = models.TextField() - # 比赛模式:0 即为是acm模式,1 即为是按照总的 ac 题目数量排名模式,2 即为按照 ac 的题目的总分排名模式 + # 比赛模式:0 即为是acm模式,1 即为是按照总的 ac 题目数量排名模式 mode = models.IntegerField() # 是否显示排名结果 show_rank = models.BooleanField() # 是否显示别人的提交记录 show_user_submission = models.BooleanField() - # 只能超级管理员创建公开赛,管理员只能创建小组内部的比赛 # 如果这一项不为空,即为有密码的公开赛,没有密码的可以为小组赛或者是公开赛(此时用比赛的类型来表示) password = models.CharField(max_length=30, blank=True, null=True) # 比赛的类型: 0 即为是小组赛,1 即为是无密码的公开赛,2 即为是有密码的公开赛 contest_type = models.IntegerField() - # 开始时间 start_time = models.DateTimeField() # 结束时间 diff --git a/contest/serializers.py b/contest/serializers.py index dd9f370..23d8474 100644 --- a/contest/serializers.py +++ b/contest/serializers.py @@ -103,3 +103,8 @@ class EditContestProblemSerializer(serializers.Serializer): sort_index = serializers.CharField(max_length=30) +class ContestPasswordVerifySerializer(serializers.Serializer): + contest_id = serializers.IntegerField() + password = serializers.CharField(max_length=30) + + diff --git a/contest/views.py b/contest/views.py index cabfff9..283f23e 100644 --- a/contest/views.py +++ b/contest/views.py @@ -12,20 +12,15 @@ from utils.shortcuts import (serializer_invalid_response, error_response, success_response, paginate, rand_str, error_page) from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN +from account.decorators import login_required from group.models import Group from announcement.models import Announcement from .models import Contest, ContestProblem from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer, - CreateContestProblemSerializer, ContestProblemSerializer, EditContestProblemSerializer) - - -def contest_page(request, contest_id): - try: - contest = Contest.objects.get(id=contest_id, visible=True) - except Contest.DoesNotExist: - return error_page(request, u"比赛不存在") - return render(request, "oj/contest/problems.html", {"contest": contest}) + CreateContestProblemSerializer, ContestProblemSerializer, + EditContestProblemSerializer, ContestPasswordVerifySerializer, + EditContestProblemSerializer) class ContestAdminAPIView(APIView): @@ -231,6 +226,61 @@ class ContestProblemAdminAPIView(APIView): return paginate(request, contest_problem, ContestProblemSerializer) +class ContestPasswordVerifyAPIView(APIView): + @login_required + def post(self, request): + serializer = ContestPasswordVerifySerializer(data=request.data) + if serializer.is_valid(): + data = request.data + try: + contest = Contest.objects.get(id=data["contest_id"], contest_type=2) + except Contest.DoesNotExist: + return error_response(u"密码错误") + + if data["password"] != contest.password: + return error_response(u" 密码错误") + else: + print request.session.get("contests", None) + if "contests" not in request.session: + request.session["contests"] = [] + request.session["contests"].append(int(data["contest_id"])) + print request.session["contests"] + + return success_response(True) + else: + return serializer_invalid_response(serializer) + + +def check_user_contest_permission(request, contest): + # 有密码的公开赛 + if contest.contest_type == 2: + # 没有输入过密码 + if contest.id not in request.session.get("contests", []): + return {"result": False, "reason": "password_protect"} + + # 指定小组参加的 + if contest.contest_type == 0: + if not contest.groups.filter(id__in=request.user.group_set.all()).exists(): + return {"result": False, "reason": "limited_group"} + return {"result": True} + + +@login_required +def contest_page(request, contest_id): + try: + contest = Contest.objects.get(id=contest_id) + except Contest.DoesNotExist: + return error_page(request, u"比赛不存在") + + Contest.objects.filter(Q(contest_type__in=[1, 2]) | Q(groups__in=request.user.group_set.all())) + + result = check_user_contest_permission(request, contest) + if not result["result"]: + return render(request, "oj/contest/contest_no_privilege.html", {"contest": contest, "reason": result["reason"]}) + + return render(request, "oj/contest/contest_index.html", {"contest": contest}) + + def contest_list_page(request, page=1): # 正常情况 contests = Contest.objects.filter(visible=True) @@ -271,4 +321,4 @@ def contest_list_page(request, page=1): {"contests": current_page, "page": int(page), "previous_page": previous_page, "next_page": next_page, "keyword": keyword, "announcements": announcements, - "join": join, "now": now}) + "join": join, "now": now}) \ No newline at end of file diff --git a/judge/judger_controller/tasks.py b/judge/judger_controller/tasks.py index ceb3f4e..cc3d7d5 100644 --- a/judge/judger_controller/tasks.py +++ b/judge/judger_controller/tasks.py @@ -1,5 +1,5 @@ # coding=utf-8 -import datetime +import json import redis import MySQLdb import subprocess @@ -38,4 +38,5 @@ def judge(submission_id, time_limit, memory_limit, test_case_id): conn.commit() conn.close() r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"]) - r.decr("judge_queue_length") \ No newline at end of file + r.decr("judge_queue_length") + r.lpush("queue", submission_id) diff --git a/mq/__init__.py b/mq/__init__.py new file mode 100644 index 0000000..9bad579 --- /dev/null +++ b/mq/__init__.py @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/mq/models.py b/mq/models.py new file mode 100644 index 0000000..9bad579 --- /dev/null +++ b/mq/models.py @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/mq/scripts/__init__.py b/mq/scripts/__init__.py new file mode 100644 index 0000000..9bad579 --- /dev/null +++ b/mq/scripts/__init__.py @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/mq/scripts/info.py b/mq/scripts/info.py new file mode 100644 index 0000000..b6482ed --- /dev/null +++ b/mq/scripts/info.py @@ -0,0 +1,39 @@ +# coding=utf-8 +import logging +import redis +from judge.judger_controller.settings import redis_config +from judge.judger.result import result +from submission.models import Submission +from problem.models import Problem + +logger = logging.getLogger("app_info") + + +class MessageQueue(object): + def __init__(self): + self.conn = redis.StrictRedis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"]) + self.queue = 'queue' + + def listen_task(self): + while True: + submission_id = self.conn.blpop(self.queue, 0)[1] + logger.debug("receive submission_id: " + submission_id) + try: + submission = Submission.objects.get(id=submission_id) + except Submission.DoesNotExist: + logger.warning("Submission does not exist, submission_id: " + submission_id) + pass + + if submission.result == result["accepted"]: + # 更新题目的 ac 计数器 + try: + problem = Problem.objects.get(id=submission.problem_id) + problem.total_accepted_number += 1 + problem.save() + except Problem.DoesNotExist: + logger.warning("Submission problem does not exist, submission_id: " + submission_id) + pass + + +logger.debug("Start message queue") +MessageQueue().listen_task() diff --git a/oj/settings.py b/oj/settings.py index 472d77e..5703566 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -52,8 +52,10 @@ INSTALLED_APPS = ( 'problem', 'admin', 'submission', + 'mq', 'contest', + 'django_extensions', 'rest_framework', 'rest_framework_swagger', ) @@ -127,10 +129,16 @@ LOGGING = { # 日志格式 }, 'handlers': { - 'file_handler': { + 'django_error': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', - 'filename': LOG_PATH + 'info.log', + 'filename': LOG_PATH + 'django.log', + 'formatter': 'standard' + }, + 'app_info': { + 'level': 'DEBUG', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': LOG_PATH + 'app_info.log', 'formatter': 'standard' }, 'console': { @@ -140,13 +148,13 @@ LOGGING = { } }, 'loggers': { - 'info_logger': { - 'handlers': ['file_handler', "console"], + 'app_info': { + 'handlers': ['app_info', "console"], 'level': 'DEBUG', 'propagate': True }, 'django.request': { - 'handlers': ['file_handler', 'console'], + 'handlers': ['django_error', 'console'], 'level': 'DEBUG', 'propagate': True, }, diff --git a/oj/urls.py b/oj/urls.py index 3898b6e..08b0c93 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -8,7 +8,7 @@ from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterA UserAdminAPIView, UserInfoAPIView) from announcement.views import AnnouncementAdminAPIView -from contest.views import ContestAdminAPIView, ContestProblemAdminAPIView +from contest.views import ContestAdminAPIView, ContestProblemAdminAPIView, ContestPasswordVerifyAPIView from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView, JoinGroupAPIView, JoinGroupRequestAdminAPIView) @@ -69,5 +69,7 @@ urlpatterns = [ url(r'^my_submissions/$', "submission.views.my_submission_list_page", name="my_submission_list_page"), url(r'^my_submissions/(?P\d+)/$', "submission.views.my_submission_list_page", name="my_submission_list_page"), url(r'^api/admin/monitor/$', QueueLengthMonitorAPIView.as_view(), name="queue_length_monitor_api"), + url(r'^contest/(?P\d+)/$', "contest.views.contest_page", name="contest_page"), + url(r'^api/contest/password/$', ContestPasswordVerifyAPIView.as_view(), name="contest_password_verify_api"), ] diff --git a/static/src/js/app/oj/contest/contest_password.js b/static/src/js/app/oj/contest/contest_password.js new file mode 100644 index 0000000..24d785f --- /dev/null +++ b/static/src/js/app/oj/contest/contest_password.js @@ -0,0 +1,24 @@ +require(["jquery", "bsAlert", "csrfToken"], function($, bsAlert, csrfTokenHeader){ + $("#contest-password-btn").click(function(){ + var password = $("#contest-password").val(); + if(!password){ + bsAlert("密码不能为空!"); + return; + } + $.ajax({ + beforeSend: csrfTokenHeader, + url: "/api/contest/password/", + data: {password: password, contest_id: location.href.split("/")[4]}, + method: "post", + dataType: "json", + success: function(data){ + if(!data.code){ + location.reload(); + } + else{ + bsAlert(data.data); + } + } + }) + }) +}); \ 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 cb3127a..009929b 100644 --- a/static/src/js/app/oj/problem/problem.js +++ b/static/src/js/app/oj/problem/problem.js @@ -59,6 +59,7 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert"], function ($, codeMirro if(counter++ > 10){ hideLoading(); bsAlert("抱歉,服务器可能出现了故障,请稍后到我的提交列表中查看"); + counter = 0; return; } $.ajax({ @@ -73,6 +74,7 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert"], function ($, codeMirro setTimeout(getResult, 1000); } else { + counter = 0; hideLoading(); $("#result").html(getResultHtml(data.data)); } diff --git a/submission/views.py b/submission/views.py index 2841909..792660a 100644 --- a/submission/views.py +++ b/submission/views.py @@ -61,18 +61,6 @@ class SubmissionAPIView(APIView): submission = Submission.objects.get(id=submission_id, user_id=request.user.id) except Submission.DoesNotExist: return error_response(u"提交不存在") - # 标记这个submission 已经被统计 - if not submission.is_counted: - submission.is_counted = True - submission.save() - if submission.result == result["accepted"]: - # 更新题目的 ac 计数器 - try: - problem = Problem.objects.get(id=submission.problem_id) - problem.total_accepted_number += 1 - problem.save() - except Problem.DoesNotExist: - pass response_data = {"result": submission.result} if submission.result == 0: response_data["accepted_answer_time"] = submission.accepted_answer_time diff --git a/template/oj/contest/_contest_header.html b/template/oj/contest/_contest_header.html new file mode 100644 index 0000000..62892ce --- /dev/null +++ b/template/oj/contest/_contest_header.html @@ -0,0 +1,38 @@ +{% load contest %} +

{{ contest.title }}

+ +
+
+ + + + + + + + + + + + + + + + {% ifequal contest.contest_type 0 %} + + {% endifequal %} + {% ifequal contest.contest_type 1 %} + + {% endifequal %} + {% ifequal contest.contest_type 2 %} + + {% endifequal %} + + + + +
开始时间结束时间状态比赛类型创建者
{{ contest.start_time }}{{ contest.end_time }}{{ contest|contest_status }}小组赛公开赛公开赛(密码保护){{ contest.created_by.username }}
+
+
{{ contest.description|safe }}
+
+

\ No newline at end of file diff --git a/template/oj/contest/contest_index.html b/template/oj/contest/contest_index.html new file mode 100644 index 0000000..7d32910 --- /dev/null +++ b/template/oj/contest/contest_index.html @@ -0,0 +1,21 @@ +{% extends 'oj_base.html' %} + +{% block body %} +
+ + {% include "oj/contest/_contest_header.html" %} +
+{% endblock %} \ No newline at end of file diff --git a/template/oj/contest/contest_no_privilege.html b/template/oj/contest/contest_no_privilege.html new file mode 100644 index 0000000..12c7fca --- /dev/null +++ b/template/oj/contest/contest_no_privilege.html @@ -0,0 +1,26 @@ +{% extends 'oj_base.html' %} + +{% block body %} +
+ + {% include "oj/contest/_contest_header.html" %} + {% ifequal reason "password_protect" %} +
+
+ + +
+ +
+ {% else %} + + {% endifequal %} +
+{% endblock %} +{% block js_block %} + +{% endblock %} \ No newline at end of file