Merge branch 'dev'

* dev: (195 commits)
  修改刷新时间
  增加题目页面倒计时的 js
  增加比赛倒计时的 api
  增加判题帮助
  修复判断验证码是否存在的时候,用户不存在导致的报错
  记录用户输出 md5
  增加反馈链接
  add docker start tool
  rename mq
  不用的语言使用不同的系统调用过滤
  update java runtime security policy
  增加 clone 地址范围限制,否则 Java 无法运行
  fix mq run path error
  修复语言判断 bug
  add kill proc
  修改codeMirror中代码的样式
  修复数据库已有用户problems_statu字段为空造成的问题
  fix typo
  add c/c++ sys call filter
  --isolate-process true
  ...

Conflicts:
	judge/judger/settings.py
	judge/judger_controller/settings.py
	template/src/oj/contest/contest_problem.html
	template/src/oj/contest/submissions_list.html
This commit is contained in:
virusdefender
2015-09-21 13:06:12 +08:00
141 changed files with 6085 additions and 3652 deletions

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
static/src/js/lib/* linguist-vendored
static/src/js/require.js linguist-vendored
static/src/js/r.js linguist-vendored

3
.gitignore vendored
View File

@@ -58,6 +58,9 @@ log/
static/release/css
static/release/js
static/release/img
static/src/upload_image/*
build.txt
tmp/
test_case/
release/
upload/

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Qingdao 青岛大学信息工程学院创新实验室
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,33 @@
# OnlineJudge
基于 Python 和 Django的在线评测平台。
文档https://www.zybuluo.com/virusdefender/note/171932
TODO
- 完善文档,目前还差很多
- 完善测试
- 搭建 demo 站点
![oj_previewindex.png][1]
![preview.jpeg][2]
![oj_preview_submission.png][3]
![contest][4]
![contest_rank_edit][5]
![admin_problem][6]
![admin_contest][7]
[1]: https://dn-virusdefender-blog-cdn.qbox.me/oj_previewindex.png
[2]: https://dn-virusdefender-blog-cdn.qbox.me/oj_previewproblem.png
[3]: https://dn-virusdefender-blog-cdn.qbox.me/oj_previewsubmission.png
[4]: https://dn-virusdefender-blog-cdn.qbox.me/oj_previewcontest.png
[5]: https://dn-virusdefender-blog-cdn.qbox.me/oj_previewcontest_rank.png?edit
[6]: https://dn-virusdefender-blog-cdn.qbox.me/oj_previewadmin_problem.png
[7]: https://dn-virusdefender-blog-cdn.qbox.me/oj_previewadmin_contest.png

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('account', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='user',
name='problems_status',
field=models.TextField(blank=True),
),
]

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('account', '0002_user_problems_status'),
]
operations = [
migrations.AlterField(
model_name='user',
name='problems_status',
field=models.TextField(default=b'{}'),
),
]

View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('account', '0003_auto_20150915_2025'),
]
operations = [
migrations.RemoveField(
model_name='user',
name='problems_status',
),
]

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('account', '0004_remove_user_problems_status'),
]
operations = [
migrations.AddField(
model_name='user',
name='problems_status',
field=models.TextField(default=b'{}'),
),
]

View File

@@ -30,6 +30,8 @@ class User(AbstractBaseUser):
create_time = models.DateTimeField(auto_now_add=True)
# 0代表不是管理员 1是普通管理员 2是超级管理员
admin_type = models.IntegerField(default=0)
# JSON字典用来表示该用户的问题的解决状态 1为ac2为正在进行
problems_status = models.TextField(default="{}")
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = []

View File

@@ -7,6 +7,7 @@ from .models import User
class UserLoginSerializer(serializers.Serializer):
username = serializers.CharField(max_length=30)
password = serializers.CharField(max_length=30)
captcha = serializers.CharField(required=False, min_length=4, max_length=4)
class UsernameCheckSerializer(serializers.Serializer):
@@ -22,11 +23,13 @@ class UserRegisterSerializer(serializers.Serializer):
real_name = serializers.CharField(max_length=30)
password = serializers.CharField(max_length=30, min_length=6)
email = serializers.EmailField(max_length=254)
captcha = serializers.CharField(max_length=4, min_length=4)
class UserChangePasswordSerializer(serializers.Serializer):
old_password = serializers.CharField()
new_password = serializers.CharField(max_length=30, min_length=6)
captcha = serializers.CharField(max_length=4, min_length=4)
class UserSerializer(serializers.ModelSerializer):

View File

@@ -5,14 +5,15 @@ from django.shortcuts import render
from django.db.models import Q
from rest_framework.views import APIView
from rest_framework.response import Response
from utils.shortcuts import serializer_invalid_response, error_response, success_response, paginate
from utils.captcha import Captcha
from .decorators import login_required
from .models import User
from .serializers import (UserLoginSerializer, UsernameCheckSerializer,
UserRegisterSerializer, UserChangePasswordSerializer,
EmailCheckSerializer, UserSerializer, EditUserSerializer)
UserRegisterSerializer, UserChangePasswordSerializer,
EmailCheckSerializer, UserSerializer, EditUserSerializer)
class UserLoginAPIView(APIView):
@@ -28,6 +29,12 @@ class UserLoginAPIView(APIView):
user = auth.authenticate(username=data["username"], password=data["password"])
# 用户名或密码错误的话 返回None
if user:
if user.admin_type > 0:
if "captcha" not in data:
return error_response(u"请填写验证码!")
captcha = Captcha(request)
if not captcha.check(data["captcha"]):
return error_response(u"验证码错误")
auth.login(request, user)
return success_response(u"登录成功")
else:
@@ -35,11 +42,24 @@ class UserLoginAPIView(APIView):
else:
return serializer_invalid_response(serializer)
@login_required
def logout(request):
auth.logout(request)
return http.HttpResponseRedirect("/")
def index_page(request):
if not request.user.is_authenticated():
return render(request, "oj/index.html")
try:
if request.META['HTTP_REFERER']:
return render(request, "oj/index.html")
except KeyError:
return http.HttpResponseRedirect('/problems/')
class UserRegisterAPIView(APIView):
def post(self, request):
"""
@@ -50,6 +70,9 @@ class UserRegisterAPIView(APIView):
serializer = UserRegisterSerializer(data=request.data)
if serializer.is_valid():
data = serializer.data
captcha = Captcha(request)
if not captcha.check(data["captcha"]):
return error_response(u"验证码错误")
try:
User.objects.get(username=data["username"])
return error_response(u"用户名已存在")
@@ -79,6 +102,9 @@ class UserChangePasswordAPIView(APIView):
serializer = UserChangePasswordSerializer(data=request.data)
if serializer.is_valid():
data = serializer.data
captcha = Captcha(request)
if not captcha.check(data["captcha"]):
return error_response(u"验证码错误")
username = request.user.username
user = auth.authenticate(username=username, password=data["old_password"])
if user:
@@ -92,39 +118,35 @@ class UserChangePasswordAPIView(APIView):
class UsernameCheckAPIView(APIView):
def post(self, request):
def get(self, request):
"""
检测用户名是否存在,存在返回True,不存在返回False
检测用户名是否存在,存在返回状态码400,不存在返回200
---
request_serializer: UsernameCheckSerializer
"""
serializer = UsernameCheckSerializer(data=request.data)
if serializer.is_valid():
username = request.GET.get("username", None)
if username:
try:
User.objects.get(username=serializer.data["username"])
return success_response(True)
User.objects.get(username=username)
return Response(status=400)
except User.DoesNotExist:
return success_response(False)
else:
return serializer_invalid_response(serializer)
return Response(status=200)
return Response(status=200)
class EmailCheckAPIView(APIView):
def post(self, request):
def get(self, request):
"""
检测邮箱是否存在,存在返回True,不存在返回False
检测邮箱是否存在,存在返回状态码400,不存在返回200
---
request_serializer: EmailCheckSerializer
"""
serializer = EmailCheckSerializer(data=request.data)
if serializer.is_valid():
email = request.GET.get("email", None)
if email:
try:
User.objects.get(email=serializer.data["email"])
return success_response(True)
User.objects.get(email=email)
return Response(status=400)
except User.DoesNotExist:
return success_response(False)
else:
return serializer_invalid_response(serializer)
return Response(status=200)
return Response(status=200)
class UserAdminAPIView(APIView):
@@ -189,3 +211,19 @@ class UserInfoAPIView(APIView):
response_serializer: UserSerializer
"""
return success_response(UserSerializer(request.user).data)
class AccountSecurityAPIView(APIView):
def get(self, request):
"""
判断用户登录是否需要验证码
---
"""
username = request.GET.get("username", None)
if username:
try:
User.objects.get(username=username, admin_type__gt=0)
except User.DoesNotExist:
return success_response({"applied_captcha": False})
return success_response({"applied_captcha": True})
return success_response({"applied_captcha": False})

View File

@@ -16,8 +16,21 @@ def announcement_page(request, announcement_id):
try:
announcement = Announcement.objects.get(id=announcement_id, visible=True)
except Announcement.DoesNotExist:
return error_page(request, u"模板不存在")
return render(request, "oj/announcement/announcement.html", {"announcement": announcement})
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"公告不存在")
class AnnouncementAdminAPIView(APIView):

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('contest', '0006_merge'),
]
operations = [
migrations.AddField(
model_name='contestsubmission',
name='ac_time',
field=models.IntegerField(default=0),
),
]

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('contest', '0007_contestsubmission_ac_time'),
]
operations = [
migrations.RenameField(
model_name='contest',
old_name='show_rank',
new_name='real_time_rank',
),
]

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('contest', '0008_auto_20150912_1912'),
]
operations = [
migrations.AddField(
model_name='contestsubmission',
name='first_achieved',
field=models.BooleanField(default=False),
),
]

View File

@@ -6,20 +6,25 @@ from account.models import User
from problem.models import AbstractProblem
from group.models import Group
GROUP_CONTEST = 0
PUBLIC_CONTEST = 1
PASSWORD_PROTECTED_CONTEST = 2
class Contest(models.Model):
title = models.CharField(max_length=40, unique=True)
description = models.TextField()
# 比赛模式0 即为是acm模式1 即为是按照总的 ac 题目数量排名模式
mode = models.IntegerField()
# 是否显示排名结果
show_rank = models.BooleanField()
# 是否显示实时排名结果
real_time_rank = models.BooleanField()
# 是否显示别人的提交记录
show_user_submission = models.BooleanField()
# 只能超级管理员创建公开赛,管理员只能创建小组内部的比赛
# 如果这一项不为空,即为有密码的公开赛,没有密码的可以为小组赛或者是公开赛(此时用比赛的类型来表示)
password = models.CharField(max_length=30, blank=True, null=True)
# 比赛的类型: 0 即为是小组赛1 即为是无密码的公开赛2 即为是有密码的公开赛
# 比赛的类型: 0 即为是小组赛(GROUP_CONTEST)1 即为是无密码的公开赛(PUBLIC_CONTEST)
# 2 即为是有密码的公开赛(PASSWORD_PUBLIC_CONTEST)
contest_type = models.IntegerField()
# 开始时间
start_time = models.DateTimeField()
@@ -84,8 +89,12 @@ class ContestSubmission(models.Model):
total_submission_number = models.IntegerField(default=1)
# 这道题是 AC 还是没过
ac = models.BooleanField()
# ac 用时以秒计
ac_time = models.IntegerField(default=0)
# 总的时间用于acm 类型的,也需要保存罚时
total_time = models.IntegerField(default=0)
# 第一个解出此题目
first_achieved = models.BooleanField(default=False)
class Meta:
db_table = "contest_submission"

View File

@@ -13,7 +13,7 @@ class CreateContestSerializer(serializers.Serializer):
description = serializers.CharField(max_length=5000)
mode = serializers.IntegerField()
contest_type = serializers.IntegerField()
show_rank = serializers.BooleanField()
real_time_rank = serializers.BooleanField()
show_user_submission = serializers.BooleanField()
password = serializers.CharField(max_length=30, required=False, default=None)
start_time = serializers.DateTimeField()
@@ -47,7 +47,7 @@ class EditContestSerializer(serializers.Serializer):
description = serializers.CharField(max_length=10000)
mode = serializers.IntegerField()
contest_type = serializers.IntegerField()
show_rank = serializers.BooleanField()
real_time_rank = serializers.BooleanField()
show_user_submission = serializers.BooleanField()
password = serializers.CharField(max_length=30, required=False, default=None)
start_time = serializers.DateTimeField()

View File

@@ -1,18 +1,16 @@
# coding=utf-8
import json
from django.core.urlresolvers import reverse
from django.test import TestCase, Client
from django.http import HttpResponse
from rest_framework.test import APITestCase, APIClient
from account.models import User
from group.models import Group
from contest.models import Contest, ContestProblem
from .models import ContestSubmission
from announcement.models import Announcement
from .models import GROUP_CONTEST, PASSWORD_PROTECTED_CONTEST
from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN
from decorators import check_user_contest_permission
class ContestAdminAPITest(APITestCase):
@@ -32,15 +30,17 @@ class ContestAdminAPITest(APITestCase):
join_group_setting=0, visible=True,
admin=user2)
self.group2 = Group.objects.create(name="group2", description="des0",
join_group_setting=0, visible=True,
admin=user1)
join_group_setting=0, visible=True,
admin=user1)
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
contest_type=2, show_rank=True, show_user_submission=True,
contest_type=PASSWORD_PROTECTED_CONTEST, show_rank=True,
show_user_submission=True,
start_time="2015-08-15T10:00:00.000Z",
end_time="2015-08-15T12:00:00.000Z",
password="aacc", created_by=User.objects.get(username="test1"))
self.group_contest = Contest.objects.create(title="titley", description="descriptiony", mode=1,
contest_type=2, show_rank=True, show_user_submission=True,
contest_type=PASSWORD_PROTECTED_CONTEST, show_rank=True,
show_user_submission=True,
start_time="2015-08-15T10:00:00.000Z",
end_time="2015-08-15T12:00:00.000Z",
password="aacc", created_by=User.objects.get(username="test1"))
@@ -54,7 +54,7 @@ class ContestAdminAPITest(APITestCase):
def test_global_contest_does_not_has_privileges(self):
self.client.login(username="test2", password="testbb")
data = {"title": "title0", "description": "description0", "mode": 1, "contest_type": 2,
data = {"title": "title0", "description": "description0", "mode": 1, "contest_type": PASSWORD_PROTECTED_CONTEST,
"show_rank": True, "show_user_submission": True, "start_time": "2015-08-15T10:00:00.000Z",
"end_time": "2015-08-15T12:00:00.000Z", "password": "aabb", "visible": True}
response = self.client.post(self.url, data=data)
@@ -62,7 +62,7 @@ class ContestAdminAPITest(APITestCase):
def test_global_contest_password_exists(self):
self.client.login(username="test1", password="testaa")
data = {"title": "title0", "description": "description0", "mode": 1, "contest_type": 2,
data = {"title": "title0", "description": "description0", "mode": 1, "contest_type": PASSWORD_PROTECTED_CONTEST,
"show_rank": True, "show_user_submission": True, "start_time": "2015-08-15T10:00:00.000Z",
"end_time": "2015-08-15T12:00:00.000Z", "visible": True}
response = self.client.post(self.url, data=data)
@@ -70,7 +70,7 @@ class ContestAdminAPITest(APITestCase):
def test_group_contest_group_at_least_one(self):
self.client.login(username="test1", password="testaa")
data = {"title": "title0", "description": "description0", "mode": 1, "contest_type": 0,
data = {"title": "title0", "description": "description0", "mode": 1, "contest_type": GROUP_CONTEST,
"show_rank": True, "show_user_submission": True, "start_time": "2015-08-15T10:00:00.000Z",
"end_time": "2015-08-15T12:00:00.000Z", "visible": True}
response = self.client.post(self.url, data=data)
@@ -78,7 +78,7 @@ class ContestAdminAPITest(APITestCase):
def test_global_contest_successfully(self):
self.client.login(username="test1", password="testaa")
data = {"title": "title1", "description": "description1", "mode": 1, "contest_type": 2,
data = {"title": "title1", "description": "description1", "mode": 1, "contest_type": PASSWORD_PROTECTED_CONTEST,
"show_rank": True, "show_user_submission": True, "start_time": "2015-08-15T10:00:00.000Z",
"end_time": "2015-08-15T12:00:00.000Z", "password": "aabb", "visible": True}
response = self.client.post(self.url, data=data)
@@ -86,7 +86,7 @@ class ContestAdminAPITest(APITestCase):
def test_group_contest_super_admin_successfully(self):
self.client.login(username="test1", password="testaa")
data = {"title": "title3", "description": "description3", "mode": 1, "contest_type": 0,
data = {"title": "title3", "description": "description3", "mode": 1, "contest_type": GROUP_CONTEST,
"show_rank": True, "show_user_submission": True, "start_time": "2015-08-15T10:00:00.000Z",
"end_time": "2015-08-15T12:00:00.000Z", "groups": [self.group.id], "visible": True}
response = self.client.post(self.url, data=data)
@@ -94,7 +94,7 @@ class ContestAdminAPITest(APITestCase):
def test_group_contest_admin_successfully(self):
self.client.login(username="test2", password="testbb")
data = {"title": "title6", "description": "description6", "mode": 2, "contest_type": 0,
data = {"title": "title6", "description": "description6", "mode": 2, "contest_type": GROUP_CONTEST,
"show_rank": True, "show_user_submission": True, "start_time": "2015-08-15T10:00:00.000Z",
"end_time": "2015-08-15T12:00:00.000Z", "groups": [self.group.id], "visible": True}
response = self.client.post(self.url, data=data)
@@ -102,7 +102,7 @@ class ContestAdminAPITest(APITestCase):
def test_time_error(self):
self.client.login(username="test1", password="testaa")
data = {"title": "title2", "description": "description2", "mode": 1, "contest_type": 2,
data = {"title": "title2", "description": "description2", "mode": 1, "contest_type": PASSWORD_PROTECTED_CONTEST,
"show_rank": True, "show_user_submission": True, "start_time": "2015-08-15T12:00:00.000Z",
"end_time": "2015-08-15T10:00:00.000Z", "password": "aabb", "visible": True}
response = self.client.post(self.url, data=data)
@@ -110,7 +110,7 @@ class ContestAdminAPITest(APITestCase):
def test_contest_has_exists(self):
self.client.login(username="test1", password="testaa")
data = {"title": "titlex", "description": "descriptionx", "mode": 1, "contest_type": 2,
data = {"title": "titlex", "description": "descriptionx", "mode": 1, "contest_type": PASSWORD_PROTECTED_CONTEST,
"show_rank": True, "show_user_submission": True, "start_time": "2015-08-15T10:00:00.000Z",
"end_time": "2015-08-15T12:00:00.000Z", "password": "aabb", "visible": True}
response = self.client.post(self.url, data=data)
@@ -126,7 +126,7 @@ class ContestAdminAPITest(APITestCase):
def test_contest_does_not_exist(self):
self.client.login(username="test1", password="testaa")
data = {"id": self.global_contest.id + 10, "title": "title2", "description": "description2", "mode": 1,
"contest_type": 2, "show_rank": True, "show_user_submission": True,
"contest_type": PASSWORD_PROTECTED_CONTEST, "show_rank": True, "show_user_submission": True,
"start_time": "2015-08-15T10:00:00.000Z", "end_time": "2015-08-15T12:00:00.000Z", "password": "aabb",
"visible": True}
response = self.client.put(self.url, data=data)
@@ -135,7 +135,7 @@ class ContestAdminAPITest(APITestCase):
def test_edit_global_contest_successfully(self):
self.client.login(username="test1", password="testaa")
data = {"id": self.global_contest.id, "title": "titlez", "description": "descriptionz", "mode": 1,
"contest_type": 2, "show_rank": True, "show_user_submission": True,
"contest_type": PASSWORD_PROTECTED_CONTEST, "show_rank": True, "show_user_submission": True,
"start_time": "2015-08-15T10:00:00.000Z", "end_time": "2015-08-15T13:00:00.000Z", "password": "aabb",
"visible": True}
response = self.client.put(self.url, data=data)
@@ -146,7 +146,7 @@ class ContestAdminAPITest(APITestCase):
def test_edit_group_contest_successfully(self):
self.client.login(username="test1", password="testaa")
data = {"id": self.group_contest.id, "title": "titleyyy", "description": "descriptionyyyy", "mode": 1,
"contest_type": 0, "show_rank": True, "show_user_submission": True,
"contest_type": GROUP_CONTEST, "show_rank": True, "show_user_submission": True,
"start_time": "2015-08-15T10:00:00.000Z", "end_time": "2015-08-15T13:00:00.000Z",
"groups": [self.group.id], "visible": False}
response = self.client.put(self.url, data=data)
@@ -158,7 +158,7 @@ class ContestAdminAPITest(APITestCase):
def test_edit_group_contest_unsuccessfully(self):
self.client.login(username="test2", password="testbb")
data = {"id": self.group_contest.id, "title": "titleyyy", "description": "descriptionyyyy", "mode": 1,
"contest_type": 0, "show_rank": True, "show_user_submission": True,
"contest_type": GROUP_CONTEST, "show_rank": True, "show_user_submission": True,
"start_time": "2015-08-15T10:00:00.000Z", "end_time": "2015-08-15T13:00:00.000Z",
"groups": [self.group.id], "visible": False}
response = self.client.put(self.url, data=data)
@@ -167,7 +167,7 @@ class ContestAdminAPITest(APITestCase):
def test_edit_group_at_least_one(self):
self.client.login(username="test1", password="testaa")
data = {"id": self.group_contest.id, "title": "titleyyy", "description": "descriptionyyyy", "mode": 1,
"contest_type": 0, "show_rank": True, "show_user_submission": True,
"contest_type": GROUP_CONTEST, "show_rank": True, "show_user_submission": True,
"start_time": "2015-08-15T10:00:00.000Z", "end_time": "2015-08-15T13:00:00.000Z", "visible": True}
response = self.client.put(self.url, data=data)
self.assertEqual(response.data, {"code": 1, "data": u"请至少选择一个小组"})
@@ -175,7 +175,7 @@ class ContestAdminAPITest(APITestCase):
def test_edit_contest_has_exists(self):
self.client.login(username="test1", password="testaa")
data = {"id": self.global_contest.id, "title": "titley", "description": "descriptiony", "mode": 1,
"contest_type": 2, "show_rank": True, "show_user_submission": True,
"contest_type": PASSWORD_PROTECTED_CONTEST, "show_rank": True, "show_user_submission": True,
"start_time": "2015-08-15T10:00:00.000Z", "end_time": "2015-08-15T12:00:00.000Z", "password": "aabb",
"visible": True}
response = self.client.put(self.url, data=data)
@@ -184,7 +184,7 @@ class ContestAdminAPITest(APITestCase):
def test_edit_global_contest_does_not_has_privileges(self):
self.client.login(username="test2", password="testbb")
data = {"id": self.global_contest.id, "title": "titlexxxxxxxxx", "description": "descriptionxxxxxx", "mode": 1,
"contest_type": 2, "show_rank": True, "show_user_submission": True,
"contest_type": PASSWORD_PROTECTED_CONTEST, "show_rank": True, "show_user_submission": True,
"start_time": "2015-08-15T10:00:00.000Z", "end_time": "2015-08-15T12:00:00.000Z", "password": "aabb",
"visible": True}
response = self.client.put(self.url, data=data)
@@ -193,8 +193,7 @@ class ContestAdminAPITest(APITestCase):
def test_edit_global_contest_password_exists(self):
self.client.login(username="test1", password="testaa")
data = {"id": self.global_contest.id, "title": "title0", "description": "description0", "mode": 1,
"contest_type": 2,
"show_rank": True, "show_user_submission": True, "start_time": "2015-08-15T10:00:00.000Z",
"contest_type": PASSWORD_PROTECTED_CONTEST, "show_rank": True, "show_user_submission": True, "start_time": "2015-08-15T10:00:00.000Z",
"end_time": "2015-08-15T12:00:00.000Z", "visible": True}
response = self.client.put(self.url, data=data)
self.assertEqual(response.data, {"code": 1, "data": u"此比赛为有密码的公开赛,密码不可为空"})
@@ -202,7 +201,7 @@ class ContestAdminAPITest(APITestCase):
def test_edit_time_error(self):
self.client.login(username="test1", password="testaa")
data = {"id": self.global_contest.id, "title": "titleaaaa", "description": "descriptionaaaaa", "mode": 1,
"contest_type": 2, "show_rank": True, "show_user_submission": True,
"contest_type": PASSWORD_PROTECTED_CONTEST, "show_rank": True, "show_user_submission": True,
"start_time": "2015-08-15T12:00:00.000Z", "end_time": "2015-08-15T10:00:00.000Z", "password": "aabb",
"visible": True}
response = self.client.put(self.url, data=data)
@@ -245,7 +244,8 @@ class ContestProblemAdminAPItEST(APITestCase):
self.user3.save()
self.client.login(username="test1", password="testaa")
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
contest_type=2, show_rank=True, show_user_submission=True,
contest_type=PASSWORD_PROTECTED_CONTEST, show_rank=True,
show_user_submission=True,
start_time="2015-08-15T10:00:00.000Z",
end_time="2015-08-15T12:00:00.000Z",
password="aacc", created_by=User.objects.get(username="test1"))
@@ -373,7 +373,7 @@ class ContestProblemAdminAPItEST(APITestCase):
def test_query_contest_problem_exists_by_contest_id(self):
self.client.login(username="test3", password="testaa")
response = self.client.get(self.url + "?contest_id="+ str(self.global_contest.id))
response = self.client.get(self.url + "?contest_id=" + str(self.global_contest.id))
self.assertEqual(response.data["code"], 0)
self.assertEqual(len(response.data["data"]), 0)
@@ -414,7 +414,8 @@ class ContestPasswordVerifyAPITest(APITestCase):
self.user2.save()
self.client.login(username="test1", password="testaa")
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
contest_type=2, show_rank=True, show_user_submission=True,
contest_type=PASSWORD_PROTECTED_CONTEST, show_rank=True,
show_user_submission=True,
start_time="2015-08-15T10:00:00.000Z",
end_time="2015-08-15T12:00:00.000Z",
password="aacc", created_by=User.objects.get(username="test1"))
@@ -453,7 +454,8 @@ class ContestPageTest(TestCase):
self.user1.save()
self.client.login(username="test1", password="testaa")
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
contest_type=2, show_rank=True, show_user_submission=True,
contest_type=PASSWORD_PROTECTED_CONTEST, show_rank=True,
show_user_submission=True,
start_time="2015-08-15T10:00:00.000Z",
end_time="2015-08-15T12:00:00.000Z",
password="aacc", created_by=User.objects.get(username="test1"))
@@ -476,7 +478,8 @@ class ContestProblemPageTest(TestCase):
self.user1.save()
self.client.login(username="test1", password="testaa")
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
contest_type=2, show_rank=True, show_user_submission=True,
contest_type=PASSWORD_PROTECTED_CONTEST, show_rank=True,
show_user_submission=True,
start_time="2015-08-15T10:00:00.000Z",
end_time="2015-08-15T12:00:00.000Z",
password="aacc", created_by=User.objects.get(username="test1"))
@@ -519,7 +522,8 @@ class ContestProblemListPageTest(TestCase):
self.user1.save()
self.client.login(username="test1", password="testaa")
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
contest_type=2, show_rank=True, show_user_submission=True,
contest_type=PASSWORD_PROTECTED_CONTEST, show_rank=True,
show_user_submission=True,
start_time="2015-08-15T10:00:00.000Z",
end_time="2015-08-15T12:00:00.000Z",
password="aacc", created_by=User.objects.get(username="test1"))
@@ -555,7 +559,8 @@ class ContestListPageTest(TestCase):
self.url = reverse('contest_list_page')
self.client.login(username="test1", password="testaa")
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
contest_type=2, show_rank=True, show_user_submission=True,
contest_type=PASSWORD_PROTECTED_CONTEST, show_rank=True,
show_user_submission=True,
start_time="2015-08-15T10:00:00.000Z",
end_time="2015-08-15T12:00:00.000Z",
password="aacc", created_by=User.objects.get(username="test1"))

View File

@@ -1,28 +1,30 @@
# coding=utf-8
import json
import datetime
from functools import wraps
from django.utils.timezone import now
from django.shortcuts import render
from django.db import IntegrityError
from django.utils import dateparse
from django.db.models import Q, Count, Sum
from django.db.models import Q, Sum
from django.core.paginator import Paginator
from rest_framework.views import APIView
from utils.shortcuts import (serializer_invalid_response, error_response,
success_response, paginate, rand_str, error_page)
from django.utils.timezone import now
from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN, User
from rest_framework.views import APIView
from utils.shortcuts import (serializer_invalid_response, error_response,
success_response, paginate, error_page)
from account.models import SUPER_ADMIN, User
from account.decorators import login_required
from group.models import Group
from announcement.models import Announcement
from .models import Contest, ContestProblem, ContestSubmission
from .models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST
from .decorators import check_user_contest_permission
from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer,
CreateContestProblemSerializer, ContestProblemSerializer,
EditContestProblemSerializer, ContestPasswordVerifySerializer,
ContestPasswordVerifySerializer,
EditContestProblemSerializer)
from oj.settings import REDIS_CACHE
import redis
class ContestAdminAPIView(APIView):
@@ -37,17 +39,18 @@ class ContestAdminAPIView(APIView):
if serializer.is_valid():
data = serializer.data
groups = []
# 首先判断比赛的类型: 0 即为是小组赛1 即为是无密码的公开赛2 即为是有密码的公开赛
# 首先判断比赛的类型: 0 即为是小组赛(GROUP_CONTEST)1 即为是无密码的公开赛(PUBLIC_CONTEST)
# 2 即为是有密码的公开赛(PASSWORD_PUBLIC_CONTEST)
# 此时为有密码的公开赛,并且此时只能超级管理员才有权限此创建比赛
if data["contest_type"] in [1, 2]:
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"] == 2:
if data["contest_type"] == PASSWORD_PROTECTED_CONTEST:
if not data["password"]:
return error_response(u"此比赛为有密码的公开赛,密码不可为空")
# 没有密码的公开赛 没有密码的小组赛
elif data["contest_type"] == 0:
elif data["contest_type"] == GROUP_CONTEST:
if request.user.admin_type == SUPER_ADMIN:
groups = Group.objects.filter(id__in=data["groups"])
else:
@@ -59,7 +62,7 @@ class ContestAdminAPIView(APIView):
try:
contest = Contest.objects.create(title=data["title"], description=data["description"],
mode=data["mode"], contest_type=data["contest_type"],
show_rank=data["show_rank"], password=data["password"],
real_time_rank=data["real_time_rank"], password=data["password"],
show_user_submission=data["show_user_submission"],
start_time=dateparse.parse_datetime(data["start_time"]),
end_time=dateparse.parse_datetime(data["end_time"]),
@@ -92,13 +95,13 @@ class ContestAdminAPIView(APIView):
return error_response(u"该比赛名称已经存在")
except Contest.DoesNotExist:
pass
if data["contest_type"] in [1, 2]:
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"] == 2:
if data["contest_type"] == PASSWORD_PROTECTED_CONTEST:
if not data["password"]:
return error_response(u"此比赛为有密码的公开赛,密码不可为空")
elif data["contest_type"] == 0:
elif data["contest_type"] == GROUP_CONTEST:
if request.user.admin_type == SUPER_ADMIN:
groups = Group.objects.filter(id__in=data["groups"])
else:
@@ -113,7 +116,7 @@ class ContestAdminAPIView(APIView):
contest.description = data["description"]
contest.mode = data["mode"]
contest.contest_type = data["contest_type"]
contest.show_rank = data["show_rank"]
contest.real_time_rank = data["real_time_rank"]
contest.show_user_submission = data["show_user_submission"]
contest.start_time = dateparse.parse_datetime(data["start_time"])
contest.end_time = dateparse.parse_datetime(data["end_time"])
@@ -256,7 +259,7 @@ class ContestPasswordVerifyAPIView(APIView):
if serializer.is_valid():
data = request.data
try:
contest = Contest.objects.get(id=data["contest_id"], contest_type=2)
contest = Contest.objects.get(id=data["contest_id"], contest_type=PASSWORD_PROTECTED_CONTEST)
except Contest.DoesNotExist:
return error_response(u"比赛不存在")
@@ -317,13 +320,23 @@ def contest_problems_list_page(request, contest_id):
比赛所有题目的列表页
"""
try:
contest_problems = ContestProblem.objects.filter(contest=Contest.objects.get(id=contest_id)).order_by("sort_index")
except ContestProblem.DoesNotExist:
return error_page(request, u"比赛题目不存在")
# 右侧的公告列表
announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
contest = Contest.objects.get(id=contest_id)
except Contest.DoesNotExist:
return error_page(request, u"比赛不存在")
contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index")
submissions = ContestSubmission.objects.filter(user=request.user, contest=contest)
state = {}
for item in submissions:
state[item.problem_id] = item.ac
for item in contest_problems:
if item.id in state:
if state[item.id]:
item.state = 1
else:
item.state = 2
else:
item.state = 0
return render(request, "oj/contest/contest_problems_list.html", {"contest_problems": contest_problems,
"announcements": announcements,
"contest": {"id": contest_id}})
@@ -341,10 +354,9 @@ def contest_list_page(request, page=1):
# 筛选我能参加的比赛
join = request.GET.get("join", None)
if join:
contests = contests.filter(Q(contest_type__in=[1, 2]) | Q(groups__in=request.user.group_set.all())).\
if request.user.is_authenticated and join:
contests = contests.filter(Q(contest_type__in=[1, 2]) | Q(groups__in=request.user.group_set.all())). \
filter(end_time__gt=datetime.datetime.now(), start_time__lt=datetime.datetime.now())
paginator = Paginator(contests, 20)
try:
current_page = paginator.page(int(page))
@@ -363,15 +375,10 @@ def contest_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/contest/contest_list.html",
{"contests": current_page, "page": int(page),
"previous_page": previous_page, "next_page": next_page,
"keyword": keyword, "announcements": announcements,
"join": join})
"keyword": keyword, "join": join})
def _cmp(x, y):
@@ -386,26 +393,85 @@ def _cmp(x, y):
return -1
def get_the_formatted_time(seconds):
if not seconds:
return ""
result = str(seconds % 60)
if seconds % 60 < 10:
result = "0" + result
result = str((seconds % 3600) / 60) + ":" + result
if (seconds % 3600) / 60 < 10:
result = "0" + result
result = str(seconds / 3600) + ":" + result
if seconds / 3600 < 10:
result = "0" + result
return result
@check_user_contest_permission
def contest_rank_page(request, contest_id):
contest = Contest.objects.get(id=contest_id)
contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index")
result = ContestSubmission.objects.filter(contest=contest).values("user_id").annotate(total_submit=Sum("total_submission_number"))
for i in range(0, len(result)):
# 这个人所有的提交
submissions = ContestSubmission.objects.filter(user_id=result[i]["user_id"], contest_id=contest_id)
result[i]["submissions"] = {}
for item in submissions:
result[i]["submissions"][item.problem_id] = item
result[i]["total_ac"] = submissions.filter(ac=True).count()
result[i]["user"] = User.objects.get(id=result[i]["user_id"])
result[i]["total_time"] = submissions.filter(ac=True).aggregate(total_time=Sum("total_time"))["total_time"]
r = redis.Redis(host=REDIS_CACHE["host"], port=REDIS_CACHE["port"], db=REDIS_CACHE["db"])
if contest.real_time_rank:
# 更新rank
result = ContestSubmission.objects.filter(contest=contest).values("user_id"). \
annotate(total_submit=Sum("total_submission_number"))
for i in range(0, len(result)):
# 这个人所有的提交
submissions = ContestSubmission.objects.filter(user_id=result[i]["user_id"], contest_id=contest_id)
result[i]["submissions"] = {}
result[i]["problems"] = []
for problem in contest_problems:
try:
status = submissions.get(problem=problem)
result[i]["problems"].append({
"first_achieved": status.first_achieved,
"ac": status.ac,
"failed_number": status.total_submission_number,
"ac_time": get_the_formatted_time(status.ac_time)})
if status.ac:
result[i]["problems"][-1]["failed_number"] -= 1
except ContestSubmission.DoesNotExist:
result[i]["problems"].append({})
result[i]["total_ac"] = submissions.filter(ac=True).count()
user= User.objects.get(id=result[i]["user_id"])
result[i]["username"] = user.username
result[i]["real_name"] = user.real_name
result[i]["total_time"] = get_the_formatted_time(submissions.filter(ac=True).aggregate(total_time=Sum("total_time"))["total_time"])
result = sorted(result, cmp=_cmp, reverse=True)
r.set("contest_rank_" + contest_id, json.dumps(list(result)))
else:
# 从缓存读取排名信息
result = r.get("contest_rank_" + contest_id)
if result:
result = json.loads(result)
else:
result = []
return render(request, "oj/contest/contest_rank.html",
{"contest": contest, "contest_problems": contest_problems, "result": sorted(result, cmp=_cmp, reverse=True)})
{"contest": contest, "contest_problems": contest_problems,
"result": result,
"auto_refresh": request.GET.get("auto_refresh", None) == "true",
"show_real_name": request.GET.get("show_real_name", None) == "true",
"real_time_rank": contest.real_time_rank})
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))

View File

@@ -1,13 +1,15 @@
# coding=utf-8
import json
from django.test import TestCase, Client
from django.core.urlresolvers import reverse
from account.models import User, REGULAR_USER, ADMIN, SUPER_ADMIN
from problem.models import Problem
from contest.models import Contest, ContestProblem
from submission.models import Submission
from rest_framework.test import APITestCase, APIClient
from account.models import User, REGULAR_USER, ADMIN, SUPER_ADMIN
from contest.models import Contest, ContestProblem
from contest.models import PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST
from submission.models import Submission
class ContestSubmissionAPITest(APITestCase):
def setUp(self):
@@ -20,7 +22,8 @@ class ContestSubmissionAPITest(APITestCase):
self.user2.set_password("testbb")
self.user2.save()
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
contest_type=1, show_rank=True, show_user_submission=True,
contest_type=PUBLIC_CONTEST, show_rank=True,
show_user_submission=True,
start_time="2015-08-15T10:00:00.000Z",
end_time="2015-08-30T12:00:00.000Z",
created_by=User.objects.get(username="test1"))
@@ -70,7 +73,8 @@ class ContestProblemMySubmissionListTest(TestCase):
self.user2.set_password("testbb")
self.user2.save()
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
contest_type=1, show_rank=True, show_user_submission=True,
contest_type=PUBLIC_CONTEST, show_rank=True,
show_user_submission=True,
start_time="2015-08-15T10:00:00.000Z",
end_time="2015-08-30T12:00:00.000Z",
created_by=User.objects.get(username="test1"))
@@ -104,7 +108,8 @@ class SubmissionAPITest(APITestCase):
self.userS.set_password("testbb")
self.userS.save()
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
contest_type=2, show_rank=True, show_user_submission=True,
contest_type=PASSWORD_PROTECTED_CONTEST, show_rank=True,
show_user_submission=True,
start_time="2015-08-15T10:00:00.000Z",
end_time="2015-08-15T12:00:00.000Z",
password="aacc", created_by=self.userS

View File

@@ -1,24 +1,24 @@
# coding=utf-8
import json
from datetime import datetime
import redis
import pytz
from django.shortcuts import render
from django.core.paginator import Paginator
from django.utils import timezone
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 contest.decorators import check_user_contest_permission
from problem.models import Problem
from contest.models import Contest, ContestProblem
from utils.shortcuts import serializer_invalid_response, error_response, success_response, error_page, paginate
from submission.models import Submission
from .serializers import CreateContestSubmissionSerializer
from submission.serializers import SubmissionSerializer
@@ -43,20 +43,16 @@ class ContestSubmissionAPIView(APIView):
problem.save()
except ContestProblem.DoesNotExist:
return error_response(u"题目不存在")
submission = Submission.objects.create(user_id=request.user.id, language=int(data["language"]),
contest_id=contest.id, code=data["code"], problem_id=problem.id)
try:
judge.delay(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
except Exception:
return error_response(u"提交判题任务失败")
# 增加redis 中判题队列长度的计数器
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
r.incr("judge_queue_length")
return success_response({"submission_id": submission.id})
else:
return serializer_invalid_response(serializer)
@@ -81,7 +77,7 @@ def contest_problem_my_submissions_list_page(request, contest_id, contest_proble
{"submissions": submissions, "problem": contest_problem})
@login_required
@check_user_contest_permission
def contest_problem_submissions_list_page(request, contest_id, page=1):
"""
单个比赛中的所有提交(包含自己和别人,自己可查提交结果,其他人不可查)
@@ -90,10 +86,36 @@ def contest_problem_submissions_list_page(request, contest_id, page=1):
contest = Contest.objects.get(id=contest_id)
except Contest.DoesNotExist:
return error_page(request, u"比赛不存在")
# 以下是本场比赛中所有的提交
submissions = Submission.objects.filter(contest_id=contest_id). \
values("id", "result", "create_time", "accepted_answer_time", "language", "user_id").order_by("-create_time")
submissions = Submission.objects.filter(contest_id=contest_id).\
values("id", "contest_id", "problem_id", "result", "create_time",
"accepted_answer_time", "language", "user_id").order_by("-create_time")
# 封榜的时候只能看到自己的提交
if not contest.real_time_rank:
if not (request.user.admin_type == SUPER_ADMIN or request.user == contest.created_by):
submissions = submissions.filter(user_id=request.user.id)
language = request.GET.get("language", None)
filter = None
if language:
submissions = submissions.filter(language=int(language))
filter = {"name": "language", "content": language}
result = request.GET.get("result", None)
if result:
submissions = submissions.filter(result=int(result))
filter = {"name": "result", "content": result}
paginator = Paginator(submissions, 20)
# 为查询题目标题创建新字典
title = {}
contest_problems = ContestProblem.objects.filter(contest=contest)
for item in contest_problems:
title[item.id] = item.title
for item in submissions:
item['title'] = title[item['problem_id']]
try:
current_page = paginator.page(int(page))
except Exception:
@@ -108,10 +130,18 @@ def contest_problem_submissions_list_page(request, contest_id, page=1):
except Exception:
pass
for item in current_page:
# 自己提交的 管理员和创建比赛的可以看到所有的提交链接
if item["user_id"] == request.user.id or request.user.admin_type == SUPER_ADMIN or \
request.user == contest.created_by:
item["show_link"] = True
else:
item["show_link"] = False
return render(request, "oj/contest/submissions_list.html",
{"submissions": current_page, "page": int(page),
"previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20,
"contest": contest})
"contest": contest, "filter": filter})
class ContestSubmissionAdminAPIView(APIView):
@@ -144,6 +174,4 @@ class ContestSubmissionAdminAPIView(APIView):
return error_response(u"参数错误!")
if problem_id:
submissions = submissions.filter(problem_id=problem_id)
return paginate(request, submissions, SubmissionSerializer)

View File

@@ -4,9 +4,9 @@ services:
- mysql
env:
- oj_env="daocloud"
- oj_env="local"
script:
- pip install -r requirements.txt
- mkdir LOG
- mkdir log
- python manage.py test

View File

@@ -3,6 +3,8 @@ MAINTAINER virusdefender<qduliyang@outlook.com>
RUN mkdir /var/install/
WORKDIR /var/install/
ENV DEBIAN_FRONTEND noninteractive
RUN rm /etc/apt/sources.list
COPY sources.list /etc/apt/
RUN apt-get update
RUN apt-get -y install software-properties-common python-software-properties
RUN add-apt-repository -y ppa:webupd8team/java
@@ -16,4 +18,5 @@ RUN git clone https://github.com/quark-zju/lrun.git
RUN cd lrun && make install
RUN mkdir -p /var/judger/run/ && mkdir /var/judger/test_case/ && mkdir /var/judger/code/
RUN chmod -R 777 /var/judger/run/
COPY policy /var/judger/run/
WORKDIR /var/judger/code/

View File

@@ -0,0 +1,3 @@
grant {
permission java.io.FilePermission "/tmp", "read";
};

View File

@@ -0,0 +1,10 @@
deb http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse

View File

@@ -1,7 +1,8 @@
FROM python:2.7
ENV PYTHONBUFFERED 1
RUN mkdir -p /code/log /code/test_case
RUN mkdir -p /code/log /code/test_case /code/upload
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt
EXPOSE 8010
CMD supervisord

View File

@@ -0,0 +1,16 @@
[program:gunicorn]
command=gunicorn oj.wsgi:application -b 0.0.0.0:8080 --reload
directory=/code/
user=root
numprocs=1
stdout_logfile=/code/log/gunicorn.log
stderr_logfile=/code/log/gunicorn.log
autostart=true
autorestart=true
startsecs=5
stopwaitsecs = 6
killasgroup=true

View File

@@ -0,0 +1,16 @@
[program:mq]
command=python manage.py runscript mq
directory=/code/
user=root
numprocs=1
stdout_logfile=/code/log/mq.log
stderr_logfile=/code/log/mq.log
autostart=true
autorestart=true
startsecs=5
stopwaitsecs = 6
killasgroup=true

View File

@@ -9,3 +9,4 @@ gunicorn
coverage
django-extensions
supervisor
pillow

View File

@@ -0,0 +1,26 @@
[unix_http_server]
file=/tmp/supervisor.sock ; path to your socket file
[supervisord]
logfile=/code/log/supervisord.log ; supervisord log file
logfile_maxbytes=50MB ; maximum size of logfile before rotation
logfile_backups=10 ; number of backed up logfiles
loglevel=info ; info, debug, warn, trace
pidfile=/code/log/supervisord.pid ; pidfile location
nodaemon=true ; run supervisord as a daemon
minfds=1024 ; number of startup file descriptors
minprocs=200 ; number of process descriptors
user=root ; default user
childlogdir=/code/log/ ; where child log files will live
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use unix:// schem for a unix sockets.
[include]
files=gunicorn.conf mq.conf

View File

@@ -254,9 +254,6 @@ class JoinGroupRequestAdminAPIView(APIView, GroupAPIViewBase):
@login_required
def group_list_page(request, page=1):
# 右侧的公告列表
announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
groups = Group.objects.filter(visible=True, join_group_setting__lte=2)
# 搜索的情况
keyword = request.GET.get("keyword", None)
@@ -282,10 +279,10 @@ def group_list_page(request, page=1):
pass
return render(request, "oj/group/group_list.html", {
"groups": groups, "announcements": announcements,
"groups": groups,
"contests": current_page, "page": int(page),
"previous_page": previous_page, "next_page": next_page,
"keyword": keyword, "announcements": announcements,
"keyword": keyword
})

View File

View File

@@ -1,3 +0,0 @@
from django.db import models
# Create your models here.

View File

@@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -1,23 +0,0 @@
# coding=utf-8
from django.shortcuts import render
from django.http import HttpResponse
from account.models import User
from group.models import Group, UserGroupRelation, JoinGroupRequest
def install(request):
for i in range(10):
user = User.objects.create(username="root" + str(i), admin_type=2, real_name="real_name", email="11111@qq.com")
user.set_password("root")
user.save()
for i in range(10):
group = Group.objects.create(name="group" + str(i),
description="description",
admin=User.objects.get(username="root0"))
for i in range(7):
UserGroupRelation.objects.create(user=User.objects.get(username="root" + str(i)), group=group)
for i in range(7, 10):
JoinGroupRequest.objects.create(user=User.objects.get(username="root" + str(i)),
group=group, message=u"你好啊")
return HttpResponse("success")

View File

@@ -1,3 +0,0 @@
/usr/bin/docker run -t -i --privileged -v /var/test_case/:/var/judger/test_case/ -v /var/code/:/var/judger/code/ judger /bin/bash
python judge/judger/run.py -solution_id 1 -max_cpu_time 1 -max_memory 1 -test_case_id 1

View File

@@ -1,4 +1,5 @@
# coding=utf-8
import os
import json
import commands
import hashlib
@@ -7,9 +8,9 @@ from multiprocessing import Pool
from settings import max_running_number, lrun_gid, lrun_uid, judger_workspace
from language import languages
from result import result
from compiler import compile_
from judge_exceptions import JudgeClientError, CompileError
from judge_exceptions import JudgeClientError
from utils import parse_lrun_output
from logger import logger
# 下面这个函数作为代理访问实例变量否则Python2会报错是Python2的已知问题
@@ -61,6 +62,8 @@ class JudgeClient(object):
" --max-real-time " + str(self._max_real_time / 1000.0 * 2) + \
" --max-memory " + str(self._max_memory * 1000 * 1000) + \
" --network false" + \
" --syscalls '" + self._language["syscalls"] + "'" + \
" --max-nprocess 20" + \
" --uid " + str(lrun_uid) + \
" --gid " + str(lrun_gid)
@@ -82,6 +85,8 @@ class JudgeClient(object):
# 倒序找到MEMORY的位置
output_start = output.rfind("MEMORY")
if output_start == -1:
logger.error("Lrun result parse error")
logger.error(output)
raise JudgeClientError("Lrun result parse error")
# 如果不是0说明lrun输出前面有输出也就是程序的stderr有内容
if output_start != 0:
@@ -92,26 +97,35 @@ class JudgeClient(object):
return error, parse_lrun_output(output)
def _compare_output(self, test_case_id):
test_case_md5 = self._test_case_info["test_cases"][str(test_case_id)]["output_md5"]
test_case_config = self._test_case_info["test_cases"][str(test_case_id)]
output_path = judger_workspace + str(test_case_id) + ".out"
try:
f = open(output_path, "rb")
except IOError:
# 文件不存在等引发的异常 返回结果错误
return False
return "", False
# 计算输出文件的md5 和之前测试用例文件的md5进行比较
md5 = hashlib.md5()
while True:
data = f.read(2 ** 8)
if not data:
break
md5.update(data)
if "striped_output_md5" not in test_case_config:
# 计算输出文件的md5 和之前测试用例文件的md5进行比较
# 兼容之前没有striped_output_md5的测试用例
# 现在比较的是完整的文件
md5 = hashlib.md5()
while True:
data = f.read(2 ** 8)
if not data:
break
md5.update(data)
output_md5 = md5.hexdigest()
# 对比文件是否一致
# todo 去除最后的空行
return md5.hexdigest() == test_case_md5
return output_md5, output_md5 == test_case_config["output_md5"]
else:
# 这时候需要去除用户输出最后的空格和换行 再去比较md5
md5 = hashlib.md5()
# 比较和返回去除空格后的md5比较结果
md5.update(f.read().rstrip())
output_md5 = md5.hexdigest()
return output_md5, output_md5 == test_case_config["striped_output_md5"]
def _judge_one(self, test_case_id):
# 运行lrun程序 接收返回值
@@ -123,26 +137,30 @@ class JudgeClient(object):
run_result["test_case_id"] = test_case_id
# 如果返回值非0 或者信号量不是0 或者程序的stderr有输出 代表非正常结束
if run_result["exit_code"] or run_result["term_sig"] or run_result["siginaled"] or error:
run_result["result"] = result["runtime_error"]
return run_result
# 代表内存或者时间超过限制了
# 代表内存或者时间超过限制了 程序被终止掉 要在runtime error 之前判断
if run_result["exceed"]:
if run_result["exceed"] == "memory":
run_result["result"] = result["memory_limit_exceeded"]
elif run_result["exceed"] in ["cpu_time", "real_time"]:
run_result["result"] = result["time_limit_exceeded"]
else:
logger.error("Error exceeded type: " + run_result["exceed"])
logger.error(output)
raise JudgeClientError("Error exceeded type: " + run_result["exceed"])
return run_result
# 如果返回值非0 或者信号量不是0 或者程序的stderr有输出 代表非正常结束
if run_result["exit_code"] or run_result["term_sig"] or run_result["siginaled"] or error:
run_result["result"] = result["runtime_error"]
return run_result
# 下面就是代码正常运行了 需要判断代码的输出是否正确
if self._compare_output(test_case_id):
output_md5, r = self._compare_output(test_case_id)
if r:
run_result["result"] = result["accepted"]
else:
run_result["result"] = result["wrong_answer"]
run_result["output_md5"] = output_md5
return run_result
@@ -160,8 +178,8 @@ class JudgeClient(object):
try:
results.append(item.get())
except Exception as e:
# todo logging
print e
logger.error("system error")
logger.error(e)
results.append({"result": result["system_error"]})
return results

View File

@@ -4,6 +4,7 @@ import commands
from settings import lrun_uid, lrun_gid
from judge_exceptions import CompileError, JudgeClientError
from utils import parse_lrun_output
from logger import logger
def compile_(language_item, src_path, exe_path):
@@ -22,14 +23,20 @@ def compile_(language_item, src_path, exe_path):
output_start = output.rfind("MEMORY")
if output_start == -1:
logger.error("Compiler error")
logger.error(output)
raise JudgeClientError("Error running compiler in lrun")
# 返回值不为0 或者 stderrlrun的输出之前有东西
if status or output_start:
# 返回值不为 0 或者 stderrlrun 的输出之前有 erro r字符串
# 判断 error 字符串的原因是链接的时候可能会有一些不推荐使用的函数的的警告,
# 但是 -w 参数并不能关闭链接时的警告
if status or "error" in output[0:output_start]:
raise CompileError(output[0:output_start])
parse_result = parse_lrun_output(output)
parse_result = parse_lrun_output(output[output_start:])
if parse_result["exit_code"] or parse_result["term_sig"] or parse_result["siginaled"] or parse_result["exceed"]:
logger.error("Compiler error")
logger.error(output)
raise CompileError("Compile error")
return exe_path

View File

@@ -6,6 +6,7 @@ languages = {
"name": "c",
"src_name": "main.c",
"code": 1,
"syscalls": "!execve:k,flock:k,ptrace:k,sync:k,fdatasync:k,fsync:k,msync,sync_file_range:k,syncfs:k,unshare:k,setns:k,clone:k,query_module:k,sysinfo:k,syslog:k,sysfs:k",
"compile_command": "gcc -DONLINE_JUDGE -O2 -w -std=c99 {src_path} -lm -o {exe_path}main",
"execute_command": "{exe_path}main"
},
@@ -13,6 +14,7 @@ languages = {
"name": "cpp",
"src_name": "main.cpp",
"code": 2,
"syscalls": "!execve:k,flock:k,ptrace:k,sync:k,fdatasync:k,fsync:k,msync,sync_file_range:k,syncfs:k,unshare:k,setns:k,clone:k,query_module:k,sysinfo:k,syslog:k,sysfs:k",
"compile_command": "g++ -DONLINE_JUDGE -O2 -w -std=c++11 {src_path} -lm -o {exe_path}main",
"execute_command": "{exe_path}main"
},
@@ -20,8 +22,9 @@ languages = {
"name": "java",
"src_name": "Main.java",
"code": 3,
"syscalls": "!execve:k,flock:k,ptrace:k,sync:k,fdatasync:k,fsync:k,msync,sync_file_range:k,syncfs:k,unshare:k,setns:k,clone[a&268435456==268435456]:k,query_module:k,sysinfo:k,syslog:k,sysfs:k",
"compile_command": "javac {src_path} -d {exe_path}",
"execute_command": "java -cp {exe_path} Main"
"execute_command": "java -cp {exe_path} -Djava.security.manager -Djava.security.policy==policy Main"
}
}

8
judge/judger/logger.py Normal file
View File

@@ -0,0 +1,8 @@
# coding=utf-8
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s',
filename='log/judge.log')
logger = logging

View File

@@ -7,9 +7,8 @@ from client import JudgeClient
from language import languages
from compiler import compile_
from result import result
from settings import judger_workspace
from settings import submission_db
from settings import judger_workspace, submission_db
from logger import logger
# 简单的解析命令行参数
@@ -60,7 +59,6 @@ except Exception as e:
conn.commit()
exit()
print "Compile successfully"
# 运行
try:
client = JudgeClient(language_code=language_code,
@@ -80,16 +78,13 @@ try:
judge_result["accepted_answer_time"] = l[-1]["cpu_time"]
except Exception as e:
print e
logger.error(e)
conn = db_conn()
cur = conn.cursor()
cur.execute("update submission set result=%s, info=%s where id=%s", (result["system_error"], str(e), submission_id))
conn.commit()
exit()
print "Run successfully"
print judge_result
conn = db_conn()
cur = conn.cursor()
cur.execute("update submission set result=%s, info=%s, accepted_answer_time=%s where id=%s",

View File

@@ -1,4 +1,5 @@
# coding=utf-8
import os
# 单个判题端最多同时运行的程序个数因为判题端会同时运行多组测试数据比如一共有5组测试数据
# 如果MAX_RUNNING_NUMBER大于等于5那么这5组数据就会同时进行评测然后返回结果。
# 如果MAX_RUNNING_NUMBER小于5为3那么就会同时运行前三组测试数据然后再运行后两组数据
@@ -14,12 +15,10 @@ lrun_gid = 1002
# judger工作目录
judger_workspace = "/var/judger/"
# 这个是在docker 中访问数据库 ip 不一定和web服务器还有celery的一样
submission_db = {
"host": "10.172.22.50",#"192.168.42.1",
"host": os.environ.get("MYSQL_PORT_3306_TCP_ADDR", "127.0.0.1"),
"port": 3306,
"db": "oj_submission",
"user": "root",
"password": "mypwd"
"password": os.environ.get("MYSQL_ENV_MYSQL_ROOT_PASSWORD", "root")
}

View File

@@ -1,7 +1,13 @@
# coding=utf-8
"""
注意:
此文件包含 celery 的部分配置,但是 celery 并不是运行在docker 中的,所以本配置文件中的 redis和 MySQL 的地址就应该是
运行 redis 和 MySQL 的 docker 容器的地址了。怎么获取这个地址见帮助文档。测试用例的路径和源代码路径同理。
"""
import os
# 这个redis 是 celery 使用的,包括存储队列信息还有部分统计信息
redis_config = {
"host": "127.0.0.1",
"host": os.environ.get("REDIS_PORT_6379_TCP_ADDR"),
"port": 6379,
"db": 0
}
@@ -9,8 +15,7 @@ redis_config = {
# 判题的 docker 容器的配置参数
docker_config = {
"image_name": " 819d3da18dc1",
"image_name": "judger",
"docker_path": "docker",
"shell": True
}
@@ -19,12 +24,14 @@ docker_config = {
# 测试用例的路径,是主机上的实际路径
test_case_dir = "/root/test_case/"
# 源代码路径,也就是 manage.py 所在的实际路径
source_code_dir = "/var/mnt/source/OnlineJudge/"
source_code_dir = "/root/qduoj/"
# 日志文件夹路径
log_dir = "/root/log/"
# 存储提交信息的数据库,是 celery 使用的,与 oj.settings/local_settings 等区分,那是 web 服务器访问的地址
submission_db = {
"host": "127.0.0.1",
"host": os.environ.get("submission_db_host"),
"port": 3306,
"db": "oj_submission",
"user": "root",

View File

@@ -5,32 +5,34 @@ import MySQLdb
import subprocess
from ..judger.result import result
from ..judger_controller.celery import app
from settings import docker_config, source_code_dir, test_case_dir, submission_db, redis_config
from settings import docker_config, source_code_dir, test_case_dir, log_dir, submission_db, redis_config
@app.task
def judge(submission_id, time_limit, memory_limit, test_case_id):
try:
command = "%s run -t -i --privileged --rm=true " \
command = "%s run --privileged --rm " \
"--link mysql " \
"-v %s:/var/judger/test_case/ " \
"-v %s:/var/judger/code/ " \
"-v %s:/var/judger/code/log/ " \
"%s " \
"python judge/judger/run.py " \
"--solution_id %s --time_limit %s --memory_limit %s --test_case_id %s" % \
(docker_config["docker_path"],
test_case_dir,
source_code_dir,
log_dir,
docker_config["image_name"],
submission_id, str(time_limit), str(memory_limit), test_case_id)
subprocess.call(command, shell=docker_config["shell"])
except Exception as e:
print e
conn = MySQLdb.connect(db=submission_db["db"],
user=submission_db["user"],
passwd=submission_db["password"],
host=submission_db["host"],
port=submission_db["port"],
character="utf8")
charset="utf8")
cur = conn.cursor()
cur.execute("update submission set result=%s, info=%s where id=%s",

View File

@@ -1,12 +0,0 @@
#include <stdio.h>
int main()
{
int a = 0;
int i = 0;
for(i = 0; i < 9999999999;i++)
{
a += i;
}
printf("%d", a);
return 0;
}

View File

@@ -1,6 +0,0 @@
#include <stdio.h>
#include <unistd.h>
int main()
{
}

View File

@@ -1,8 +0,0 @@
# include <stdio.h>
int main()
{
int a, b;
scanf("%d %d", &a, &b);
printf("%d", a + b);
return 0;
}

View File

@@ -1,93 +0,0 @@
# 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
from contest.models import ContestProblem, Contest, ContestSubmission
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)
continue
if submission.result == result["accepted"] and not submission.contest_id:
# 更新普通题目的 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)
# 普通题目的话,到这里就结束了
continue
# 能运行到这里的都是比赛题目
try:
contest = Contest.objects.get(id=submission.contest_id)
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)
continue
except ContestProblem.DoesNotExist:
logger.warning("Submission problem does not exist, submission_id: " + submission_id)
continue
try:
contest_submission = ContestSubmission.objects.get(user_id=submission.user_id, contest=contest,
problem_id=contest_problem.id)
# 提交次数加1
contest_submission.total_submission_number += 1
if submission.result == result["accepted"]:
# 避免这道题已经 ac 了,但是又重新提交了一遍
if not contest_submission.ac:
# 这种情况是这个题目前处于错误状态,就使用已经存储了的罚时加上这道题的实际用时
logger.debug(contest.start_time)
logger.debug(submission.create_time)
logger.debug((submission.create_time - contest.start_time).total_seconds())
logger.debug(int((submission.create_time - contest.start_time).total_seconds() / 60))
contest_submission.total_time += int((submission.create_time - contest.start_time).total_seconds() / 60)
# 标记为已经通过
contest_submission.ac = True
# contest problem ac 计数器加1
contest_problem.total_accepted_number += 1
else:
# 如果这个提交是错误的就罚时20分钟
contest_submission.total_time += 20
contest_submission.save()
contest_problem.save()
except ContestSubmission.DoesNotExist:
# 第一次提交
is_ac = submission.result == result["accepted"]
if is_ac:
total_time = int((submission.create_time - contest.start_time).total_seconds() / 60)
# 增加题目总的ac数计数器
contest_problem.total_accepted_number += 1
contest_problem.save()
else:
# 没过罚时20分钟
total_time = 20
ContestSubmission.objects.create(user_id=submission.user_id, contest=contest, problem=contest_problem,
ac=is_ac, total_time=total_time)
logger.debug("Start message queue")
MessageQueue().listen_task()

114
mq/scripts/mq.py Normal file
View File

@@ -0,0 +1,114 @@
# coding=utf-8
import logging
import redis
import json
from django.db import transaction
from judge.judger_controller.settings import redis_config
from judge.judger.result import result
from submission.models import Submission
from problem.models import Problem
from contest.models import ContestProblem, Contest, ContestSubmission
from account.models import User
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)
continue
if submission.result == result["accepted"] and not submission.contest_id:
# 更新普通题目的 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)
continue
# 更新该用户的解题状态
try:
user = User.objects.get(pk=submission.user_id)
except User.DoesNotExist:
logger.warning("Submission user does not exist, submission_id: " + submission_id)
continue
problems_status = json.loads(user.problems_status)
problems_status[str(problem.id)] = 1
user.problems_status = json.dumps(problems_status)
user.save()
# 普通题目的话,到这里就结束了
continue
# 能运行到这里的都是比赛题目
try:
contest = Contest.objects.get(id=submission.contest_id)
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)
continue
except ContestProblem.DoesNotExist:
logger.warning("Submission problem does not exist, submission_id: " + submission_id)
continue
try:
contest_submission = ContestSubmission.objects.get(user_id=submission.user_id, contest=contest,
problem_id=contest_problem.id)
# 提交次数加1
with transaction.atomic():
if submission.result == result["accepted"]:
# 避免这道题已经 ac 了,但是又重新提交了一遍
if not contest_submission.ac:
# 这种情况是这个题目前处于错误状态,就使用已经存储了的罚时加上这道题的实际用时
contest_submission.ac_time = int((submission.create_time - contest.start_time).total_seconds())
contest_submission.total_time += contest_submission.ac_time
contest_submission.total_submission_number += 1
# 标记为已经通过
if contest_problem.total_accepted_number == 0:
contest_submission.first_achieved = True
contest_submission.ac = True
# contest problem ac 计数器加1
contest_problem.total_accepted_number += 1
else:
# 如果这个提交是错误的就罚时20分钟
contest_submission.total_time += 1200
contest_submission.total_submission_number += 1
contest_submission.save()
contest_problem.save()
except ContestSubmission.DoesNotExist:
# 第一次提交
with transaction.atomic():
is_ac = submission.result == result["accepted"]
first_achieved = False
ac_time = 0
if is_ac:
ac_time = int((submission.create_time - contest.start_time).total_seconds())
total_time = int((submission.create_time - contest.start_time).total_seconds())
# 增加题目总的ac数计数器
if contest_problem.total_accepted_number == 0:
first_achieved = True
contest_problem.total_accepted_number += 1
contest_problem.save()
else:
# 没过罚时20分钟
total_time = 1200
ContestSubmission.objects.create(user_id=submission.user_id, contest=contest, problem=contest_problem,
ac=is_ac, total_time=total_time, first_achieved=first_achieved,
ac_time=ac_time)
logger.debug("Start message queue")
MessageQueue().listen_task()

View File

@@ -3,10 +3,6 @@ import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 下面是需要自己修改的
LOG_PATH = "log/"
# 注意这是web 服务器访问的地址,判题端访问的地址不一定一样,因为可能不在一台机器上
DATABASES = {
'default': {
@@ -17,17 +13,27 @@ DATABASES = {
'submission': {
'NAME': 'oj_submission',
'ENGINE': 'django.db.backends.mysql',
'HOST': "121.42.32.129",
'CONN_MAX_AGE': 0.1,
'HOST': "127.0.0.1",
'PORT': 3306,
'USER': 'root',
'PASSWORD': 'mypwd',
'CONN_MAX_AGE': 0.1,
'PASSWORD': 'root',
}
}
REDIS_CACHE = {
"host": "121.42.32.129",
"port": 6379,
"db": 1
}
DEBUG = True
# 同理 这是 web 服务器的上传路径
TEST_CASE_DIR = os.path.join(BASE_DIR, 'test_case/')
ALLOWED_HOSTS = []
# 在 debug 关闭的情况下,静态文件不是有 django runserver 来处理的,应该由 nginx 返回
# 在 debug 开启的情况下django 会在下面两个文件夹中寻找对应的静态文件。
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static/src/"), BASE_DIR]
# 模板文件夹
TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'template/src/')]

View File

@@ -3,35 +3,41 @@ import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 下面是需要自己修改的
LOG_PATH = "/var/log/oj/"
# 注意这是web 服务器访问的地址,判题端访问的地址不一定一样,因为可能不在一台机器上
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': "oj",
'CONN_MAX_AGE': 0.1,
'HOST': '127.0.0.1',
'HOST': os.environ.get("MYSQL_PORT_3306_TCP_ADDR", "127.0.0.1"),
'PORT': 3306,
'USER': 'root',
'PASSWORD': 'mypwd'
'PASSWORD': os.environ.get("MYSQL_ENV_MYSQL_ROOT_PASSWORD", "root")
},
'submission': {
'NAME': 'oj_submission',
'ENGINE': 'django.db.backends.mysql',
'CONN_MAX_AGE': 0.1,
'HOST': "127.0.0.1",
'HOST': os.environ.get("MYSQL_PORT_3306_TCP_ADDR", "127.0.0.1"),
'PORT': 3306,
'USER': 'root',
'PASSWORD': 'mypwd'
'PASSWORD': os.environ.get("MYSQL_ENV_MYSQL_ROOT_PASSWORD", "root")
}
}
DEBUG = True
REDIS_CACHE = {
"host": os.environ.get("REDIS_PORT_6379_TCP_ADDR", "127.0.0.1"),
"port": 6379,
"db": 1
}
# 同理 这是 web 服务器的上传路径
TEST_CASE_DIR = '/root/test_case/'
DEBUG = False
ALLOWED_HOSTS = ['*']
# 在 debug 关闭的情况下,静态文件不是有 django runserver 来处理的,应该由 nginx 返回
# 在 debug 开启的情况下django 会在下面两个文件夹中寻找对应的静态文件。
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static/release/"), os.path.join(BASE_DIR, "static/release/")]
# 模板文件夹
TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'template/release/')]

View File

@@ -32,8 +32,6 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = 'hzfp^8mbgapc&x%$#xv)0=t8s7_ilingw(q3!@h&2fty6v6fxz'
# Application definition
INSTALLED_APPS = (
@@ -76,7 +74,7 @@ ROOT_URLCONF = 'oj.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'template/src')],
'DIRS': TEMPLATE_DIRS,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@@ -91,7 +89,6 @@ TEMPLATES = [
WSGI_APPLICATION = 'oj.wsgi.application'
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
@@ -111,10 +108,11 @@ USE_TZ = True
STATIC_URL = '/static/'
STATICFILES_DIRS = (os.path.join(BASE_DIR, "static/src/"),)
AUTH_USER_MODEL = 'account.User'
LOG_PATH = "log/"
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
@@ -167,3 +165,7 @@ REST_FRAMEWORK = {
}
DATABASE_ROUTERS = ['oj.db_router.DBRouter']
TEST_CASE_DIR = os.path.join(BASE_DIR, 'test_case/')
IMAGE_UPLOAD_DIR = os.path.join(BASE_DIR, 'upload/')

View File

@@ -4,11 +4,12 @@ from django.views.generic import TemplateView
from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView,
UserChangePasswordAPIView, EmailCheckAPIView,
UserAdminAPIView, UserInfoAPIView)
UserAdminAPIView, UserInfoAPIView, AccountSecurityAPIView)
from announcement.views import AnnouncementAdminAPIView
from contest.views import ContestAdminAPIView, ContestProblemAdminAPIView, ContestPasswordVerifyAPIView
from contest.views import (ContestAdminAPIView, ContestProblemAdminAPIView,
ContestPasswordVerifyAPIView, ContestTimeAPIView)
from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView,
JoinGroupAPIView, JoinGroupRequestAdminAPIView)
@@ -16,16 +17,16 @@ from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView,
from admin.views import AdminTemplateView
from problem.views import TestCaseUploadAPIView, ProblemTagAdminAPIView, ProblemAdminAPIView
from submission.views import SubmissionAPIView, SubmissionAdminAPIView
from submission.views import SubmissionAPIView, SubmissionAdminAPIView, SubmissionShareAPIView
from contest_submission.views import ContestSubmissionAPIView, ContestSubmissionAdminAPIView
from monitor.views import QueueLengthMonitorAPIView
from utils.views import SimditorImageUploadAPIView
from contest_submission.views import contest_problem_my_submissions_list_page
urlpatterns = [
url(r'^install/$', "install.views.install"),
url("^$", TemplateView.as_view(template_name="oj/index.html"), name="index_page"),
url("^$", "account.views.index_page", name="index_page"),
url(r'^docs/', include('rest_framework_swagger.urls')),
url(r'^admin/$', TemplateView.as_view(template_name="admin/admin.html"), name="admin_spa_page"),
url(r'^admin/contest/$', TemplateView.as_view(template_name="admin/contest/add_contest.html"),
@@ -53,6 +54,7 @@ urlpatterns = [
url(r'^api/submission/$', SubmissionAPIView.as_view(), name="submission_api"),
url(r'^api/group_join/$', JoinGroupAPIView.as_view(), name="group_join_api"),
url(r'^api/admin/upload_image/$', SimditorImageUploadAPIView.as_view(), name="simditor_upload_image"),
url(r'^api/admin/announcement/$', AnnouncementAdminAPIView.as_view(), name="announcement_admin_api"),
url(r'^api/admin/contest/$', ContestAdminAPIView.as_view(), name="contest_admin_api"),
url(r'^api/admin/user/$', UserAdminAPIView.as_view(), name="user_admin_api"),
@@ -106,5 +108,15 @@ urlpatterns = [
url(r'^groups/(?P<page>\d+)/$', "group.views.group_list_page", name="group_list_page"),
url(r'^group/(?P<group_id>\d+)/$', "group.views.group_page", name="group_page"),
url(r'^group/(?P<group_id>\d+)/applications/$', "group.views.application_list_page", name="group_application_page"),
url(r'^group/application/(?P<request_id>\d+)/$', "group.views.application_page", name="group_application")
url(r'^group/application/(?P<request_id>\d+)/$', "group.views.application_page", name="group_application"),
url(r'^about/$', TemplateView.as_view(template_name="utils/about.html"), name="about_page"),
url(r'^help/$', TemplateView.as_view(template_name="utils/help.html"), name="help_page"),
url(r'^api/submission/share/$', SubmissionShareAPIView.as_view(), name="submission_share_api"),
url(r'^captcha/$', "utils.captcha.views.show_captcha", name="show_captcha"),
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"),
]

View File

@@ -31,6 +31,7 @@ class CreateProblemSerializer(serializers.Serializer):
difficulty = serializers.IntegerField()
tags = serializers.ListField(child=serializers.CharField(max_length=10))
hint = serializers.CharField(max_length=3000, allow_blank=True)
visible = visible = serializers.BooleanField()
class ProblemTagSerializer(serializers.ModelSerializer):

View File

@@ -13,12 +13,16 @@ from rest_framework.views import APIView
from django.conf import settings
from announcement.models import Announcement
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
logger = logging.getLogger("app_info")
def problem_page(request, problem_id):
@@ -56,7 +60,8 @@ class ProblemAdminAPIView(APIView):
memory_limit=data["memory_limit"],
difficulty=data["difficulty"],
created_by=request.user,
hint=data["hint"])
hint=data["hint"],
visible=data["visible"])
for tag in data["tags"]:
try:
tag = ProblemTag.objects.get(name=tag)
@@ -147,9 +152,13 @@ class TestCaseUploadAPIView(APIView):
f = request.FILES["file"]
tmp_zip = "/tmp/" + rand_str() + ".zip"
with open(tmp_zip, "wb") as test_case_zip:
for chunk in f:
test_case_zip.write(chunk)
try:
with open(tmp_zip, "wb") as test_case_zip:
for chunk in f:
test_case_zip.write(chunk)
except IOError as e:
logger.error(e)
return error_response(u"上传失败")
test_case_file = zipfile.ZipFile(tmp_zip, 'r')
name_list = test_case_file.namelist()
@@ -198,16 +207,24 @@ class TestCaseUploadAPIView(APIView):
# 计算输出文件的md5
for i in range(len(l) / 2):
md5 = hashlib.md5()
striped_md5 = hashlib.md5()
f = open(test_case_dir + str(i + 1) + ".out", "r")
# 完整文件的md5
while True:
data = f.read(2 ** 8)
if not data:
break
md5.update(data)
# 删除标准输出最后的空格和换行
# 这时只能一次全部读入了,分块读的话,没办法确定文件结尾
f.seek(0)
striped_md5.update(f.read().rstrip())
file_info["test_cases"][str(i + 1)] = {"input_name": str(i + 1) + ".in",
"output_name": str(i + 1) + ".out",
"output_md5": md5.hexdigest(),
"striped_output_md5": striped_md5.hexdigest(),
"output_size": os.path.getsize(test_case_dir + str(i + 1) + ".out")}
# 写入配置文件
open(test_case_dir + "info", "w").write(json.dumps(file_info))
@@ -228,6 +245,17 @@ def problem_list_page(request, page=1):
if keyword:
problems = problems.filter(Q(title__contains=keyword) | Q(description__contains=keyword))
difficulty_order = request.GET.get("order_by", None)
if difficulty_order:
if difficulty_order[0] == "-":
problems = problems.order_by("-difficulty")
difficulty_order = "difficulty"
else:
problems = problems.order_by("difficulty")
difficulty_order = "-difficulty"
else:
difficulty_order = "difficulty"
# 按照标签筛选
tag_text = request.GET.get("tag", None)
if tag_text:
@@ -235,7 +263,7 @@ def problem_list_page(request, page=1):
tag = ProblemTag.objects.get(name=tag_text)
except ProblemTag.DoesNotExist:
return error_page(request, u"标签不存在")
problems = tag.problem_set.all()
problems = tag.problem_set.all().filter(visible=True)
paginator = Paginator(problems, 20)
try:
@@ -255,13 +283,15 @@ def problem_list_page(request, page=1):
except Exception:
pass
# 右侧的公告列表
announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
if request.user.is_authenticated():
problems_status = json.loads(request.user.problems_status)
else:
problems_status = {}
# 右侧标签列表 按照关联的题目的数量排序 排除题目数量为0的
tags = ProblemTag.objects.annotate(problem_number=Count("problem")).filter(problem_number__gt=0).order_by("-problem_number")
return render(request, "oj/problem/problem_list.html",
{"problems": current_page, "page": int(page),
"previous_page": previous_page, "next_page": next_page,
"keyword": keyword, "tag": tag_text,
"announcements": announcements, "tags": tags})
"keyword": keyword, "tag": tag_text,"problems_status": problems_status,
"tags": tags, "difficulty_order": difficulty_order})

View File

@@ -1,3 +0,0 @@
/**
* Created by virusdefender on 8/25/15.
*/

