Accept Merge Request #114 创建了单个比赛的详情页等 : (virusdefender-dev -> dev)

Merge Request: 创建了单个比赛的详情页等
Created By: @virusdefender
Accepted By: @virusdefender
URL: https://coding.net/u/virusdefender/p/qduoj/git/merge/114
This commit is contained in:
virusdefender
2015-08-22 20:55:20 +08:00
16 changed files with 238 additions and 33 deletions

View File

@@ -9,19 +9,17 @@ from group.models import Group
class Contest(models.Model): class Contest(models.Model):
title = models.CharField(max_length=40, unique=True) title = models.CharField(max_length=40, unique=True)
description = models.TextField() description = models.TextField()
# 比赛模式0 即为是acm模式1 即为是按照总的 ac 题目数量排名模式2 即为按照 ac 的题目的总分排名模式 # 比赛模式0 即为是acm模式1 即为是按照总的 ac 题目数量排名模式
mode = models.IntegerField() mode = models.IntegerField()
# 是否显示排名结果 # 是否显示排名结果
show_rank = models.BooleanField() show_rank = models.BooleanField()
# 是否显示别人的提交记录 # 是否显示别人的提交记录
show_user_submission = models.BooleanField() show_user_submission = models.BooleanField()
# 只能超级管理员创建公开赛,管理员只能创建小组内部的比赛 # 只能超级管理员创建公开赛,管理员只能创建小组内部的比赛
# 如果这一项不为空,即为有密码的公开赛,没有密码的可以为小组赛或者是公开赛(此时用比赛的类型来表示) # 如果这一项不为空,即为有密码的公开赛,没有密码的可以为小组赛或者是公开赛(此时用比赛的类型来表示)
password = models.CharField(max_length=30, blank=True, null=True) password = models.CharField(max_length=30, blank=True, null=True)
# 比赛的类型: 0 即为是小组赛1 即为是无密码的公开赛2 即为是有密码的公开赛 # 比赛的类型: 0 即为是小组赛1 即为是无密码的公开赛2 即为是有密码的公开赛
contest_type = models.IntegerField() contest_type = models.IntegerField()
# 开始时间 # 开始时间
start_time = models.DateTimeField() start_time = models.DateTimeField()
# 结束时间 # 结束时间

View File

@@ -103,3 +103,8 @@ class EditContestProblemSerializer(serializers.Serializer):
sort_index = serializers.CharField(max_length=30) sort_index = serializers.CharField(max_length=30)
class ContestPasswordVerifySerializer(serializers.Serializer):
contest_id = serializers.IntegerField()
password = serializers.CharField(max_length=30)

View File

@@ -12,20 +12,15 @@ from utils.shortcuts import (serializer_invalid_response, error_response,
success_response, paginate, rand_str, error_page) success_response, paginate, rand_str, error_page)
from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN
from account.decorators import login_required
from group.models import Group from group.models import Group
from announcement.models import Announcement from announcement.models import Announcement
from .models import Contest, ContestProblem from .models import Contest, ContestProblem
from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer, from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer,
CreateContestProblemSerializer, ContestProblemSerializer, EditContestProblemSerializer) CreateContestProblemSerializer, ContestProblemSerializer,
EditContestProblemSerializer, ContestPasswordVerifySerializer,
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})
class ContestAdminAPIView(APIView): class ContestAdminAPIView(APIView):
@@ -231,6 +226,61 @@ class ContestProblemAdminAPIView(APIView):
return paginate(request, contest_problem, ContestProblemSerializer) 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): def contest_list_page(request, page=1):
# 正常情况 # 正常情况
contests = Contest.objects.filter(visible=True) contests = Contest.objects.filter(visible=True)
@@ -271,4 +321,4 @@ def contest_list_page(request, page=1):
{"contests": current_page, "page": int(page), {"contests": current_page, "page": int(page),
"previous_page": previous_page, "next_page": next_page, "previous_page": previous_page, "next_page": next_page,
"keyword": keyword, "announcements": announcements, "keyword": keyword, "announcements": announcements,
"join": join, "now": now}) "join": join, "now": now})

View File

@@ -1,5 +1,5 @@
# coding=utf-8 # coding=utf-8
import datetime import json
import redis import redis
import MySQLdb import MySQLdb
import subprocess import subprocess
@@ -38,4 +38,5 @@ def judge(submission_id, time_limit, memory_limit, test_case_id):
conn.commit() conn.commit()
conn.close() conn.close()
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"]) r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
r.decr("judge_queue_length") r.decr("judge_queue_length")
r.lpush("queue", submission_id)

1
mq/__init__.py Normal file
View File

@@ -0,0 +1 @@
# coding=utf-8

1
mq/models.py Normal file
View File

@@ -0,0 +1 @@
# coding=utf-8

1
mq/scripts/__init__.py Normal file
View File

@@ -0,0 +1 @@
# coding=utf-8

39
mq/scripts/info.py Normal file
View File

@@ -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()

View File

@@ -52,8 +52,10 @@ INSTALLED_APPS = (
'problem', 'problem',
'admin', 'admin',
'submission', 'submission',
'mq',
'contest', 'contest',
'django_extensions',
'rest_framework', 'rest_framework',
'rest_framework_swagger', 'rest_framework_swagger',
) )
@@ -127,10 +129,16 @@ LOGGING = {
# 日志格式 # 日志格式
}, },
'handlers': { 'handlers': {
'file_handler': { 'django_error': {
'level': 'DEBUG', 'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler', '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' 'formatter': 'standard'
}, },
'console': { 'console': {
@@ -140,13 +148,13 @@ LOGGING = {
} }
}, },
'loggers': { 'loggers': {
'info_logger': { 'app_info': {
'handlers': ['file_handler', "console"], 'handlers': ['app_info', "console"],
'level': 'DEBUG', 'level': 'DEBUG',
'propagate': True 'propagate': True
}, },
'django.request': { 'django.request': {
'handlers': ['file_handler', 'console'], 'handlers': ['django_error', 'console'],
'level': 'DEBUG', 'level': 'DEBUG',
'propagate': True, 'propagate': True,
}, },

View File

@@ -8,7 +8,7 @@ from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterA
UserAdminAPIView, UserInfoAPIView) UserAdminAPIView, UserInfoAPIView)
from announcement.views import AnnouncementAdminAPIView from announcement.views import AnnouncementAdminAPIView
from contest.views import ContestAdminAPIView, ContestProblemAdminAPIView from contest.views import ContestAdminAPIView, ContestProblemAdminAPIView, ContestPasswordVerifyAPIView
from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView, from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView,
JoinGroupAPIView, JoinGroupRequestAdminAPIView) 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/$', "submission.views.my_submission_list_page", name="my_submission_list_page"),
url(r'^my_submissions/(?P<page>\d+)/$', "submission.views.my_submission_list_page", name="my_submission_list_page"), url(r'^my_submissions/(?P<page>\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'^api/admin/monitor/$', QueueLengthMonitorAPIView.as_view(), name="queue_length_monitor_api"),
url(r'^contest/(?P<contest_id>\d+)/$', "contest.views.contest_page", name="contest_page"),
url(r'^api/contest/password/$', ContestPasswordVerifyAPIView.as_view(), name="contest_password_verify_api"),
] ]

View File

@@ -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);
}
}
})
})
});

View File

@@ -59,6 +59,7 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert"], function ($, codeMirro
if(counter++ > 10){ if(counter++ > 10){
hideLoading(); hideLoading();
bsAlert("抱歉,服务器可能出现了故障,请稍后到我的提交列表中查看"); bsAlert("抱歉,服务器可能出现了故障,请稍后到我的提交列表中查看");
counter = 0;
return; return;
} }
$.ajax({ $.ajax({
@@ -73,6 +74,7 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert"], function ($, codeMirro
setTimeout(getResult, 1000); setTimeout(getResult, 1000);
} }
else { else {
counter = 0;
hideLoading(); hideLoading();
$("#result").html(getResultHtml(data.data)); $("#result").html(getResultHtml(data.data));
} }

View File

@@ -61,18 +61,6 @@ class SubmissionAPIView(APIView):
submission = Submission.objects.get(id=submission_id, user_id=request.user.id) submission = Submission.objects.get(id=submission_id, user_id=request.user.id)
except Submission.DoesNotExist: except Submission.DoesNotExist:
return error_response(u"提交不存在") 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} response_data = {"result": submission.result}
if submission.result == 0: if submission.result == 0:
response_data["accepted_answer_time"] = submission.accepted_answer_time response_data["accepted_answer_time"] = submission.accepted_answer_time

View File

@@ -0,0 +1,38 @@
{% load contest %}
<h2 class="text-center">{{ contest.title }}</h2>
<hr>
<div>
<table class="table table-bordered">
<thead>
<tr>
<th>开始时间</th>
<th>结束时间</th>
<th>状态</th>
<th>比赛类型</th>
<th>创建者</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ contest.start_time }}</td>
<td>{{ contest.end_time }}</td>
<td>{{ contest|contest_status }}</td>
{% ifequal contest.contest_type 0 %}
<td>小组赛</td>
{% endifequal %}
{% ifequal contest.contest_type 1 %}
<td>公开赛</td>
{% endifequal %}
{% ifequal contest.contest_type 2 %}
<td>公开赛(密码保护)</td>
{% endifequal %}
<td>{{ contest.created_by.username }}</td>
</tr>
</tbody>
</table>
<hr>
<div>{{ contest.description|safe }}</div>
</div>
<p class="text-center"></p>

View File

@@ -0,0 +1,21 @@
{% extends 'oj_base.html' %}
{% block body %}
<div class="container main">
<ul class="nav nav-tabs nav-tabs-google">
<li role="presentation" class="active">
<a href="/contest/{{ contest.id }}/">比赛详情</a>
</li>
<li role="presentation">
<a href="/contest/{{ contest.id }}/problems/">题目</a>
</li>
<li role="presentation">
<a href="/contest/{{ contest.id }}/submissions/">提交</a>
</li>
<li role="presentation">
<a href="/contest/{{ contest.id }}/rank/">排名</a>
</li>
</ul>
{% include "oj/contest/_contest_header.html" %}
</div>
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends 'oj_base.html' %}
{% block body %}
<div class="container main">
<ul class="nav nav-tabs nav-tabs-google">
<li role="presentation" class="active">
<a href="/contest/{{ contest.id }}/">比赛详情</a>
</li>
</ul>
{% include "oj/contest/_contest_header.html" %}
{% ifequal reason "password_protect" %}
<div class="form-inline">
<div class="form-group">
<label>密码</label>
<input type="password" class="form-control" id="contest-password" placeholder="请输入密码">
</div>
<button type="button" id="contest-password-btn" class="btn btn-primary">提交</button>
</div>
{% else %}
<div class="alert alert-danger" role="alert">比赛仅指定小组可以参加,你不在这些小组中。</div>
{% endifequal %}
</div>
{% endblock %}
{% block js_block %}
<script src="/static/js/app/oj/contest/contest_password.js"></script>
{% endblock %}