View File

@@ -1,4 +1,3 @@
@import url("global.css");
@import url("bootstrap/bootstrap.min.css");
@import url("bootstrap/todc-bootstrap.min.css");
@import url("codeMirror/codemirror.css");
@@ -6,6 +5,7 @@
@import url("webuploader/webuploader.css");
@import url("datetime_picker/bootstrap-datetimepicker.css");
@import url("tagEditor/jquery.tag-editor.css");
@import url("global.css");
#loading-gif {
width: 40px;

View File

@@ -1,13 +1,17 @@
html{
body, button, input, select, textarea, h1, h2, h3, h4, h5, h6 {
font-family: Georgia, STHeiti, "Microsoft Yahei", SimSun, "Droid Sans";
}
html {
height: 100%;
}
body{
height:100%; /*使内容高度和body一样*/
margin-bottom:-80px;/*向上缩减80像素不至于footer超出屏幕可视范围*/
body {
height: 100%; /*使内容高度和body一样*/
margin-bottom: -80px; /*向上缩减80像素不至于footer超出屏幕可视范围*/
}
.main{
.main {
padding-bottom: 120px;
}
@@ -30,10 +34,17 @@ label {
display: none
}
.right{
.right {
float: right;
}
.CodeMirror pre {
font-family: "Consolas","Bitstream Vera Sans Mono","Courier New", Courier, monospace !important;
pre {
font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
background-color: white;
}
.CodeMirror-code{
font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
background-color: white;
}

View File

@@ -1,8 +1,7 @@
@import url("global.css");
@import url("bootstrap/bootstrap.min.css");
@import url("bootstrap/todc-bootstrap.min.css");
@import url("codeMirror/codemirror.css");
@import url("global.css");
#language-selector {
width: 130px;
@@ -73,6 +72,10 @@ li.list-group-item {
color: green;
}
.dealing-flag {
color: #FF9933;
}
.CodeMirror{
min-height: 250px;
_height:250px;
@@ -92,3 +95,20 @@ li.list-group-item {
.contest-tab{
margin-bottom: 15px;
}
#share-code{
margin-top: 15px;
}
#share-code textarea {
margin-top: 15px;
}
#about-acm-logo{
width: 40%;
}
.rank .first-achieved{
background: #33CC99;
}

BIN
static/src/img/ZeroClipboard.swf Executable file

Binary file not shown.

BIN
static/src/img/acm_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

BIN
static/src/img/chrome.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
static/src/img/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
static/src/img/firefox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
static/src/img/ie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>不支持的浏览器</title>
<style>
body{
padding-top: 50px;;
}
div {
margin: 20px;
display: inline;
}
</style>
</head>
<body>
很抱歉,我们无法完全兼容低版本的 IE 浏览器,您可以
<a href="http://down.tech.sina.com.cn/page/40975.html">
<div>
<img src="/static/img/chrome.png">使用Chrome
</div>
</a>
<a href="https://www.mozilla.org/zh-CN/firefox/new/?scene=2#download-fx">
<div>
<img src="/static/img/firefox.png">使用FireFox
</div>
</a>
<a href="http://windows.microsoft.com/en-us/internet-explorer/download-ie">
<div>
<img src="/static/img/ie.png">升级IE
</div>
</a>
</body>
</html>

View File

@@ -1,4 +1,4 @@
define("admin", ["jquery", "avalon"], function ($, avalon) {
require(["jquery", "avalon", "bootstrap"], function ($, avalon) {
avalon.ready(function () {

View File

@@ -2,40 +2,46 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
"validator"],
function ($, avalon, editor, uploader, bsAlert, csrfTokenHeader) {
//avalon.vmodels.add_contest = null;
$("#add-contest-form").validator().on('submit', function (e) {
if (!e.isDefaultPrevented()){
if (!e.isDefaultPrevented()) {
e.preventDefault();
var ajaxData = {
title: vm.title,
description: vm.description,
mode: vm.mode,
contest_type: 0,
show_rank: vm.showRank,
real_time_rank: vm.realTimeRank,
show_user_submission: vm.showSubmission,
start_time: vm.startTime,
end_time: vm.endTime,
visible: false
};
if (vm.choseGroupList.length == 0) {
bsAlert("你没有选择参赛用户!");
return false;
var selectedGroups = [];
if (!vm.isGlobal) {
for (var i = 0; i < vm.allGroups.length; i++) {
if (vm.allGroups[i].isSelected) {
selectedGroups.push(vm.allGroups[i].id);
}
}
ajaxData.groups = selectedGroups;
}
if (vm.choseGroupList[0].id == 0) { //everyone | public contest
else {
if (vm.password) {
ajaxData.password = vm.password;
ajaxData.contest_type = 2;
}
else{
else
ajaxData.contest_type = 1;
}
}
else { // Add groups info
ajaxData.groups = [];
for (var i = 0; vm.choseGroupList[i]; i++)
ajaxData.groups.push(parseInt(vm.choseGroupList[i].id))
if (!vm.isGlobal && !selectedGroups.length) {
bsAlert("你没有选择参赛用户!");
return false;
}
if (vm.editDescription == "") {
bsAlert("比赛描述不能为空!");
return false;
}
$.ajax({ // Add contest
beforeSend: csrfTokenHeader,
url: "/api/admin/contest/",
@@ -45,20 +51,18 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
method: "post",
success: function (data) {
if (!data.code) {
bsAlert("添加成功!将转到比赛列表页以便为比赛添加问题(注意比赛当前状态为:隐藏)");
vm.title = "";
vm.description = "";
vm.startTime = "";
vm.endTime = "";
vm.password = "";
vm.mode = "";
vm.showRank = false;
vm.showSubmission = false;
vm.group = "-1";
vm.groupList = [];
vm.choseGroupList = [];
vm.passwordUsable = false;
location.hash = "#contest/contest_list";
bsAlert("添加成功!将转到比赛列表页以便为比赛添加问题(注意比赛当前状态为:隐藏)");
vm.title = "";
vm.description = "";
vm.startTime = "";
vm.endTime = "";
vm.password = "";
vm.mode = "0";
vm.showSubmission = true;
location.hash = "#contest/contest_list";
vm.isGlobal = true;
vm.allGroups = [];
vm.showGlobalViewRadio = true;
}
else {
bsAlert(data.data);
@@ -70,80 +74,59 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
});
editor("#editor");
if (avalon.vmodels.add_contest)
var vm = avalon.vmodels.add_contest;
else
var vm = avalon.define({
$id: "add_contest",
title: "",
description: "",
startTime: "",
endTime: "",
password: "",
mode: "",
showRank: false,
showSubmission: false,
group: "-1",
groupList: [],
choseGroupList: [],
passwordUsable: false,
addGroup: function() {
if (vm.group == -1) return;
if (vm.groupList[vm.group].id == 0){
vm.passwordUsable = true;
vm.choseGroupList = [];
for (var key in vm.groupList){
vm.groupList[key].chose = true;
}
}
vm.groupList[vm.group]. chose = true;
vm.choseGroupList.push({name:vm.groupList[vm.group].name, index:vm.group, id:vm.groupList[vm.group].id});
vm.group = -1;
},
removeGroup: function(groupIndex){
if (vm.groupList[vm.choseGroupList[groupIndex].index].id == 0){
vm.passwordUsable = false;
for (key in vm.groupList){
vm.groupList[key].chose = false;
}
}
vm.groupList[vm.choseGroupList[groupIndex].index].chose = false;
vm.choseGroupList.remove(vm.choseGroupList[groupIndex]);
}
});
if (avalon.vmodels.add_contest)
var vm = avalon.vmodels.add_contest;
else
var vm = avalon.define({
$id: "add_contest",
title: "",
description: "",
startTime: "",
endTime: "",
password: "",
mode: "0",
showSubmission: true,
isGlobal: true,
allGroups: [],
showGlobalViewRadio: true,
realTimeRank: true
});
$.ajax({ // Get current user type
$.ajax({
url: "/api/user/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
if (data.data.admin_type == 2) { // Is super user
vm.isGlobal = true;
vm.groupList.push({id:0,name:"所有人",chose:false});
var admin_type = data.data.admin_type;
if (data.data.admin_type == 1) {
vm.isGlobal = false;
vm.showGlobalViewRadio = false;
}
$.ajax({ // Get the group list of current user
beforeSend: csrfTokenHeader,
url: "/api/admin/group/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
if (!data.data.length) {
return;
}
for (var i = 0; i < data.data.length; i++) {
var item = data.data[i];
item["chose"] = false;
vm.groupList.push(item);
}
}
$.ajax({
url: "/api/admin/group/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
if (!data.data.length) {
if (admin_type != 2)
bsAlert("您的用户权限只能创建小组内比赛,但是您还没有创建过小组");
return;
}
else {
bsAlert(data.data);
for (var i = 0; i < data.data.length; i++) {
var item = data.data[i];
item["isSelected"] = false;
vm.allGroups.push(item);
}
}
});
}
else {
bsAlert(data.data);
}
}
});
}
});

View File

@@ -3,21 +3,39 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
avalon.ready(function () {
$("#edit-contest-form").validator().on('submit', function (e) {
if (!e.isDefaultPrevented()){
if (!e.isDefaultPrevented()) {
e.preventDefault();
var ajaxData = {
id: vm.contestList[vm.editingContestId-1].id,
title: vm.editTitle,
description: vm.editDescription,
mode: vm.editMode,
contest_type: 0,
show_rank: vm.editShowRank,
id: vm.contestList[vm.editingContestId - 1].id,
title: vm.editTitle,
description: vm.editDescription,
mode: vm.editMode,
contest_type: 0,
real_time_rank: vm.editRealTimeRank,
show_user_submission: vm.editShowSubmission,
start_time: vm.editStartTime,
end_time: vm.editEndTime,
visible: vm.editVisible
start_time: vm.editStartTime,
end_time: vm.editEndTime,
visible: vm.editVisible
};
if (vm.choseGroupList.length == 0) {
var selectedGroups = [];
if (!vm.isGlobal) {
for (var i = 0; i < vm.allGroups.length; i++) {
if (vm.allGroups[i].isSelected) {
selectedGroups.push(vm.allGroups[i].id);
}
}
ajaxData.groups = selectedGroups;
}
else {
if (vm.editPassword) {
ajaxData.password = vm.editPassword;
ajaxData.contest_type = 2;
}
else
ajaxData.contest_type = 1;
}
if (!vm.isGlobal && !selectedGroups.length) {
bsAlert("你没有选择参赛用户!");
return false;
}
@@ -25,22 +43,8 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
bsAlert("比赛描述不能为空!");
return false;
}
if (vm.choseGroupList[0].id == 0) { //everyone | public contest
if (vm.editPassword) {
ajaxData.password = vm.editPassword;
ajaxData.contest_type = 2;
}
else{
ajaxData.contest_type = 1;
}
}
else { // Add groups info
ajaxData.groups = [];
for (var i = 0; vm.choseGroupList[i]; i++)
ajaxData.groups.push(parseInt(vm.choseGroupList[i].id))
}
$.ajax({ // Add contest
$.ajax({ // modify contest info
beforeSend: csrfTokenHeader,
url: "/api/admin/contest/",
dataType: "json",
@@ -52,7 +56,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
if (!data.code) {
bsAlert("修改成功!");
vm.editingContestId = 0; // Hide the editor
vm.getPage(1); // Refresh the contest list
vm.getPage(1); // Refresh the contest list
}
else {
bsAlert(data.data);
@@ -63,138 +67,124 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
return false;
});
if(avalon.vmodels.contestList){
// this page has been loaded before, so set the default value
var vm = avalon.vmodels.contestList;
vm.contestList= [];
vm.previousPage= 0;
vm.nextPage= 0;
vm.page= 1;
vm.totalPage= 1;
vm.group= "-1";
vm.groupList= [];
vm.choseGroupList= [];
vm.passwordUsable= false;
vm.keyword= "";
vm.editingContestId= 0;
vm.editTitle= "";
vm.editDescription= "";
vm.editProblemList= [];
vm.editPassword= "";
vm.editStartTime= "";
vm.editEndTime= "";
vm.editMode= "";
vm.editShowRank= false;
vm.editShowSubmission= false;
vm.editProblemList= [];
vm.editVisible= false;
vm.editChoseGroupList= [];
vm.editingProblemContestIndex= 0;
}
else {
var vm = avalon.define({
$id: "contestList",
contestList: [],
previousPage: 0,
nextPage: 0,
page: 1,
totalPage: 1,
showVisibleOnly: false,
group: "-1",
groupList: [],
choseGroupList: [],
passwordUsable: false,
keyword: "",
editingContestId: 0,
editTitle: "",
editDescription: "",
editProblemList: [],
editPassword: "",
editStartTime: "",
editEndTime: "",
editMode: "",
editShowRank: false,
editShowSubmission: false,
editProblemList: [],
editVisible: false,
editChoseGroupList: [],
editingProblemContestIndex: 0,
getNext: function () {
if (!vm.nextPage)
return;
getPageData(vm.page + 1);
},
getPrevious: function () {
if (!vm.previousPage)
return;
getPageData(vm.page - 1);
},
getBtnClass: function (btn) {
if (btn == "next") {
return vm.nextPage ? "btn btn-primary" : "btn btn-primary disabled";
}
else {
return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled";
}
},
getPage: function (page_index) {
getPageData(page_index);
},
showEditContestArea: function (contestId) {
if (vm.editingContestId && !confirm("如果继续将丢失未保存的信息,是否继续?"))
return;
if (contestId == vm.editingContestId)
vm.editingContestId = 0;
else {
vm.editingContestId = contestId;
vm.editTitle = vm.contestList[contestId-1].title;
vm.editPassword = vm.contestList[contestId-1].password;
vm.editStartTime = vm.contestList[contestId-1].start_time.substring(0,16).replace("T"," ");
vm.editEndTime = vm.contestList[contestId-1].end_time.substring(0,16).replace("T"," ");
vm.editMode = vm.contestList[contestId-1].mode;
vm.editVisible = vm.contestList[contestId-1].visible;
if (vm.contestList[contestId-1].contest_type == 0) { //contest type == 0, contest in group
//Clear the choseGroupList
while (vm.choseGroupList.length) {
vm.removeGroup(0);
}
for (var i = 0; i < vm.contestList[contestId-1].groups.length; i++){
var id = parseInt(vm.contestList[contestId-1].groups[i]);
var index = 0;
for (; vm.groupList[index]; index++) {
if (vm.groupList[index].id == id)
break;
if (avalon.vmodels.contestList) {
// this page has been loaded before, so set the default value
var vm = avalon.vmodels.contestList;
vm.contestList = [];
vm.previousPage = 0;
vm.nextPage = 0;
vm.page = 1;
vm.totalPage = 1;
vm.keyword = "";
vm.editingContestId = 0;
vm.editTitle = "";
vm.editDescription = "";
vm.editProblemList = [];
vm.editPassword = "";
vm.editStartTime = "";
vm.editEndTime = "";
vm.editMode = "";
vm.editShowSubmission = false;
vm.editVisible = false;
vm.editingProblemContestIndex = 0;
vm.editRealTimeRank = true;
}
else {
var vm = avalon.define({
$id: "contestList",
contestList: [],
previousPage: 0,
nextPage: 0,
page: 1,
totalPage: 1,
showVisibleOnly: false,
keyword: "",
editingContestId: 0,
editTitle: "",
editDescription: "",
editProblemList: [],
editPassword: "",
editStartTime: "",
editEndTime: "",
editMode: "",
editShowSubmission: false,
editVisible: false,
editRealTimeRank: true,
editingProblemContestIndex: 0,
isGlobal: true,
allGroups: [],
showGlobalViewRadio: true,
admin_type: 1,
getNext: function () {
if (!vm.nextPage)
return;
getPageData(vm.page + 1);
},
getPrevious: function () {
if (!vm.previousPage)
return;
getPageData(vm.page - 1);
},
getBtnClass: function (btn) {
if (btn == "next") {
return vm.nextPage ? "btn btn-primary" : "btn btn-primary disabled";
}
else {
return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled";
}
},
getPage: function (page_index) {
getPageData(page_index);
},
showEditContestArea: function (contestId) {
if (vm.editingContestId && !confirm("如果继续将丢失未保存的信息,是否继续?"))
return;
if (contestId == vm.editingContestId)
vm.editingContestId = 0;
else {
vm.editingContestId = contestId;
vm.editTitle = vm.contestList[contestId - 1].title;
vm.editPassword = vm.contestList[contestId - 1].password;
vm.editStartTime = vm.contestList[contestId - 1].start_time.substring(0, 16).replace("T", " ");
vm.editEndTime = vm.contestList[contestId - 1].end_time.substring(0, 16).replace("T", " ");
vm.editMode = vm.contestList[contestId - 1].mode;
vm.editVisible = vm.contestList[contestId - 1].visible;
vm.editRealTimeRank = vm.contestList[contestId - 1].real_time_rank;
if (vm.contestList[contestId - 1].contest_type == 0) { //contest type == 0, contest in group
vm.isGlobal = false;
for (var i = 0; i < vm.allGroups.length; i++) {
vm.allGroups[i].isSelected = false;
}
for (var i = 0; i < vm.contestList[contestId - 1].groups.length; i++) {
var id = parseInt(vm.contestList[contestId - 1].groups[i]);
for (var index = 0; vm.allGroups[index]; index++) {
if (vm.allGroups[index].id == id) {
vm.allGroups[index].isSelected = true;
break;
}
}
}
vm.groupList[index].chose = true;
vm.choseGroupList.push({
name:vm.groupList[index].name,
index:index,
id:id
});
}
else {
vm.isGlobal = true;
}
vm.editShowSubmission = vm.contestList[contestId - 1].show_user_submission;
editor("#editor").setValue(vm.contestList[contestId - 1].description);
vm.editingProblemContestIndex = 0;
}
else{
vm.group = "0";
vm.addGroup()//vm.editChoseGroupList = [0]; id 0 is for the group of everyone~
},
showEditProblemArea: function (contestId) {
if (vm.editingProblemContestIndex == contestId) {
vm.editingProblemContestIndex = 0;
return;
}
vm.editShowRank = vm.contestList[contestId-1].show_rank;
vm.editShowSubmission = vm.contestList[contestId-1].show_user_submission;
editor("#editor").setValue(vm.contestList[contestId-1].description);
vm.editingProblemContestIndex = 0;
}
},
showEditProblemArea: function(contestId) {
if (vm.editingProblemContestIndex == contestId) {
vm.editingProblemContestIndex = 0;
return;
}
if (vm.editingContestId&&!confirm("如果继续将丢失未保存的信息,是否继续?")){
return;
}
$.ajax({ // Get the problem list of current contest
if (vm.editingContestId && !confirm("如果继续将丢失未保存的信息,是否继续?")) {
return;
}
$.ajax({ // Get the problem list of current contest
beforeSend: csrfTokenHeader,
url: "/api/admin/contest_problem/?contest_id=" + vm.contestList[contestId-1].id,
url: "/api/admin/contest_problem/?contest_id=" + vm.contestList[contestId - 1].id,
method: "get",
dataType: "json",
success: function (data) {
@@ -206,51 +196,60 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
}
}
});
vm.editingContestId = 0;
vm.editingProblemContestIndex = contestId;
vm.editMode = vm.contestList[contestId-1].mode;
},
addGroup: function() {
if (vm.group == -1) return;
if (vm.groupList[vm.group].id == 0){
vm.passwordUsable = true;
vm.choseGroupList = [];
for (var i = 0; i < vm.groupList.length; i++) {
vm.groupList[i].chose = true;
}
}
vm.groupList[vm.group]. chose = true;
// index of the group is relative. It is related to user
vm.choseGroupList.push({name:vm.groupList[vm.group].name, index:vm.group, id:vm.groupList[vm.group].id});
vm.group = -1;
},
removeGroup: function(groupIndex){
if (vm.groupList[vm.choseGroupList[groupIndex].index].id == 0){
vm.passwordUsable = false;
for (var i = 0; i < vm.groupList.length; i++) {
vm.groupList[i].chose = false;
}
}
vm.groupList[vm.choseGroupList[groupIndex].index].chose = false;
vm.choseGroupList.remove(vm.choseGroupList[groupIndex]);
vm.editingContestId = 0;
vm.editingProblemContestIndex = contestId;
vm.editMode = vm.contestList[contestId - 1].mode;
},
addProblem: function () {
vm.$fire("up!showContestProblemPage", 0, vm.contestList[vm.editingProblemContestIndex-1].id, vm.editMode);
},
showProblemEditPage: function(el) {
vm.$fire("up!showContestProblemPage", el.id, vm.contestList[vm.editingProblemContestIndex-1].id, vm.editMode);
},
showSubmissionPage: function(el) {
var problemId = 0
if (el)
problemId = el.id;
vm.$fire("up!showContestSubmissionPage", problemId, vm.contestList[vm.editingProblemContestIndex-1].id, vm.editMode);
}
});
vm.$watch("showVisibleOnly", function() {
getPageData(1);
})
}
addProblem: function () {
vm.$fire("up!showContestProblemPage", 0, vm.contestList[vm.editingProblemContestIndex - 1].id, vm.editMode);
},
showProblemEditPage: function (el) {
vm.$fire("up!showContestProblemPage", el.id, vm.contestList[vm.editingProblemContestIndex - 1].id, vm.editMode);
},
showSubmissionPage: function (el) {
var problemId = 0
if (el)
problemId = el.id;
vm.$fire("up!showContestSubmissionPage", problemId, vm.contestList[vm.editingProblemContestIndex - 1].id, vm.editMode);
},
addToProblemList: function (problem) {
var ajaxData = {
title: problem.title,
description: problem.description,
time_limit: problem.time_limit,
memory_limit: problem.memory_limit,
samples: problem.samples,
test_case_id: problem.test_case_id,
hint: problem.hint,
source: problem.contest.title,
visible: false,
tags: [],
input_description: problem.input_description,
output_description: problem.output_description,
difficulty: 0
};
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/admin/problem/",
dataType: "json",
data: JSON.stringify(ajaxData),
method: "post",
contentType: "application/json",
success: function (data) {
if (!data.code) {
bsAlert("题目添加成功!题目现在处于隐藏状态,请到题目列表手动修改,并添加分类和难度信息!");
}
else {
bsAlert(data.data);
}
}
});
}
});
vm.$watch("showVisibleOnly", function () {
getPageData(1);
})
}
getPageData(1);
//init time picker
@@ -293,39 +292,42 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
}
// Get group list
$.ajax({ // Get current user type
$.ajax({
url: "/api/user/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
if (data.data.admin_type == 2) { // Is super user
vm.isGlobal = true;
vm.groupList.push({id:0,name:"所有人",chose:false});
var admin_type = data.data.admin_type;
vm.admin_type = admin_type;
if (data.data.admin_type == 1) {
vm.isGlobal = false;
vm.showGlobalViewRadio = false;
}
$.ajax({ // Get the group list of current user
beforeSend: csrfTokenHeader,
url: "/api/admin/group/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
if (!data.data.length) {
//this user have no group can use
return;
}
for (var i = 0; i < data.data.length; i++) {
var item = data.data[i];
item["chose"] = false;
vm.groupList.push(item);
}
}
$.ajax({
url: "/api/admin/group/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
if (!data.data.length) {
if (admin_type != 2)
bsAlert("您的用户权限只能创建小组内比赛,但是您还没有创建过小组");
return;
}
else {
bsAlert(data.data);
for (var i = 0; i < data.data.length; i++) {
var item = data.data[i];
item["isSelected"] = false;
vm.allGroups.push(item);
}
}
});
}
else {
bsAlert(data.data);
}
}
});
}
});

View File

@@ -13,6 +13,10 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
bsAlert("题目描述不能为空!");
return false;
}
if (vm.timeLimit < 100 || vm.timeLimit > 5000) {
bsAlert("保证时间限制是一个100-5000的合法整数");
return false;
}
if (vm.samples.length == 0) {
bsAlert("请至少添加一组样例!");
return false;

View File

@@ -14,8 +14,8 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
bsAlert("题目描述不能为空!");
return false;
}
if (vm.timeLimit < 1000 || vm.timeLimit > 5000) {
bsAlert("保证时间限制是一个1000-5000的合法整数");
if (vm.timeLimit < 100 || vm.timeLimit > 5000) {
bsAlert("保证时间限制是一个100-5000的合法整数");
return false;
}
if (vm.samples.length == 0) {

View File

@@ -1,13 +1,22 @@
require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, csrfTokenHeader) {
function refresh_captcha(){
$("#captcha-img")[0].src = "/captcha/?" + Math.random();
$("#captcha")[0].value = "";
}
$("#captcha-img").click(function(){
refresh_captcha();
});
$('form').validator().on('submit', function (e) {
e.preventDefault();
var newPassword = $("#new_password ").val();
var password = $("#password").val();
var captcha = $("#captcha").val();
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/change_password/",
data: {new_password: newPassword, old_password: password},
data: {new_password: newPassword, old_password: password, captcha: captcha},
dataType: "json",
method: "post",
success: function (data) {
@@ -15,6 +24,7 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
window.location.href = "/login/";
}
else {
refresh_captcha();
bsAlert(data.data);
}
}

View File

@@ -1,26 +1,31 @@
require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, csrfTokenHeader) {
var applied_captcha = false;
$('form').validator().on('submit', function (e) {
if (!e.isDefaultPrevented()) {
var username = $("#username").val();
var password = $("#password").val();
var ajaxData = {username: username, password: password};
if (applied_captcha) {
ajaxData.captcha = $("#captcha").val();
}
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/login/",
data: {username: username, password: password},
data: ajaxData,
dataType: "json",
method: "post",
success: function (data) {
if (!data.code) {
//成功登陆
var ref = document.referrer;
if(ref){
if (ref) {
// 注册页和本页的来源的跳转回首页,防止死循环
if(ref.indexOf("register") > -1 || ref.indexOf("login") > -1){
if (ref.indexOf("register") > -1 || ref.indexOf("login") > -1) {
location.href = "/";
return;
}
// 判断来源,只有同域下才跳转
if(ref.split("/")[2].split(":")[0] == location.hostname){
if (ref.split("/")[2].split(":")[0] == location.hostname) {
location.href = ref;
return;
}
@@ -28,6 +33,7 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
location.href = "/";
}
else {
refresh_captcha();
bsAlert(data.data);
}
}
@@ -35,5 +41,34 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
});
return false;
}
})
});
$('#username').blur(function () {
if ($("#username").val()) {
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/account_security_check/?username=" + $("#username").val(),
method: "get",
success: function (data) {
if (!data.code) {
if (data.data.applied_captcha) {
$('#captcha-area').html('<label for="captcha">验证码</label>&nbsp;&nbsp;<img src="/captcha/" id="captcha-img"><small><p></p></small><input type="text" class="form-control input-lg" id="captcha" name="captcha" placeholder="验证码" maxlength="4" data-error="请填写验证码" required><div class="help-block with-errors"></div>');
applied_captcha = true;
}
else {
$('#captcha-area').html('');
applied_captcha = false;
}
}
}
});
}
});
function refresh_captcha(){
$("#captcha-img")[0].src = "/captcha/?" + Math.random();
$("#captcha")[0].value = "";
}
$("#captcha-img").click(function(){
refresh_captcha();
});
});

View File

@@ -5,10 +5,11 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
var realName = $("#real_name").val();
var password = $("#password").val();
var email = $("#email").val();
var captcha = $("#captcha").val();
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/register/",
data: {username: username, real_name: realName, password: password, email: email},
data: {username: username, real_name: realName, password: password, email: email, captcha:captcha},
dataType: "json",
method: "post",
success: function (data) {
@@ -16,11 +17,20 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
window.location.href = "/login/";
}
else {
refresh_captcha();
bsAlert(data.data);
}
}
});
return false;
}
})
});
function refresh_captcha() {
$("#captcha-img")[0].src = "/captcha/?" + Math.random();
$("#captcha")[0].value = "";
}
$("#captcha-img").click(function () {
refresh_captcha();
});
});

View File

@@ -1,157 +1,252 @@
require(["jquery", "codeMirror", "csrfToken", "bsAlert"], function ($, codeMirror, csrfTokenHeader, bsAlert) {
var codeEditor = codeMirror($("#code-editor")[0], "text/x-csrc");
var language = $("input[name='language'][checked]").val();
var submissionId;
$("input[name='language']").change(function () {
language = this.value;
var languageTypes = {"1": "text/x-csrc", "2": "text/x-c++src", "3": "text/x-java"};
codeEditor.setOption("mode", languageTypes[language]);
});
$("#show-more-btn").click(function () {
$(".hide").attr("class", "problem-section");
$("#show-more-btn").hide();
});
function showLoading() {
$("#submit-code-button").attr("disabled", "disabled");
$("#loading-gif").show();
}
function hideLoading() {
$("#submit-code-button").removeAttr("disabled");
$("#loading-gif").hide();
}
function getResultHtml(data) {
// 0 结果正确 1 运行错误 2 超时 3 超内存 4 编译错误
// 5 格式错误 6 结果错误 7 系统错误 8 等待判题
var results = {
0: {"alert_class": "success", message: "Accepted"},
1: {"alert_class": "danger", message: "Runtime Error"},
2: {"alert_class": "warning", message: "Time Limit Exceeded"},
3: {"alert_class": "warning", message: "Memory Limit Exceeded"},
4: {"alert_class": "danger", message: "Compile Error"},
5: {"alert_class": "warning", message: "Format Error"},
6: {"alert_class": "danger", message: "Wrong Answer"},
7: {"alert_class": "danger", message: "System Error"},
8: {"alert_class": "info", message: "Waiting"}
};
var html = '<div class="alert alert-' +
results[data.result].alert_class + ' result"' +
' role="alert">' +
'<div class="alert-link">' +
results[data.result].message +
'!&nbsp;&nbsp; ';
if (!data.result) {
html += "CPU time: " + data.accepted_answer_time + "ms &nbsp;&nbsp;";
require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"],
function ($, codeMirror, csrfTokenHeader, bsAlert, ZeroClipboard) {
// 复制样例需要 Flash 的支持 检测浏览器是否安装了 Flash
function detect_flash() {
var ie_flash;
try {
ie_flash = (window.ActiveXObject && (new ActiveXObject("ShockwaveFlash.ShockwaveFlash")) !== false)
} catch (err) {
ie_flash = false;
}
var _flash_installed = ((typeof navigator.plugins != "undefined" && typeof navigator.plugins["Shockwave Flash"] == "object") || ie_flash);
return _flash_installed;
}
html += ('<a href="/submission/' + submissionId + '/" target="_blank">查看详情</a></div> </div>');
return html;
}
if(detect_flash()) {
// 提供点击复制到剪切板的功能
ZeroClipboard.config({swfPath: "/static/img/ZeroClipboard.swf"});
new ZeroClipboard($(".copy-sample"));
}
else{
$(".copy-sample").hide();
}
var counter = 0;
function getResult() {
if (counter++ > 10) {
hideLoading();
bsAlert("抱歉,服务器可能出现了故障,请稍后到我的提交列表中查看");
counter = 0;
var codeEditorSelector = $("#code-editor")[0];
// 部分界面逻辑会隐藏代码输入框,先判断有没有。
if (codeEditorSelector == undefined) {
return;
}
var codeEditor = codeMirror(codeEditorSelector, "text/x-csrc");
var language = $("input[name='language'][checked]").val();
var submissionId;
$("input[name='language']").change(function () {
language = this.value;
var languageTypes = {"1": "text/x-csrc", "2": "text/x-c++src", "3": "text/x-java"};
codeEditor.setOption("mode", languageTypes[language]);
});
$("#show-more-btn").click(function () {
$(".hide").attr("class", "problem-section");
$("#show-more-btn").hide();
});
function showLoading() {
$("#submit-code-button").attr("disabled", "disabled");
$("#loading-gif").show();
}
function hideLoading() {
$("#submit-code-button").removeAttr("disabled");
$("#loading-gif").hide();
}
function getResultHtml(data) {
// 0 结果正确 1 运行错误 2 超时 3 超内存 4 编译错误
// 5 格式错误 6 结果错误 7 系统错误 8 等待判题
var results = {
0: {"alert_class": "success", message: "Accepted"},
1: {"alert_class": "danger", message: "Runtime Error"},
2: {"alert_class": "warning", message: "Time Limit Exceeded"},
3: {"alert_class": "warning", message: "Memory Limit Exceeded"},
4: {"alert_class": "danger", message: "Compile Error"},
5: {"alert_class": "warning", message: "Format Error"},
6: {"alert_class": "danger", message: "Wrong Answer"},
7: {"alert_class": "danger", message: "System Error"},
8: {"alert_class": "info", message: "Waiting"}
};
var html = '<div class="alert alert-' +
results[data.result].alert_class + ' result"' +
' role="alert">' +
'<div class="alert-link">' +
results[data.result].message +
'!&nbsp;&nbsp; ';
if (!data.result) {
html += "CPU time: " + data.accepted_answer_time + "ms &nbsp;&nbsp;";
}
html += ('<a href="/submission/' + submissionId + '/" target="_blank">查看详情</a></div> </div>');
return html;
}
var counter = 0;
function getResult() {
if (counter++ > 10) {
hideLoading();
bsAlert("抱歉,服务器正在紧张判题中,请稍后到我的提交列表中查看");
counter = 0;
return;
}
$.ajax({
url: "/api/submission/?submission_id=" + submissionId,
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
// 8是还没有完成判题
if (data.data.result == 8) {
// 1秒之后重新去获取
setTimeout(getResult, 1000);
}
else {
counter = 0;
hideLoading();
$("#result").html(getResultHtml(data.data));
}
}
else {
bsAlert(data.data);
hideLoading();
}
}
})
}
function guessLanguage(code) {
//cpp
if (code.indexOf("using namespace std") > -1||code.indexOf("<cstdio>") > -1) {
return "2";
}
if (code.indexOf("printf") > -1)
{
return "1";
}
//java
if (code.indexOf("public class Main") > -1) {
return "3";
}
}
function getServerTime(){
var contestId = location.pathname.split("/")[2];
var time = 0;
$.ajax({
url: "/api/contest/time/?contest_id=" + contestId + "&type=end",
dataType: "json",
method: "get",
async: false,
success: function(data){
if(!data.code){
time = data.data;
}
},
error: function(){
time = new Date().getTime();
}
});
return time;
}
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() + "分钟");
}
}, 1000 * 60);
}
$("#submit-code-button").click(function () {
var code = codeEditor.getValue();
if (!code.trim()) {
bsAlert("请填写代码!");
hideLoading();
return false;
}
if (guessLanguage(code) != language) {
if (!confirm("您选择的代码语言可能存在错误,是否继续提交?")) {
return;
}
}
if (language < 3) {
if (code.indexOf("__int64") > -1) {
if (!confirm("您是否在尝试使用'__int64'类型? 这不是 c/c++ 标准并将引发编译错误可以使用 'long long' 代替(详见 关于->帮助),是否仍然提交?")) {
return;
}
}
if (code.indexOf("%I64d") > -1) {
if (!confirm("您是否在尝试将'%I64d'用于long long类型的I/O? 这不被支持,并可能会导致程序输出异常,可以使用 '%lld' 代替(详见 关于->帮助),是否仍然提交?")) {
return;
}
}
}
if (location.href.indexOf("contest") > -1) {
var problemId = location.pathname.split("/")[4];
var contestId = location.pathname.split("/")[2];
var url = "/api/contest/submission/";
var data = {
problem_id: problemId,
language: language,
code: code,
contest_id: contestId
};
}
else {
var problemId = window.location.pathname.split("/")[2];
var url = "/api/submission/";
var data = {
problem_id: problemId,
language: language,
code: code
};
}
showLoading();
$("#result").html("");
$.ajax({
beforeSend: csrfTokenHeader,
url: url,
method: "post",
data: JSON.stringify(data),
contentType: "application/json",
success: function (data) {
if (!data.code) {
submissionId = data.data.submission_id;
// 获取到id 之后2秒去查询一下判题结果
setTimeout(getResult, 2000);
}
else {
bsAlert(data.data);
hideLoading();
}
}
});
});
$.ajax({
url: "/api/submission/?submission_id=" + submissionId,
url: "/api/user/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
// 8是还没有完成判题
if (data.data.result == 8) {
// 1秒之后重新去获取
setTimeout(getResult, 1000);
}
else {
counter = 0;
hideLoading();
$("#result").html(getResultHtml(data.data));
}
}
else {
bsAlert(data.data);
hideLoading();
if (data.code) {
$("#submit-code-button").attr("disabled", "disabled");
$("#result").html('<div class="alert alert-danger" role="alert"><div class="alert-link">请先<a href="/login/" target="_blank">登录</a>!</div> </div>');
}
}
})
}
$("#submit-code-button").click(function () {
var code = codeEditor.getValue();
if (location.href.indexOf("contest") > -1) {
var problemId = location.pathname.split("/")[4];
var contestId = location.pathname.split("/")[2];
var url = "/api/contest/submission/";
var data = {
problem_id: problemId,
language: language,
code: code,
contest_id: contestId
};
}
else {
var problemId = window.location.pathname.split("/")[2];
var url = "/api/submission/";
var data = {
problem_id: problemId,
language: language,
code: code
};
}
showLoading();
if (!code.trim()) {
bsAlert("请填写代码!");
hideLoading();
return false;
}
$("#result").html("");
$.ajax({
beforeSend: csrfTokenHeader,
url: url,
method: "post",
data: JSON.stringify(data),
contentType: "application/json",
success: function (data) {
if (!data.code) {
submissionId = data.data.submission_id;
// 获取到id 之后2秒去查询一下判题结果
setTimeout(getResult, 2000);
}
else {
bsAlert(data.data);
hideLoading();
}
}
});
});
$.ajax({
url: "/api/user/",
method: "get",
dataType: "json",
success: function (data) {
if (data.code) {
$("#submit-code-button").attr("disabled", "disabled");
$("#result").html('<div class="alert alert-danger" role="alert"><div class="alert-link">请先<a href="/login/" target="_blank">登录</a>!</div> </div>');
}
}
})
});

View File

@@ -1,6 +0,0 @@
require(["jquery", "avalon"], function($, avalon){
var vm = avalon.define({
$id: "problem_list",
problem_list: []
})
});

View File

@@ -1,10 +1,10 @@
({
// RequireJS 通过一个相对的路径 baseUrl来加载所有代码。baseUrl通常被设置成data-main属性指定脚本的同级目录。
baseUrl: "js/",
baseUrl: "./js",
// 第三方脚本模块的别名,jquery比libs/jquery-1.11.1.min.js简洁明了
paths: {
jquery: "lib/jquery/jquery",
avalon: "lib/avalon/avalon",
jquery: "empty:",
avalon: "empty:",
editor: "utils/editor",
uploader: "utils/uploader",
formValidation: "utils/formValidation",
@@ -18,9 +18,9 @@
tagEditor: "lib/tagEditor/jquery.tag-editor.min",
jqueryUI: "lib/jqueryUI/jquery-ui",
bootstrap: "lib/bootstrap/bootstrap",
datetimePicker: "lib/datetime_picker/bootstrap-datetimepicker.zh-CN",
datetimePicker: "lib/datetime_picker/bootstrap-datetimepicker",
validator: "lib/validator/validator",
ZeroClipboard: "lib/ZeroClipboard/ZeroClipboard",
// ------ 下面写的都不要直接用,而是使用上面的封装版本 ------
//富文本编辑器simditor -> editor
@@ -33,35 +33,109 @@
_codeMirror: "lib/codeMirror/codemirror",
codeMirrorClang: "lib/codeMirror/language/clike",
// bootstrap组件
modal: "lib/bootstrap/modal",
dropdown: "lib/bootstrap/dropdown",
transition: "lib/bootstrap/transition",
//百度webuploader -> uploader
webUploader: "lib/webuploader/webuploader",
"_datetimePicker": "lib/datetime_picker/bootstrap-datetimepicker"
//"_datetimePicker": "lib/datetime_picker/bootstrap-datetimepicker",
},
shim: {
"bootstrap": {"deps": ['jquery']},
"_datetimepicker": {"deps": ["jquery"]},
"datetimepicker": {"deps": ["_datetimepicker"]}
//以下都是页面 script 标签引用的js
addProblem_0_pack: "app/admin/problem/addProblem",
addContest_1_pack: "app/admin/contest/addContest",
problem_2_pack: "app/admin/problem/problem",
register_3_pack: "app/oj/account/register",
contestList_4_pack: "app/admin/contest/contestList",
group_5_pack: "app/oj/group/group",
editProblem_6_pack: "app/admin/problem/editProblem",
announcement_7_pack: "app/admin/announcement/announcement",
monitor_8_pack: "app/admin/monitor/monitor",
groupDetail_9_pack: "app/admin/group/groupDetail",
admin_10_pack: "app/admin/admin",
problem_11_pack: "app/oj/problem/problem",
submissionList_12_pack: "app/admin/problem/submissionList",
editProblem_13_pack: "app/admin/contest/editProblem",
joinGroupRequestList_14_pack: "app/admin/group/joinGroupRequestList",
changePassword_15_pack: "app/oj/account/changePassword",
group_16_pack: "app/admin/group/group",
submissionList_17_pack: "app/admin/contest/submissionList",
login_18_pack: "app/oj/account/login",
contestPassword_19_pack: "app/oj/contest/contestPassword",
userList_20_pack: "app/admin/user/userList"
},
findNestedDependencies: true,
appDir: "../",
dir: "../../release/",
modules: [
{
name: "submit_code"
name: "bootstrap",
},
{
name: "validation"
name: "addProblem_0_pack"
},
{
name: "editor"
name: "addContest_1_pack"
},
{
name: "code_mirror"
name: "problem_2_pack"
},
{
name: "datetimepicker"
name: "register_3_pack"
},
{
name: "contestList_4_pack"
},
{
name: "group_5_pack"
},
{
name: "editProblem_6_pack"
},
{
name: "announcement_7_pack"
},
{
name: "monitor_8_pack"
},
{
name: "groupDetail_9_pack"
},
{
name: "admin_10_pack"
},
{
name: "problem_11_pack"
},
{
name: "submissionList_12_pack"
},
{
name: "editProblem_13_pack"
},
{
name: "joinGroupRequestList_14_pack"
},
{
name: "changePassword_15_pack"
},
{
name: "group_16_pack"
},
{
name: "submissionList_17_pack"
},
{
name: "login_18_pack"
},
{
name: "contestPassword_19_pack"
},
{
name: "userList_20_pack"
}
]
],
optimizeCss: "standard",
})

View File

@@ -2,7 +2,6 @@ var require = {
// RequireJS 通过一个相对的路径 baseUrl来加载所有代码。baseUrl通常被设置成data-main属性指定脚本的同级目录。
baseUrl: "/static/js/",
paths: {
jquery: "lib/jquery/jquery",
avalon: "lib/avalon/avalon",
editor: "utils/editor",
@@ -18,8 +17,9 @@ var require = {
tagEditor: "lib/tagEditor/jquery.tag-editor.min",
jqueryUI: "lib/jqueryUI/jquery-ui",
bootstrap: "lib/bootstrap/bootstrap",
datetimePicker: "lib/datetime_picker/bootstrap-datetimepicker.zh-CN",
datetimePicker: "lib/datetime_picker/bootstrap-datetimepicker",
validator: "lib/validator/validator",
ZeroClipboard: "lib/ZeroClipboard/ZeroClipboard",
// ------ 下面写的都不要直接用,而是使用上面的封装版本 ------
@@ -33,15 +33,37 @@ var require = {
_codeMirror: "lib/codeMirror/codemirror",
codeMirrorClang: "lib/codeMirror/language/clike",
// bootstrap组件
modal: "lib/bootstrap/modal",
dropdown: "lib/bootstrap/dropdown",
transition: "lib/bootstrap/transition",
//百度webuploader -> uploader
webUploader: "lib/webuploader/webuploader",
"_datetimePicker": "lib/datetime_picker/bootstrap-datetimepicker"
},
shim: {
bootstrap: {deps: ["jquery"]},
_datetimePicker: {dep: ["jquery"]},
datetimePicker: {deps: ["_datetimePicker"]},
validator: ["jquery"]
// "_datetimePicker": "lib/datetime_picker/bootstrap-datetimepicker",
//以下都是页面 script 标签引用的js
addProblem_0_pack: "app/admin/problem/addProblem",
addContest_1_pack: "app/admin/contest/addContest",
problem_2_pack: "app/admin/problem/problem",
register_3_pack: "app/oj/account/register",
contestList_4_pack: "app/admin/contest/contestList",
group_5_pack: "app/oj/group/group",
editProblem_6_pack: "app/admin/problem/editProblem",
announcement_7_pack: "app/admin/announcement/announcement",
monitor_8_pack: "app/admin/monitor/monitor",
groupDetail_9_pack: "app/admin/group/groupDetail",
admin_10_pack: "app/admin/admin",
problem_11_pack: "app/oj/problem/problem",
submissionList_12_pack: "app/admin/problem/submissionList",
editProblem_13_pack: "app/admin/contest/editProblem",
joinGroupRequestList_14_pack: "app/admin/group/joinGroupRequestList",
changePassword_15_pack: "app/oj/account/changePassword",
group_16_pack: "app/admin/group/group",
submissionList_17_pack: "app/admin/contest/submissionList",
login_18_pack: "app/oj/account/login",
contestPassword_19_pack: "app/oj/contest/contestPassword",
userList_20_pack: "app/admin/user/userList"
}
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,168 @@
define([ 'jquery', 'transition' ], function ( jQuery ) {
/* ========================================================================
* Bootstrap: dropdown.js v3.3.5
* http://getbootstrap.com/javascript/#dropdowns
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// DROPDOWN CLASS DEFINITION
// =========================
var backdrop = '.dropdown-backdrop'
var toggle = '[data-toggle="dropdown"]'
var Dropdown = function (element) {
$(element).on('click.bs.dropdown', this.toggle)
}
Dropdown.VERSION = '3.3.5'
function getParent($this) {
var selector = $this.attr('data-target')
if (!selector) {
selector = $this.attr('href')
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
var $parent = selector && $(selector)
return $parent && $parent.length ? $parent : $this.parent()
}
function clearMenus(e) {
if (e && e.which === 3) return
$(backdrop).remove()
$(toggle).each(function () {
var $this = $(this)
var $parent = getParent($this)
var relatedTarget = { relatedTarget: this }
if (!$parent.hasClass('open')) return
if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
if (e.isDefaultPrevented()) return
$this.attr('aria-expanded', 'false')
$parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))
})
}
Dropdown.prototype.toggle = function (e) {
var $this = $(this)
if ($this.is('.disabled, :disabled')) return
var $parent = getParent($this)
var isActive = $parent.hasClass('open')
clearMenus()
if (!isActive) {
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
// if mobile we use a backdrop because click events don't delegate
$(document.createElement('div'))
.addClass('dropdown-backdrop')
.insertAfter($(this))
.on('click', clearMenus)
}
var relatedTarget = { relatedTarget: this }
$parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
if (e.isDefaultPrevented()) return
$this
.trigger('focus')
.attr('aria-expanded', 'true')
$parent
.toggleClass('open')
.trigger($.Event('shown.bs.dropdown', relatedTarget))
}
return false
}
Dropdown.prototype.keydown = function (e) {
if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
var $this = $(this)
e.preventDefault()
e.stopPropagation()
if ($this.is('.disabled, :disabled')) return
var $parent = getParent($this)
var isActive = $parent.hasClass('open')
if (!isActive && e.which != 27 || isActive && e.which == 27) {
if (e.which == 27) $parent.find(toggle).trigger('focus')
return $this.trigger('click')
}
var desc = ' li:not(.disabled):visible a'
var $items = $parent.find('.dropdown-menu' + desc)
if (!$items.length) return
var index = $items.index(e.target)
if (e.which == 38 && index > 0) index-- // up
if (e.which == 40 && index < $items.length - 1) index++ // down
if (!~index) index = 0
$items.eq(index).trigger('focus')
}
// DROPDOWN PLUGIN DEFINITION
// ==========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.dropdown')
if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
if (typeof option == 'string') data[option].call($this)
})
}
var old = $.fn.dropdown
$.fn.dropdown = Plugin
$.fn.dropdown.Constructor = Dropdown
// DROPDOWN NO CONFLICT
// ====================
$.fn.dropdown.noConflict = function () {
$.fn.dropdown = old
return this
}
// APPLY TO STANDARD DROPDOWN ELEMENTS
// ===================================
$(document)
.on('click.bs.dropdown.data-api', clearMenus)
.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
.on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
}(jQuery);
});

View File

@@ -0,0 +1,340 @@
define([ 'jquery', 'transition' ], function ( jQuery ) {
/* ========================================================================
* Bootstrap: modal.js v3.3.5
* http://getbootstrap.com/javascript/#modals
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// MODAL CLASS DEFINITION
// ======================
var Modal = function (element, options) {
this.options = options
this.$body = $(document.body)
this.$element = $(element)
this.$dialog = this.$element.find('.modal-dialog')
this.$backdrop = null
this.isShown = null
this.originalBodyPad = null
this.scrollbarWidth = 0
this.ignoreBackdropClick = false
if (this.options.remote) {
this.$element
.find('.modal-content')
.load(this.options.remote, $.proxy(function () {
this.$element.trigger('loaded.bs.modal')
}, this))
}
}
Modal.VERSION = '3.3.5'
Modal.TRANSITION_DURATION = 300
Modal.BACKDROP_TRANSITION_DURATION = 150
Modal.DEFAULTS = {
backdrop: true,
keyboard: true,
show: true
}
Modal.prototype.toggle = function (_relatedTarget) {
return this.isShown ? this.hide() : this.show(_relatedTarget)
}
Modal.prototype.show = function (_relatedTarget) {
var that = this
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
this.$element.trigger(e)
if (this.isShown || e.isDefaultPrevented()) return
this.isShown = true
this.checkScrollbar()
this.setScrollbar()
this.$body.addClass('modal-open')
this.escape()
this.resize()
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
this.$dialog.on('mousedown.dismiss.bs.modal', function () {
that.$element.one('mouseup.dismiss.bs.modal', function (e) {
if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
})
})
this.backdrop(function () {
var transition = $.support.transition && that.$element.hasClass('fade')
if (!that.$element.parent().length) {
that.$element.appendTo(that.$body) // don't move modals dom position
}
that.$element
.show()
.scrollTop(0)
that.adjustDialog()
if (transition) {
that.$element[0].offsetWidth // force reflow
}
that.$element.addClass('in')
that.enforceFocus()
var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
transition ?
that.$dialog // wait for modal to slide in
.one('bsTransitionEnd', function () {
that.$element.trigger('focus').trigger(e)
})
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
that.$element.trigger('focus').trigger(e)
})
}
Modal.prototype.hide = function (e) {
if (e) e.preventDefault()
e = $.Event('hide.bs.modal')
this.$element.trigger(e)
if (!this.isShown || e.isDefaultPrevented()) return
this.isShown = false
this.escape()
this.resize()
$(document).off('focusin.bs.modal')
this.$element
.removeClass('in')
.off('click.dismiss.bs.modal')
.off('mouseup.dismiss.bs.modal')
this.$dialog.off('mousedown.dismiss.bs.modal')
$.support.transition && this.$element.hasClass('fade') ?
this.$element
.one('bsTransitionEnd', $.proxy(this.hideModal, this))
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
this.hideModal()
}
Modal.prototype.enforceFocus = function () {
$(document)
.off('focusin.bs.modal') // guard against infinite focus loop
.on('focusin.bs.modal', $.proxy(function (e) {
if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
this.$element.trigger('focus')
}
}, this))
}
Modal.prototype.escape = function () {
if (this.isShown && this.options.keyboard) {
this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
e.which == 27 && this.hide()
}, this))
} else if (!this.isShown) {
this.$element.off('keydown.dismiss.bs.modal')
}
}
Modal.prototype.resize = function () {
if (this.isShown) {
$(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
} else {
$(window).off('resize.bs.modal')
}
}
Modal.prototype.hideModal = function () {
var that = this
this.$element.hide()
this.backdrop(function () {
that.$body.removeClass('modal-open')
that.resetAdjustments()
that.resetScrollbar()
that.$element.trigger('hidden.bs.modal')
})
}
Modal.prototype.removeBackdrop = function () {
this.$backdrop && this.$backdrop.remove()
this.$backdrop = null
}
Modal.prototype.backdrop = function (callback) {
var that = this
var animate = this.$element.hasClass('fade') ? 'fade' : ''
if (this.isShown && this.options.backdrop) {
var doAnimate = $.support.transition && animate
this.$backdrop = $(document.createElement('div'))
.addClass('modal-backdrop ' + animate)
.appendTo(this.$body)
this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
if (this.ignoreBackdropClick) {
this.ignoreBackdropClick = false
return
}
if (e.target !== e.currentTarget) return
this.options.backdrop == 'static'
? this.$element[0].focus()
: this.hide()
}, this))
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
this.$backdrop.addClass('in')
if (!callback) return
doAnimate ?
this.$backdrop
.one('bsTransitionEnd', callback)
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
callback()
} else if (!this.isShown && this.$backdrop) {
this.$backdrop.removeClass('in')
var callbackRemove = function () {
that.removeBackdrop()
callback && callback()
}
$.support.transition && this.$element.hasClass('fade') ?
this.$backdrop
.one('bsTransitionEnd', callbackRemove)
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
callbackRemove()
} else if (callback) {
callback()
}
}
// these following methods are used to handle overflowing modals
Modal.prototype.handleUpdate = function () {
this.adjustDialog()
}
Modal.prototype.adjustDialog = function () {
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
this.$element.css({
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
})
}
Modal.prototype.resetAdjustments = function () {
this.$element.css({
paddingLeft: '',
paddingRight: ''
})
}
Modal.prototype.checkScrollbar = function () {
var fullWindowWidth = window.innerWidth
if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
var documentElementRect = document.documentElement.getBoundingClientRect()
fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
}
this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
this.scrollbarWidth = this.measureScrollbar()
}
Modal.prototype.setScrollbar = function () {
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
this.originalBodyPad = document.body.style.paddingRight || ''
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
}
Modal.prototype.resetScrollbar = function () {
this.$body.css('padding-right', this.originalBodyPad)
}
Modal.prototype.measureScrollbar = function () { // thx walsh
var scrollDiv = document.createElement('div')
scrollDiv.className = 'modal-scrollbar-measure'
this.$body.append(scrollDiv)
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
this.$body[0].removeChild(scrollDiv)
return scrollbarWidth
}
// MODAL PLUGIN DEFINITION
// =======================
function Plugin(option, _relatedTarget) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.modal')
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
if (typeof option == 'string') data[option](_relatedTarget)
else if (options.show) data.show(_relatedTarget)
})
}
var old = $.fn.modal
$.fn.modal = Plugin
$.fn.modal.Constructor = Modal
// MODAL NO CONFLICT
// =================
$.fn.modal.noConflict = function () {
$.fn.modal = old
return this
}
// MODAL DATA-API
// ==============
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
var $this = $(this)
var href = $this.attr('href')
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
if ($this.is('a')) e.preventDefault()
$target.one('show.bs.modal', function (showEvent) {
if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
$target.one('hidden.bs.modal', function () {
$this.is(':visible') && $this.trigger('focus')
})
})
Plugin.call($target, option, this)
})
}(jQuery);
});

View File

@@ -0,0 +1,62 @@
define([ 'jquery', 'transition' ], function ( jQuery ) {
/* ========================================================================
* Bootstrap: transition.js v3.3.5
* http://getbootstrap.com/javascript/#transitions
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
// ============================================================
function transitionEnd() {
var el = document.createElement('bootstrap')
var transEndEventNames = {
WebkitTransition : 'webkitTransitionEnd',
MozTransition : 'transitionend',
OTransition : 'oTransitionEnd otransitionend',
transition : 'transitionend'
}
for (var name in transEndEventNames) {
if (el.style[name] !== undefined) {
return { end: transEndEventNames[name] }
}
}
return false // explicit for ie8 ( ._.)
}
// http://blog.alexmaccaw.com/css-transitions
$.fn.emulateTransitionEnd = function (duration) {
var called = false
var $el = this
$(this).one('bsTransitionEnd', function () { called = true })
var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
setTimeout(callback, duration)
return this
}
$(function () {
$.support.transition = transitionEnd()
if (!$.support.transition) return
$.event.special.bsTransitionEnd = {
bindType: $.support.transition.end,
delegateType: $.support.transition.end,
handle: function (e) {
if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
}
}
})
}(jQuery);
});

View File

@@ -26,8 +26,15 @@
*
* Make it work in bootstrap v3
*/
!function ($) {
(function(factory){
if (typeof define === "function" && define.amd) {
define(["jquery"], factory);
} else if (typeof exports === 'object') {
factory(require('jquery'));
} else {
factory(jQuery);
}
}(function ($, undefined) {
function UTCDate() {
return new Date(Date.UTC.apply(Date, arguments));
@@ -1764,6 +1771,17 @@
'</div>';
$.fn.datetimepicker.DPGlobal = DPGlobal;
$.fn.datetimepicker.dates['zh-CN'] = {
days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"],
daysMin: ["日", "一", "二", "三", "四", "五", "六", "日"],
months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
today: "今天",
suffix: [],
meridiem: ["上午", "下午"]
};
/* DATETIMEPICKER NO CONFLICT
* =================== */
@@ -1790,4 +1808,4 @@
$('[data-provide="datetimepicker-inline"]').datetimepicker();
});
}(window.jQuery);
}));

View File

@@ -2,7 +2,13 @@
* Simplified Chinese translation for bootstrap-datetimepicker
* Yuan Cheung <advanimal@gmail.com>
*/
;(function($){
!function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery', '_datetimePicker'], factory);
} else {
factory(root.jQuery);
}
}(this, function($){
$.fn.datetimepicker.dates['zh-CN'] = {
days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"],
@@ -13,4 +19,4 @@
suffix: [],
meridiem: ["上午", "下午"]
};
}(jQuery));
});

View File

@@ -143,6 +143,16 @@ Uploader = (function(superClass) {
processData: false,
contentType: false,
type: 'POST',
beforeSend: function(){
var name = "csrftoken=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1);
if (c.indexOf(name) != -1) name = c.substring(name.length, c.length);
}
arguments[0].setRequestHeader("X-CSRFToken", name);
},
headers: {
'X-File-Name': encodeURIComponent(file.name)
},

View File

@@ -25,8 +25,13 @@
* THE SOFTWARE.
* ======================================================================== */
+function ($) {
!function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else {
factory(root.jQuery);
}
}(this, function ($) {
'use strict';
// VALIDATOR CLASS DEFINITION
@@ -322,4 +327,4 @@
})
})
}(jQuery);
});

View File

@@ -8,7 +8,7 @@ define("editor", ["simditor"], function(Simditor){
toolbarFloat: false,
defaultImage: null,
upload: {
url: "",
url: "/api/admin/upload_image/",
params: null,
fileKey: "image",
connectionCount: 3,

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('submission', '0005_submission_contest_id'),
]
operations = [
migrations.AddField(
model_name='submission',
name='shared',
field=models.BooleanField(default=False),
),
]

View File

@@ -19,6 +19,8 @@ class Submission(models.Model):
accepted_answer_time = models.IntegerField(blank=True, null=True)
# 这个字段只有在题目是accepted 的时候才会用到,比赛题目的提交可能还会有得分等信息,存储在这里面
accepted_answer_info = models.TextField(blank=True, null=True)
# 是否可以分享
shared = models.BooleanField(default=False)
class Meta:
db_table = "submission"

View File

@@ -22,3 +22,7 @@ class SubmissionSerializer(serializers.ModelSerializer):
return User.objects.get(id=obj.user_id).username
class SubmissionhareSerializer(serializers.Serializer):
submission_id = serializers.CharField(max_length=40)

View File

@@ -5,6 +5,7 @@ from django.core.urlresolvers import reverse
from account.models import User, REGULAR_USER, ADMIN, SUPER_ADMIN
from problem.models import Problem
from contest.models import Contest
from contest.models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PUBLIC_CONTEST
from submission.models import Submission
from rest_framework.test import APITestCase, APIClient
@@ -82,7 +83,8 @@ class SubmissionAPITest(APITestCase):
hint="hint1",
created_by=User.objects.get(username="test2"))
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
contest_type=2, show_rank=True, show_user_submission=True,
contest_type=PASSWORD_PUBLIC_CONTEST, show_rank=True,
show_user_submission=True,
start_time="2015-08-15T10:00:00.000Z",
end_time="2015-08-15T12:00:00.000Z",
password="aacc", created_by=User.objects.get(username="test2"))
@@ -151,7 +153,8 @@ class SubmissionAdminAPITest(APITestCase):
hint="hint1",
created_by=self.user)
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
contest_type=2, show_rank=True, show_user_submission=True,
contest_type=PASSWORD_PUBLIC_CONTEST, show_rank=True,
show_user_submission=True,
start_time="2015-08-15T10:00:00.000Z",
end_time="2015-08-15T12:00:00.000Z",
password="aacc", created_by=self.user)
@@ -190,7 +193,8 @@ class SubmissionPageTest(TestCase):
hint="hint1",
created_by=User.objects.get(username="test1"))
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
contest_type=2, show_rank=True, show_user_submission=True,
contest_type=PASSWORD_PUBLIC_CONTEST, show_rank=True,
show_user_submission=True,
start_time="2015-08-15T10:00:00.000Z",
end_time="2015-08-15T12:00:00.000Z",
password="aacc", created_by=User.objects.get(username="test1"))

View File

@@ -1,5 +1,6 @@
# coding=utf-8
import json
import logging
import redis
from django.shortcuts import render
@@ -12,14 +13,17 @@ from judge.judger_controller.settings import redis_config
from account.decorators import login_required
from account.models import SUPER_ADMIN
from problem.models import Problem
from contest.models import ContestProblem
from utils.shortcuts import serializer_invalid_response, error_response, success_response, error_page, paginate
from .models import Submission
from .serializers import CreateSubmissionSerializer, SubmissionSerializer
from contest.models import ContestProblem, Contest
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
logger = logging.getLogger("app_info")
class SubmissionAPIView(APIView):
@login_required
@@ -44,9 +48,14 @@ class SubmissionAPIView(APIView):
try:
judge.delay(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
except Exception:
except Exception as e:
logger.error(e)
return error_response(u"提交判题任务失败")
# 修改用户解题状态
problems_status = json.loads(request.user.problems_status)
problems_status[str(data["problem_id"])] = 2
request.user.problems_status = json.dumps(problems_status)
request.user.save()
# 增加redis 中判题队列长度的计数器
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
r.incr("judge_queue_length")
@@ -80,24 +89,37 @@ def problem_my_submissions_list_page(request, problem_id):
except Problem.DoesNotExist:
return error_page(request, u"问题不存在")
submissions = Submission.objects.filter(user_id=request.user.id, problem_id=problem.id, contest_id__isnull=True).order_by("-create_time"). \
submissions = Submission.objects.filter(user_id=request.user.id, problem_id=problem.id,
contest_id__isnull=True).order_by("-create_time"). \
values("id", "result", "create_time", "accepted_answer_time", "language")
return render(request, "oj/problem/my_submissions_list.html",
{"submissions": submissions, "problem": problem})
def _get_submission(submission_id, user):
"""
判断用户权限 看能否获取这个提交详情页面
"""
submission = Submission.objects.get(id=submission_id)
# 超级管理员或者提交者自己或者是一个分享的提交
if user.admin_type == SUPER_ADMIN or submission.user_id == user.id or submission.shared:
return submission
if submission.contest_id:
contest = Contest.objects.get(id=submission.contest_id)
# 比赛提交的话,比赛创建者也可见
if contest.created_by == user:
return submission
raise Submission.DoesNotExist
@login_required
def my_submission(request, submission_id):
"""
单个题目的提交详情页
"""
try:
# 超级管理员可以查看所有的提交
if request.user.admin_type != SUPER_ADMIN:
submission = Submission.objects.get(id=submission_id, user_id=request.user.id)
else:
submission = Submission.objects.get(id=submission_id)
submission = _get_submission(submission_id, request.user)
except Submission.DoesNotExist:
return error_page(request, u"提交不存在")
@@ -139,7 +161,7 @@ def my_submission_list_page(request, page=1):
我的所有提交的列表页
"""
submissions = Submission.objects.filter(user_id=request.user.id, contest_id__isnull=True). \
values("id", "result", "create_time", "accepted_answer_time", "language").order_by("-create_time")
values("id", "problem_id", "result", "create_time", "accepted_answer_time", "language").order_by("-create_time")
language = request.GET.get("language", None)
filter = None
if language:
@@ -149,6 +171,16 @@ def my_submission_list_page(request, page=1):
if result:
submissions = submissions.filter(result=int(result))
filter = {"name": "result", "content": result}
# 为 submission 查询题目 因为提交页面经常会有重复的题目,缓存一下查询结果
cache_result = {}
for item in submissions:
problem_id = item["problem_id"]
if problem_id not in cache_result:
problem = Problem.objects.get(id=problem_id)
cache_result[problem_id] = problem.title
item["title"] = cache_result[problem_id]
paginator = Paginator(submissions, 20)
try:
current_page = paginator.page(int(page))
@@ -170,4 +202,20 @@ def my_submission_list_page(request, page=1):
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})
"announcements": announcements, "filter": filter})
class SubmissionShareAPIView(APIView):
def post(self, request):
serializer = SubmissionhareSerializer(data=request.data)
if serializer.is_valid():
submission_id = serializer.data["submission_id"]
try:
submission = _get_submission(submission_id, request.user)
except Submission.DoesNotExist:
return error_response(u"提交不存在")
submission.shared = not submission.shared
submission.save()
return success_response(submission.shared)
else:
return serializer_invalid_response(serializer)

View File

@@ -31,9 +31,9 @@
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="#">主页</a></li>
<li><a href="#about">题目</a></li>
<li><a href="#contact">提交</a></li>
<li class="active"><a href="/" target="_blank">主页</a></li>
<li><a href="#problem">题目</a></li>
<li><a href="#">提交</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
@@ -54,13 +54,11 @@
</nav>
<!-- nav end -->
<!--browser happy begin -->
<!--[if lt IE 9]>
<div class="alert alert-danger text-center" role="alert">
当前网页 <strong>不支持</strong> 你正在使用的浏览器. 为了正常的访问, 请 <a href="http://browsehappy.com/">升级你的浏览器</a>.
</div>
<![endif]-->
<!-- browser happy end -->
<script>
if(navigator.userAgent.indexOf("MSIE") > -1){
location.href = "/static/img/unsupported_browser.html";
}
</script>
<div class="container main">
<div class="row">
@@ -127,9 +125,7 @@
<script src="/static/js/config.js"></script>
<script src="/static/js/require.js"></script>
<script>
require(["bootstrap", "admin"]);
</script>
<script src="/static/js/app/admin/admin.js"></script>
<!-- footer begin -->
<div class="footer">

View File

@@ -7,7 +7,7 @@
<div class="col-md-12">
<div class="form-group">
<input type="text" name="name" class="form-control" ms-duplex="title"
data-error="请填写比赛名称(名称不能超过50个字)" ms-attr-readonly="contestCreated" required>
data-error="请填写比赛名称(名称不能超过50个字)" required>
<div class="help-block with-errors"></div>
</div>
@@ -26,7 +26,6 @@
<div class="form-group">
<input type="text" class="form-control" name="start_time" id="contest_start_time"
ms-duplex="startTime" data-error="请填写比赛开始时间" required>
<div class="help-block with-errors"></div>
</div>
</div>
@@ -35,61 +34,66 @@
<div class="form-group">
<input type="text" class="form-control" name="end_time" id="contest_end_time"
ms-duplex="endTime" data-error="请填写比赛结束时间" required>
<div class="help-block with-errors"></div>
</div>
</div>
<div class="col-md-6">
<label>允许参加的用户</label>
<div class="form-group">
<select class="form-control" ms-duplex="group" ms-change="addGroup" value="-1">
<option value="-1">请选择</option>
<option ms-repeat="groupList" ms-attr-value="$index" ms-visible="!el.chose">{{el.name}}</option>
</select>
<label>可见范围</label>
<div>
<span ms-if="showGlobalViewRadio">
<label>
<small><input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">全局可见
</small>
</label>
</span>
<span>
<label>
<small><input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组内可见
</small>
</label>
</span>
</div>
</div>
</div>
<div class="col-md-6" ms-visible="passwordUsable">
<div class="col-md-6" ms-visible="isGlobal">
<label>密码保护</label>
<div class="form-group">
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="password">
</div>
</div>
<div class="col-md-12">
<div ms-repeat="choseGroupList" class="group-tag" ms-click="removeGroup($index)">{{el.name}}</div>
<div class="form-group col-md-12" ms-visible="!isGlobal">
<!-- radio 的value 没有用 但是没有的话,表单验证会出错-->
<div ms-repeat="allGroups" class="col-md-4">
<input type="checkbox" value="group_id" ms-duplex-checked="el.isSelected">&nbsp;&nbsp;{{ el.name }}
</div>
</div>
<div class="col-md-6">
<div class="col-md-3">
<label>排名方式</label>
</div>
<div class="col-md-3">
<label>结束前是否开放排名</label>
</div>
<div class="col-md-3">
<label>是否公开提交记录</label>
</div>
<div class="col-md-6">
<div class="form-group">
<label><input type="radio" name="mode" ms-duplex-string="mode" value="0">
<small>ACM</small>
</label>
<label><input type="radio" name="mode" ms-duplex-string="mode" value="1">
<small>AC数量</small>
</label>
<label><input type="radio" name="mode" ms-duplex-string="mode" value="2">
<small>分数</small>
</label>
</div>
</div>
<div class="col-md-3">
<label>公开提交记录</label>
<div class="form-group">
<label class="text"><input type="checkbox" ms-duplex-checked="showRank">
<small>放排名</small>
<label class="text"><input type="checkbox" ms-duplex-checked="showSubmission">
<small></small>
</label>
</div>
</div>
<div class="col-md-3">
<label>实时排名</label>
<div class="form-group">
<label class="text"><input type="checkbox" ms-duplex-checked="showSubmission">
<small>允许查看提交记录</small>
<label class="text"><input type="checkbox" ms-duplex-checked="realTimeRank">
<small></small>
</label>
</div>
</div>

View File

@@ -24,7 +24,7 @@
<tr ms-repeat="contestList">
<td>{{ el.id }}</td>
<td>{{ el.title }}</td>
<td ms-text="el.show_rank?'公开':'不公开'"></td>
<td ms-text="el.real_time_rank?'实时':'已封榜'"></td>
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{ el.created_by.username }}</td>
<td ms-text="el.visible?'可见':'不可见'"></td>
@@ -63,7 +63,7 @@
<textarea id="editor" placeholder="这里输入内容" autofocus ms-duplex="editDescription"></textarea>
<div class="help-block with-errors"></div>
<p class="error-info" ms-visible="editDescription==''" >请填写比赛描述</p>
<p class="error-info" ms-visible="editDescription==''">请填写比赛描述</p>
</div>
</div>
<div class="col-md-6">
@@ -87,32 +87,33 @@
</div>
</div>
<div class="col-md-6">
<label>允许参加的用户</label>
<div class="form-group">
<select class="form-control" ms-duplex="group" ms-change="addGroup" value="-1">
<option value="-1">请选择</option>
<option ms-repeat="groupList" ms-attr-value="$index" ms-visible="!el.chose">{{el.name}}</option>
</select>
<label>可见范围</label>
<span ms-if="showGlobalViewRadio">
<label>
<small><input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">全局可见
</small>
</label>
</span>
<span>
<label>
<small><input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组内可见
</small>
</label>
</span>
</div>
</div>
<div class="col-md-6" ms-visible="passwordUsable">
<div class="col-md-6" ms-visible="isGlobal">
<label>密码保护</label>
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="editPassword">
</div>
<div class="col-md-12">
<div ms-repeat="choseGroupList" class="group-tag" ms-click="removeGroup($index)">{{el.name}}</div>
</div>
<div class="col-md-6">
<div class="form-group col-md-12" ms-visible="!isGlobal">
<!-- radio 的value 没有用 但是没有的话,表单验证会出错-->
<div ms-repeat="allGroups" class="col-md-4">
<input type="checkbox" value="group_id" ms-duplex-checked="el.isSelected">&nbsp;&nbsp;{{ el.name }}
</div>
</div>
<div class="col-md-3">
</div>
<div class="col-md-3">
</div>
<div class="col-md-4">
<label>排名方式</label>
<div class="form-group">
@@ -120,34 +121,31 @@
<small>ACM</small>
</label>
<label><input type="radio" name="mode" ms-duplex-string="editMode" value="1">
<small>AC数量</small>
</label>
<label><input type="radio" name="mode" ms-duplex-string="editMode" value="2">
<small>分数</small>
</label>
</div>
</div>
<div class="col-md-2">
<label>是否可见</label><br>
<label><input type="checkbox" ms-duplex-checked="editVisible">
<small> 可见</small>
</label>
</div>
<div class="col-md-3">
<label>结束前是否开放排名</label>
<div class="form-group">
<label class="text"><input type="checkbox" ms-duplex-checked="editShowRank">
<small>开放排名</small>
<label>是否可见</label>
<label><input type="checkbox" ms-duplex-checked="editVisible">
<small> 可见</small>
</label>
</div>
</div>
<div class="col-md-3">
<label>是否公开提交记录</label>
<label>公开提交记录</label>
<div class="form-group">
<label class="text"><input type="checkbox" ms-duplex-checked="editShowSubmission">
<small>允许查看提交记录</small>
<small>公开</small>
</label>
</div>
</div>
<div class="col-md-3">
<label>实时排名</label>
<div class="form-group">
<label class="text"><input type="checkbox" ms-duplex-checked="editRealTimeRank">
<small></small>
</label>
</div>
</div>
@@ -172,7 +170,7 @@
<tr ms-repeat="editProblemList">
<td>{{ el.sort_index }}</td>
<td>{{ el.title }}</td>
<td ms-visible="editMode=='2'">{{ el.score}}</td>
<td ms-visible="editMode=='2'">{{ el.score }}</td>
<td ms-text="el.visible?'可见':'不可见'"></td>
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss") }}</td>
<td>
@@ -180,6 +178,8 @@
ms-click="showProblemEditPage(el)">编辑</a>
<a href="javascript:void(0)" class="btn-sm btn-info"
ms-click="showSubmissionPage(el)">提交</a>
<a href="javascript:void(0)" class="btn-sm btn-info"
ms-click="addToProblemList(el)" ms-visible="admin_type=='2'">添加到前台</a>
</td>
</tr>
</table>

View File

@@ -18,7 +18,7 @@
<div class="col-md-3">
<div class="form-group"><label>时间限制(ms)</label>
<input type="number" name="timeLimit" class="form-control" ms-duplex="timeLimit"
data-error="请输入时间限制(保证是一个1000-5000的合法整数)" required>
data-error="请输入时间限制(保证是一个100-5000的合法整数)" required>
<div class="help-block with-errors"></div>
</div>
</div>
@@ -31,8 +31,12 @@
</div>
<div class="col-md-3">
<div class="form-group"><label>难度</label>
<input type="number" name="difficulty" class="form-control" ms-duplex="difficulty"
data-error="请输入难度(保证是一个合法整数)" required>
<select name="difficulty" class="form-control" ms-duplex="difficulty"
data-error="请选择难度" required>
<option value="1" selected="selected">简单</option>
<option value="2">中等</option>
<option value="3"></option>
</select>
<div class="help-block with-errors"></div>
</div>
</div>

View File

@@ -24,7 +24,7 @@
<div class="col-md-3">
<div class="form-group"><label>时间限制(ms)</label>
<input type="number" name="timeLimit" class="form-control" ms-duplex="timeLimit"
data-error="请输入时间限制(保证是一个1000-5000的合法整数)" required>
data-error="请输入时间限制(保证是一个100-5000的合法整数)" required>
<div class="help-block with-errors"></div>
</div>
</div>
@@ -37,8 +37,12 @@
</div>
<div class="col-md-3">
<div class="form-group"><label>难度</label>
<input type="number" name="difficulty" class="form-control" ms-duplex="difficulty"
data-error="请输入难度(保证是一个合法整数)" required>
<select name="difficulty" class="form-control" ms-duplex="difficulty"
data-error="请选择难度" required>
<option value="1">简单</option>
<option value="2">中等</option>
<option value="3"></option>
</select>
<div class="help-block with-errors"></div>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More