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:
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal 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
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -58,6 +58,9 @@ log/
|
|||||||
static/release/css
|
static/release/css
|
||||||
static/release/js
|
static/release/js
|
||||||
static/release/img
|
static/release/img
|
||||||
|
static/src/upload_image/*
|
||||||
build.txt
|
build.txt
|
||||||
tmp/
|
tmp/
|
||||||
test_case/
|
test_case/
|
||||||
|
release/
|
||||||
|
upload/
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal 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.
|
||||||
33
README.md
33
README.md
@@ -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
|
||||||
19
account/migrations/0002_user_problems_status.py
Normal file
19
account/migrations/0002_user_problems_status.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
account/migrations/0003_auto_20150915_2025.py
Normal file
19
account/migrations/0003_auto_20150915_2025.py
Normal 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'{}'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
account/migrations/0004_remove_user_problems_status.py
Normal file
18
account/migrations/0004_remove_user_problems_status.py
Normal 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',
|
||||||
|
),
|
||||||
|
]
|
||||||
19
account/migrations/0005_user_problems_status.py
Normal file
19
account/migrations/0005_user_problems_status.py
Normal 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'{}'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -30,6 +30,8 @@ class User(AbstractBaseUser):
|
|||||||
create_time = models.DateTimeField(auto_now_add=True)
|
create_time = models.DateTimeField(auto_now_add=True)
|
||||||
# 0代表不是管理员 1是普通管理员 2是超级管理员
|
# 0代表不是管理员 1是普通管理员 2是超级管理员
|
||||||
admin_type = models.IntegerField(default=0)
|
admin_type = models.IntegerField(default=0)
|
||||||
|
# JSON字典用来表示该用户的问题的解决状态 1为ac,2为正在进行
|
||||||
|
problems_status = models.TextField(default="{}")
|
||||||
|
|
||||||
USERNAME_FIELD = 'username'
|
USERNAME_FIELD = 'username'
|
||||||
REQUIRED_FIELDS = []
|
REQUIRED_FIELDS = []
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from .models import User
|
|||||||
class UserLoginSerializer(serializers.Serializer):
|
class UserLoginSerializer(serializers.Serializer):
|
||||||
username = serializers.CharField(max_length=30)
|
username = serializers.CharField(max_length=30)
|
||||||
password = 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):
|
class UsernameCheckSerializer(serializers.Serializer):
|
||||||
@@ -22,11 +23,13 @@ class UserRegisterSerializer(serializers.Serializer):
|
|||||||
real_name = serializers.CharField(max_length=30)
|
real_name = serializers.CharField(max_length=30)
|
||||||
password = serializers.CharField(max_length=30, min_length=6)
|
password = serializers.CharField(max_length=30, min_length=6)
|
||||||
email = serializers.EmailField(max_length=254)
|
email = serializers.EmailField(max_length=254)
|
||||||
|
captcha = serializers.CharField(max_length=4, min_length=4)
|
||||||
|
|
||||||
|
|
||||||
class UserChangePasswordSerializer(serializers.Serializer):
|
class UserChangePasswordSerializer(serializers.Serializer):
|
||||||
old_password = serializers.CharField()
|
old_password = serializers.CharField()
|
||||||
new_password = serializers.CharField(max_length=30, min_length=6)
|
new_password = serializers.CharField(max_length=30, min_length=6)
|
||||||
|
captcha = serializers.CharField(max_length=4, min_length=4)
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ from django.shortcuts import render
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from rest_framework.views import APIView
|
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.shortcuts import serializer_invalid_response, error_response, success_response, paginate
|
||||||
|
from utils.captcha import Captcha
|
||||||
|
|
||||||
from .decorators import login_required
|
from .decorators import login_required
|
||||||
from .models import User
|
from .models import User
|
||||||
from .serializers import (UserLoginSerializer, UsernameCheckSerializer,
|
from .serializers import (UserLoginSerializer, UsernameCheckSerializer,
|
||||||
UserRegisterSerializer, UserChangePasswordSerializer,
|
UserRegisterSerializer, UserChangePasswordSerializer,
|
||||||
EmailCheckSerializer, UserSerializer, EditUserSerializer)
|
EmailCheckSerializer, UserSerializer, EditUserSerializer)
|
||||||
|
|
||||||
|
|
||||||
class UserLoginAPIView(APIView):
|
class UserLoginAPIView(APIView):
|
||||||
@@ -28,6 +29,12 @@ class UserLoginAPIView(APIView):
|
|||||||
user = auth.authenticate(username=data["username"], password=data["password"])
|
user = auth.authenticate(username=data["username"], password=data["password"])
|
||||||
# 用户名或密码错误的话 返回None
|
# 用户名或密码错误的话 返回None
|
||||||
if user:
|
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)
|
auth.login(request, user)
|
||||||
return success_response(u"登录成功")
|
return success_response(u"登录成功")
|
||||||
else:
|
else:
|
||||||
@@ -35,11 +42,24 @@ class UserLoginAPIView(APIView):
|
|||||||
else:
|
else:
|
||||||
return serializer_invalid_response(serializer)
|
return serializer_invalid_response(serializer)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def logout(request):
|
def logout(request):
|
||||||
auth.logout(request)
|
auth.logout(request)
|
||||||
return http.HttpResponseRedirect("/")
|
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):
|
class UserRegisterAPIView(APIView):
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -50,6 +70,9 @@ class UserRegisterAPIView(APIView):
|
|||||||
serializer = UserRegisterSerializer(data=request.data)
|
serializer = UserRegisterSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
|
captcha = Captcha(request)
|
||||||
|
if not captcha.check(data["captcha"]):
|
||||||
|
return error_response(u"验证码错误")
|
||||||
try:
|
try:
|
||||||
User.objects.get(username=data["username"])
|
User.objects.get(username=data["username"])
|
||||||
return error_response(u"用户名已存在")
|
return error_response(u"用户名已存在")
|
||||||
@@ -79,6 +102,9 @@ class UserChangePasswordAPIView(APIView):
|
|||||||
serializer = UserChangePasswordSerializer(data=request.data)
|
serializer = UserChangePasswordSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
|
captcha = Captcha(request)
|
||||||
|
if not captcha.check(data["captcha"]):
|
||||||
|
return error_response(u"验证码错误")
|
||||||
username = request.user.username
|
username = request.user.username
|
||||||
user = auth.authenticate(username=username, password=data["old_password"])
|
user = auth.authenticate(username=username, password=data["old_password"])
|
||||||
if user:
|
if user:
|
||||||
@@ -92,39 +118,35 @@ class UserChangePasswordAPIView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class UsernameCheckAPIView(APIView):
|
class UsernameCheckAPIView(APIView):
|
||||||
def post(self, request):
|
def get(self, request):
|
||||||
"""
|
"""
|
||||||
检测用户名是否存在,存在返回True,不存在返回False
|
检测用户名是否存在,存在返回状态码400,不存在返回200
|
||||||
---
|
---
|
||||||
request_serializer: UsernameCheckSerializer
|
|
||||||
"""
|
"""
|
||||||
serializer = UsernameCheckSerializer(data=request.data)
|
username = request.GET.get("username", None)
|
||||||
if serializer.is_valid():
|
if username:
|
||||||
try:
|
try:
|
||||||
User.objects.get(username=serializer.data["username"])
|
User.objects.get(username=username)
|
||||||
return success_response(True)
|
return Response(status=400)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return success_response(False)
|
return Response(status=200)
|
||||||
else:
|
return Response(status=200)
|
||||||
return serializer_invalid_response(serializer)
|
|
||||||
|
|
||||||
|
|
||||||
class EmailCheckAPIView(APIView):
|
class EmailCheckAPIView(APIView):
|
||||||
def post(self, request):
|
def get(self, request):
|
||||||
"""
|
"""
|
||||||
检测邮箱是否存在,存在返回True,不存在返回False
|
检测邮箱是否存在,存在返回状态码400,不存在返回200
|
||||||
---
|
---
|
||||||
request_serializer: EmailCheckSerializer
|
|
||||||
"""
|
"""
|
||||||
serializer = EmailCheckSerializer(data=request.data)
|
email = request.GET.get("email", None)
|
||||||
if serializer.is_valid():
|
if email:
|
||||||
try:
|
try:
|
||||||
User.objects.get(email=serializer.data["email"])
|
User.objects.get(email=email)
|
||||||
return success_response(True)
|
return Response(status=400)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return success_response(False)
|
return Response(status=200)
|
||||||
else:
|
return Response(status=200)
|
||||||
return serializer_invalid_response(serializer)
|
|
||||||
|
|
||||||
|
|
||||||
class UserAdminAPIView(APIView):
|
class UserAdminAPIView(APIView):
|
||||||
@@ -189,3 +211,19 @@ class UserInfoAPIView(APIView):
|
|||||||
response_serializer: UserSerializer
|
response_serializer: UserSerializer
|
||||||
"""
|
"""
|
||||||
return success_response(UserSerializer(request.user).data)
|
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})
|
||||||
|
|||||||
@@ -16,8 +16,21 @@ def announcement_page(request, announcement_id):
|
|||||||
try:
|
try:
|
||||||
announcement = Announcement.objects.get(id=announcement_id, visible=True)
|
announcement = Announcement.objects.get(id=announcement_id, visible=True)
|
||||||
except Announcement.DoesNotExist:
|
except Announcement.DoesNotExist:
|
||||||
return error_page(request, u"模板不存在")
|
return error_page(request, u"公告不存在")
|
||||||
return render(request, "oj/announcement/announcement.html", {"announcement": announcement})
|
# 公开的公告
|
||||||
|
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):
|
class AnnouncementAdminAPIView(APIView):
|
||||||
|
|||||||
19
contest/migrations/0007_contestsubmission_ac_time.py
Normal file
19
contest/migrations/0007_contestsubmission_ac_time.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
contest/migrations/0008_auto_20150912_1912.py
Normal file
19
contest/migrations/0008_auto_20150912_1912.py
Normal 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',
|
||||||
|
),
|
||||||
|
]
|
||||||
19
contest/migrations/0009_contestsubmission_first_achieved.py
Normal file
19
contest/migrations/0009_contestsubmission_first_achieved.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -6,20 +6,25 @@ from account.models import User
|
|||||||
from problem.models import AbstractProblem
|
from problem.models import AbstractProblem
|
||||||
from group.models import Group
|
from group.models import Group
|
||||||
|
|
||||||
|
GROUP_CONTEST = 0
|
||||||
|
PUBLIC_CONTEST = 1
|
||||||
|
PASSWORD_PROTECTED_CONTEST = 2
|
||||||
|
|
||||||
|
|
||||||
class Contest(models.Model):
|
class Contest(models.Model):
|
||||||
title = models.CharField(max_length=40, unique=True)
|
title = models.CharField(max_length=40, unique=True)
|
||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
# 比赛模式:0 即为是acm模式,1 即为是按照总的 ac 题目数量排名模式
|
# 比赛模式:0 即为是acm模式,1 即为是按照总的 ac 题目数量排名模式
|
||||||
mode = models.IntegerField()
|
mode = models.IntegerField()
|
||||||
# 是否显示排名结果
|
# 是否显示实时排名结果
|
||||||
show_rank = models.BooleanField()
|
real_time_rank = models.BooleanField()
|
||||||
# 是否显示别人的提交记录
|
# 是否显示别人的提交记录
|
||||||
show_user_submission = models.BooleanField()
|
show_user_submission = models.BooleanField()
|
||||||
# 只能超级管理员创建公开赛,管理员只能创建小组内部的比赛
|
# 只能超级管理员创建公开赛,管理员只能创建小组内部的比赛
|
||||||
# 如果这一项不为空,即为有密码的公开赛,没有密码的可以为小组赛或者是公开赛(此时用比赛的类型来表示)
|
# 如果这一项不为空,即为有密码的公开赛,没有密码的可以为小组赛或者是公开赛(此时用比赛的类型来表示)
|
||||||
password = models.CharField(max_length=30, blank=True, null=True)
|
password = models.CharField(max_length=30, blank=True, null=True)
|
||||||
# 比赛的类型: 0 即为是小组赛,1 即为是无密码的公开赛,2 即为是有密码的公开赛
|
# 比赛的类型: 0 即为是小组赛(GROUP_CONTEST),1 即为是无密码的公开赛(PUBLIC_CONTEST),
|
||||||
|
# 2 即为是有密码的公开赛(PASSWORD_PUBLIC_CONTEST)
|
||||||
contest_type = models.IntegerField()
|
contest_type = models.IntegerField()
|
||||||
# 开始时间
|
# 开始时间
|
||||||
start_time = models.DateTimeField()
|
start_time = models.DateTimeField()
|
||||||
@@ -84,8 +89,12 @@ class ContestSubmission(models.Model):
|
|||||||
total_submission_number = models.IntegerField(default=1)
|
total_submission_number = models.IntegerField(default=1)
|
||||||
# 这道题是 AC 还是没过
|
# 这道题是 AC 还是没过
|
||||||
ac = models.BooleanField()
|
ac = models.BooleanField()
|
||||||
|
# ac 用时以秒计
|
||||||
|
ac_time = models.IntegerField(default=0)
|
||||||
# 总的时间,用于acm 类型的,也需要保存罚时
|
# 总的时间,用于acm 类型的,也需要保存罚时
|
||||||
total_time = models.IntegerField(default=0)
|
total_time = models.IntegerField(default=0)
|
||||||
|
# 第一个解出此题目
|
||||||
|
first_achieved = models.BooleanField(default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "contest_submission"
|
db_table = "contest_submission"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class CreateContestSerializer(serializers.Serializer):
|
|||||||
description = serializers.CharField(max_length=5000)
|
description = serializers.CharField(max_length=5000)
|
||||||
mode = serializers.IntegerField()
|
mode = serializers.IntegerField()
|
||||||
contest_type = serializers.IntegerField()
|
contest_type = serializers.IntegerField()
|
||||||
show_rank = serializers.BooleanField()
|
real_time_rank = serializers.BooleanField()
|
||||||
show_user_submission = serializers.BooleanField()
|
show_user_submission = serializers.BooleanField()
|
||||||
password = serializers.CharField(max_length=30, required=False, default=None)
|
password = serializers.CharField(max_length=30, required=False, default=None)
|
||||||
start_time = serializers.DateTimeField()
|
start_time = serializers.DateTimeField()
|
||||||
@@ -47,7 +47,7 @@ class EditContestSerializer(serializers.Serializer):
|
|||||||
description = serializers.CharField(max_length=10000)
|
description = serializers.CharField(max_length=10000)
|
||||||
mode = serializers.IntegerField()
|
mode = serializers.IntegerField()
|
||||||
contest_type = serializers.IntegerField()
|
contest_type = serializers.IntegerField()
|
||||||
show_rank = serializers.BooleanField()
|
real_time_rank = serializers.BooleanField()
|
||||||
show_user_submission = serializers.BooleanField()
|
show_user_submission = serializers.BooleanField()
|
||||||
password = serializers.CharField(max_length=30, required=False, default=None)
|
password = serializers.CharField(max_length=30, required=False, default=None)
|
||||||
start_time = serializers.DateTimeField()
|
start_time = serializers.DateTimeField()
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
from django.http import HttpResponse
|
|
||||||
|
|
||||||
from rest_framework.test import APITestCase, APIClient
|
from rest_framework.test import APITestCase, APIClient
|
||||||
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from group.models import Group
|
from group.models import Group
|
||||||
from contest.models import Contest, ContestProblem
|
from contest.models import Contest, ContestProblem
|
||||||
from .models import ContestSubmission
|
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 account.models import REGULAR_USER, ADMIN, SUPER_ADMIN
|
||||||
from decorators import check_user_contest_permission
|
|
||||||
|
|
||||||
|
|
||||||
class ContestAdminAPITest(APITestCase):
|
class ContestAdminAPITest(APITestCase):
|
||||||
@@ -32,15 +30,17 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
join_group_setting=0, visible=True,
|
join_group_setting=0, visible=True,
|
||||||
admin=user2)
|
admin=user2)
|
||||||
self.group2 = Group.objects.create(name="group2", description="des0",
|
self.group2 = Group.objects.create(name="group2", description="des0",
|
||||||
join_group_setting=0, visible=True,
|
join_group_setting=0, visible=True,
|
||||||
admin=user1)
|
admin=user1)
|
||||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
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",
|
start_time="2015-08-15T10:00:00.000Z",
|
||||||
end_time="2015-08-15T12:00:00.000Z",
|
end_time="2015-08-15T12:00:00.000Z",
|
||||||
password="aacc", created_by=User.objects.get(username="test1"))
|
password="aacc", created_by=User.objects.get(username="test1"))
|
||||||
self.group_contest = Contest.objects.create(title="titley", description="descriptiony", mode=1,
|
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",
|
start_time="2015-08-15T10:00:00.000Z",
|
||||||
end_time="2015-08-15T12:00:00.000Z",
|
end_time="2015-08-15T12:00:00.000Z",
|
||||||
password="aacc", created_by=User.objects.get(username="test1"))
|
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):
|
def test_global_contest_does_not_has_privileges(self):
|
||||||
self.client.login(username="test2", password="testbb")
|
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",
|
"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}
|
"end_time": "2015-08-15T12:00:00.000Z", "password": "aabb", "visible": True}
|
||||||
response = self.client.post(self.url, data=data)
|
response = self.client.post(self.url, data=data)
|
||||||
@@ -62,7 +62,7 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
|
|
||||||
def test_global_contest_password_exists(self):
|
def test_global_contest_password_exists(self):
|
||||||
self.client.login(username="test1", password="testaa")
|
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",
|
"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}
|
"end_time": "2015-08-15T12:00:00.000Z", "visible": True}
|
||||||
response = self.client.post(self.url, data=data)
|
response = self.client.post(self.url, data=data)
|
||||||
@@ -70,7 +70,7 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
|
|
||||||
def test_group_contest_group_at_least_one(self):
|
def test_group_contest_group_at_least_one(self):
|
||||||
self.client.login(username="test1", password="testaa")
|
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",
|
"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}
|
"end_time": "2015-08-15T12:00:00.000Z", "visible": True}
|
||||||
response = self.client.post(self.url, data=data)
|
response = self.client.post(self.url, data=data)
|
||||||
@@ -78,7 +78,7 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
|
|
||||||
def test_global_contest_successfully(self):
|
def test_global_contest_successfully(self):
|
||||||
self.client.login(username="test1", password="testaa")
|
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",
|
"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}
|
"end_time": "2015-08-15T12:00:00.000Z", "password": "aabb", "visible": True}
|
||||||
response = self.client.post(self.url, data=data)
|
response = self.client.post(self.url, data=data)
|
||||||
@@ -86,7 +86,7 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
|
|
||||||
def test_group_contest_super_admin_successfully(self):
|
def test_group_contest_super_admin_successfully(self):
|
||||||
self.client.login(username="test1", password="testaa")
|
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",
|
"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}
|
"end_time": "2015-08-15T12:00:00.000Z", "groups": [self.group.id], "visible": True}
|
||||||
response = self.client.post(self.url, data=data)
|
response = self.client.post(self.url, data=data)
|
||||||
@@ -94,7 +94,7 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
|
|
||||||
def test_group_contest_admin_successfully(self):
|
def test_group_contest_admin_successfully(self):
|
||||||
self.client.login(username="test2", password="testbb")
|
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",
|
"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}
|
"end_time": "2015-08-15T12:00:00.000Z", "groups": [self.group.id], "visible": True}
|
||||||
response = self.client.post(self.url, data=data)
|
response = self.client.post(self.url, data=data)
|
||||||
@@ -102,7 +102,7 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
|
|
||||||
def test_time_error(self):
|
def test_time_error(self):
|
||||||
self.client.login(username="test1", password="testaa")
|
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",
|
"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}
|
"end_time": "2015-08-15T10:00:00.000Z", "password": "aabb", "visible": True}
|
||||||
response = self.client.post(self.url, data=data)
|
response = self.client.post(self.url, data=data)
|
||||||
@@ -110,7 +110,7 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
|
|
||||||
def test_contest_has_exists(self):
|
def test_contest_has_exists(self):
|
||||||
self.client.login(username="test1", password="testaa")
|
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",
|
"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}
|
"end_time": "2015-08-15T12:00:00.000Z", "password": "aabb", "visible": True}
|
||||||
response = self.client.post(self.url, data=data)
|
response = self.client.post(self.url, data=data)
|
||||||
@@ -126,7 +126,7 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
def test_contest_does_not_exist(self):
|
def test_contest_does_not_exist(self):
|
||||||
self.client.login(username="test1", password="testaa")
|
self.client.login(username="test1", password="testaa")
|
||||||
data = {"id": self.global_contest.id + 10, "title": "title2", "description": "description2", "mode": 1,
|
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",
|
"start_time": "2015-08-15T10:00:00.000Z", "end_time": "2015-08-15T12:00:00.000Z", "password": "aabb",
|
||||||
"visible": True}
|
"visible": True}
|
||||||
response = self.client.put(self.url, data=data)
|
response = self.client.put(self.url, data=data)
|
||||||
@@ -135,7 +135,7 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
def test_edit_global_contest_successfully(self):
|
def test_edit_global_contest_successfully(self):
|
||||||
self.client.login(username="test1", password="testaa")
|
self.client.login(username="test1", password="testaa")
|
||||||
data = {"id": self.global_contest.id, "title": "titlez", "description": "descriptionz", "mode": 1,
|
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",
|
"start_time": "2015-08-15T10:00:00.000Z", "end_time": "2015-08-15T13:00:00.000Z", "password": "aabb",
|
||||||
"visible": True}
|
"visible": True}
|
||||||
response = self.client.put(self.url, data=data)
|
response = self.client.put(self.url, data=data)
|
||||||
@@ -146,7 +146,7 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
def test_edit_group_contest_successfully(self):
|
def test_edit_group_contest_successfully(self):
|
||||||
self.client.login(username="test1", password="testaa")
|
self.client.login(username="test1", password="testaa")
|
||||||
data = {"id": self.group_contest.id, "title": "titleyyy", "description": "descriptionyyyy", "mode": 1,
|
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",
|
"start_time": "2015-08-15T10:00:00.000Z", "end_time": "2015-08-15T13:00:00.000Z",
|
||||||
"groups": [self.group.id], "visible": False}
|
"groups": [self.group.id], "visible": False}
|
||||||
response = self.client.put(self.url, data=data)
|
response = self.client.put(self.url, data=data)
|
||||||
@@ -158,7 +158,7 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
def test_edit_group_contest_unsuccessfully(self):
|
def test_edit_group_contest_unsuccessfully(self):
|
||||||
self.client.login(username="test2", password="testbb")
|
self.client.login(username="test2", password="testbb")
|
||||||
data = {"id": self.group_contest.id, "title": "titleyyy", "description": "descriptionyyyy", "mode": 1,
|
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",
|
"start_time": "2015-08-15T10:00:00.000Z", "end_time": "2015-08-15T13:00:00.000Z",
|
||||||
"groups": [self.group.id], "visible": False}
|
"groups": [self.group.id], "visible": False}
|
||||||
response = self.client.put(self.url, data=data)
|
response = self.client.put(self.url, data=data)
|
||||||
@@ -167,7 +167,7 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
def test_edit_group_at_least_one(self):
|
def test_edit_group_at_least_one(self):
|
||||||
self.client.login(username="test1", password="testaa")
|
self.client.login(username="test1", password="testaa")
|
||||||
data = {"id": self.group_contest.id, "title": "titleyyy", "description": "descriptionyyyy", "mode": 1,
|
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}
|
"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)
|
response = self.client.put(self.url, data=data)
|
||||||
self.assertEqual(response.data, {"code": 1, "data": u"请至少选择一个小组"})
|
self.assertEqual(response.data, {"code": 1, "data": u"请至少选择一个小组"})
|
||||||
@@ -175,7 +175,7 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
def test_edit_contest_has_exists(self):
|
def test_edit_contest_has_exists(self):
|
||||||
self.client.login(username="test1", password="testaa")
|
self.client.login(username="test1", password="testaa")
|
||||||
data = {"id": self.global_contest.id, "title": "titley", "description": "descriptiony", "mode": 1,
|
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",
|
"start_time": "2015-08-15T10:00:00.000Z", "end_time": "2015-08-15T12:00:00.000Z", "password": "aabb",
|
||||||
"visible": True}
|
"visible": True}
|
||||||
response = self.client.put(self.url, data=data)
|
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):
|
def test_edit_global_contest_does_not_has_privileges(self):
|
||||||
self.client.login(username="test2", password="testbb")
|
self.client.login(username="test2", password="testbb")
|
||||||
data = {"id": self.global_contest.id, "title": "titlexxxxxxxxx", "description": "descriptionxxxxxx", "mode": 1,
|
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",
|
"start_time": "2015-08-15T10:00:00.000Z", "end_time": "2015-08-15T12:00:00.000Z", "password": "aabb",
|
||||||
"visible": True}
|
"visible": True}
|
||||||
response = self.client.put(self.url, data=data)
|
response = self.client.put(self.url, data=data)
|
||||||
@@ -193,8 +193,7 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
def test_edit_global_contest_password_exists(self):
|
def test_edit_global_contest_password_exists(self):
|
||||||
self.client.login(username="test1", password="testaa")
|
self.client.login(username="test1", password="testaa")
|
||||||
data = {"id": self.global_contest.id, "title": "title0", "description": "description0", "mode": 1,
|
data = {"id": self.global_contest.id, "title": "title0", "description": "description0", "mode": 1,
|
||||||
"contest_type": 2,
|
"contest_type": PASSWORD_PROTECTED_CONTEST, "show_rank": True, "show_user_submission": True, "start_time": "2015-08-15T10:00:00.000Z",
|
||||||
"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}
|
"end_time": "2015-08-15T12:00:00.000Z", "visible": True}
|
||||||
response = self.client.put(self.url, data=data)
|
response = self.client.put(self.url, data=data)
|
||||||
self.assertEqual(response.data, {"code": 1, "data": u"此比赛为有密码的公开赛,密码不可为空"})
|
self.assertEqual(response.data, {"code": 1, "data": u"此比赛为有密码的公开赛,密码不可为空"})
|
||||||
@@ -202,7 +201,7 @@ class ContestAdminAPITest(APITestCase):
|
|||||||
def test_edit_time_error(self):
|
def test_edit_time_error(self):
|
||||||
self.client.login(username="test1", password="testaa")
|
self.client.login(username="test1", password="testaa")
|
||||||
data = {"id": self.global_contest.id, "title": "titleaaaa", "description": "descriptionaaaaa", "mode": 1,
|
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",
|
"start_time": "2015-08-15T12:00:00.000Z", "end_time": "2015-08-15T10:00:00.000Z", "password": "aabb",
|
||||||
"visible": True}
|
"visible": True}
|
||||||
response = self.client.put(self.url, data=data)
|
response = self.client.put(self.url, data=data)
|
||||||
@@ -245,7 +244,8 @@ class ContestProblemAdminAPItEST(APITestCase):
|
|||||||
self.user3.save()
|
self.user3.save()
|
||||||
self.client.login(username="test1", password="testaa")
|
self.client.login(username="test1", password="testaa")
|
||||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
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",
|
start_time="2015-08-15T10:00:00.000Z",
|
||||||
end_time="2015-08-15T12:00:00.000Z",
|
end_time="2015-08-15T12:00:00.000Z",
|
||||||
password="aacc", created_by=User.objects.get(username="test1"))
|
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):
|
def test_query_contest_problem_exists_by_contest_id(self):
|
||||||
self.client.login(username="test3", password="testaa")
|
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(response.data["code"], 0)
|
||||||
self.assertEqual(len(response.data["data"]), 0)
|
self.assertEqual(len(response.data["data"]), 0)
|
||||||
|
|
||||||
@@ -414,7 +414,8 @@ class ContestPasswordVerifyAPITest(APITestCase):
|
|||||||
self.user2.save()
|
self.user2.save()
|
||||||
self.client.login(username="test1", password="testaa")
|
self.client.login(username="test1", password="testaa")
|
||||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
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",
|
start_time="2015-08-15T10:00:00.000Z",
|
||||||
end_time="2015-08-15T12:00:00.000Z",
|
end_time="2015-08-15T12:00:00.000Z",
|
||||||
password="aacc", created_by=User.objects.get(username="test1"))
|
password="aacc", created_by=User.objects.get(username="test1"))
|
||||||
@@ -453,7 +454,8 @@ class ContestPageTest(TestCase):
|
|||||||
self.user1.save()
|
self.user1.save()
|
||||||
self.client.login(username="test1", password="testaa")
|
self.client.login(username="test1", password="testaa")
|
||||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
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",
|
start_time="2015-08-15T10:00:00.000Z",
|
||||||
end_time="2015-08-15T12:00:00.000Z",
|
end_time="2015-08-15T12:00:00.000Z",
|
||||||
password="aacc", created_by=User.objects.get(username="test1"))
|
password="aacc", created_by=User.objects.get(username="test1"))
|
||||||
@@ -476,7 +478,8 @@ class ContestProblemPageTest(TestCase):
|
|||||||
self.user1.save()
|
self.user1.save()
|
||||||
self.client.login(username="test1", password="testaa")
|
self.client.login(username="test1", password="testaa")
|
||||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
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",
|
start_time="2015-08-15T10:00:00.000Z",
|
||||||
end_time="2015-08-15T12:00:00.000Z",
|
end_time="2015-08-15T12:00:00.000Z",
|
||||||
password="aacc", created_by=User.objects.get(username="test1"))
|
password="aacc", created_by=User.objects.get(username="test1"))
|
||||||
@@ -519,7 +522,8 @@ class ContestProblemListPageTest(TestCase):
|
|||||||
self.user1.save()
|
self.user1.save()
|
||||||
self.client.login(username="test1", password="testaa")
|
self.client.login(username="test1", password="testaa")
|
||||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
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",
|
start_time="2015-08-15T10:00:00.000Z",
|
||||||
end_time="2015-08-15T12:00:00.000Z",
|
end_time="2015-08-15T12:00:00.000Z",
|
||||||
password="aacc", created_by=User.objects.get(username="test1"))
|
password="aacc", created_by=User.objects.get(username="test1"))
|
||||||
@@ -555,7 +559,8 @@ class ContestListPageTest(TestCase):
|
|||||||
self.url = reverse('contest_list_page')
|
self.url = reverse('contest_list_page')
|
||||||
self.client.login(username="test1", password="testaa")
|
self.client.login(username="test1", password="testaa")
|
||||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
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",
|
start_time="2015-08-15T10:00:00.000Z",
|
||||||
end_time="2015-08-15T12:00:00.000Z",
|
end_time="2015-08-15T12:00:00.000Z",
|
||||||
password="aacc", created_by=User.objects.get(username="test1"))
|
password="aacc", created_by=User.objects.get(username="test1"))
|
||||||
|
|||||||
164
contest/views.py
164
contest/views.py
@@ -1,28 +1,30 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
import json
|
import json
|
||||||
import datetime
|
import datetime
|
||||||
from functools import wraps
|
|
||||||
from django.utils.timezone import now
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
from django.utils import dateparse
|
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 django.core.paginator import Paginator
|
||||||
from rest_framework.views import APIView
|
from django.utils.timezone import now
|
||||||
from utils.shortcuts import (serializer_invalid_response, error_response,
|
|
||||||
success_response, paginate, rand_str, error_page)
|
|
||||||
|
|
||||||
from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN, 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 account.decorators import login_required
|
||||||
from group.models import Group
|
from group.models import Group
|
||||||
from announcement.models import Announcement
|
|
||||||
|
|
||||||
from .models import Contest, ContestProblem, ContestSubmission
|
from .models import Contest, ContestProblem, ContestSubmission
|
||||||
|
from .models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST
|
||||||
from .decorators import check_user_contest_permission
|
from .decorators import check_user_contest_permission
|
||||||
from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer,
|
from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer,
|
||||||
CreateContestProblemSerializer, ContestProblemSerializer,
|
CreateContestProblemSerializer, ContestProblemSerializer,
|
||||||
EditContestProblemSerializer, ContestPasswordVerifySerializer,
|
ContestPasswordVerifySerializer,
|
||||||
EditContestProblemSerializer)
|
EditContestProblemSerializer)
|
||||||
|
from oj.settings import REDIS_CACHE
|
||||||
|
import redis
|
||||||
|
|
||||||
|
|
||||||
class ContestAdminAPIView(APIView):
|
class ContestAdminAPIView(APIView):
|
||||||
@@ -37,17 +39,18 @@ class ContestAdminAPIView(APIView):
|
|||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
groups = []
|
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:
|
if request.user.admin_type != SUPER_ADMIN:
|
||||||
return error_response(u"只有超级管理员才可创建公开赛")
|
return error_response(u"只有超级管理员才可创建公开赛")
|
||||||
if data["contest_type"] == 2:
|
if data["contest_type"] == PASSWORD_PROTECTED_CONTEST:
|
||||||
if not data["password"]:
|
if not data["password"]:
|
||||||
return error_response(u"此比赛为有密码的公开赛,密码不可为空")
|
return error_response(u"此比赛为有密码的公开赛,密码不可为空")
|
||||||
|
|
||||||
# 没有密码的公开赛 没有密码的小组赛
|
# 没有密码的公开赛 没有密码的小组赛
|
||||||
elif data["contest_type"] == 0:
|
elif data["contest_type"] == GROUP_CONTEST:
|
||||||
if request.user.admin_type == SUPER_ADMIN:
|
if request.user.admin_type == SUPER_ADMIN:
|
||||||
groups = Group.objects.filter(id__in=data["groups"])
|
groups = Group.objects.filter(id__in=data["groups"])
|
||||||
else:
|
else:
|
||||||
@@ -59,7 +62,7 @@ class ContestAdminAPIView(APIView):
|
|||||||
try:
|
try:
|
||||||
contest = Contest.objects.create(title=data["title"], description=data["description"],
|
contest = Contest.objects.create(title=data["title"], description=data["description"],
|
||||||
mode=data["mode"], contest_type=data["contest_type"],
|
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"],
|
show_user_submission=data["show_user_submission"],
|
||||||
start_time=dateparse.parse_datetime(data["start_time"]),
|
start_time=dateparse.parse_datetime(data["start_time"]),
|
||||||
end_time=dateparse.parse_datetime(data["end_time"]),
|
end_time=dateparse.parse_datetime(data["end_time"]),
|
||||||
@@ -92,13 +95,13 @@ class ContestAdminAPIView(APIView):
|
|||||||
return error_response(u"该比赛名称已经存在")
|
return error_response(u"该比赛名称已经存在")
|
||||||
except Contest.DoesNotExist:
|
except Contest.DoesNotExist:
|
||||||
pass
|
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:
|
if request.user.admin_type != SUPER_ADMIN:
|
||||||
return error_response(u"只有超级管理员才可创建公开赛")
|
return error_response(u"只有超级管理员才可创建公开赛")
|
||||||
if data["contest_type"] == 2:
|
if data["contest_type"] == PASSWORD_PROTECTED_CONTEST:
|
||||||
if not data["password"]:
|
if not data["password"]:
|
||||||
return error_response(u"此比赛为有密码的公开赛,密码不可为空")
|
return error_response(u"此比赛为有密码的公开赛,密码不可为空")
|
||||||
elif data["contest_type"] == 0:
|
elif data["contest_type"] == GROUP_CONTEST:
|
||||||
if request.user.admin_type == SUPER_ADMIN:
|
if request.user.admin_type == SUPER_ADMIN:
|
||||||
groups = Group.objects.filter(id__in=data["groups"])
|
groups = Group.objects.filter(id__in=data["groups"])
|
||||||
else:
|
else:
|
||||||
@@ -113,7 +116,7 @@ class ContestAdminAPIView(APIView):
|
|||||||
contest.description = data["description"]
|
contest.description = data["description"]
|
||||||
contest.mode = data["mode"]
|
contest.mode = data["mode"]
|
||||||
contest.contest_type = data["contest_type"]
|
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.show_user_submission = data["show_user_submission"]
|
||||||
contest.start_time = dateparse.parse_datetime(data["start_time"])
|
contest.start_time = dateparse.parse_datetime(data["start_time"])
|
||||||
contest.end_time = dateparse.parse_datetime(data["end_time"])
|
contest.end_time = dateparse.parse_datetime(data["end_time"])
|
||||||
@@ -256,7 +259,7 @@ class ContestPasswordVerifyAPIView(APIView):
|
|||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
data = request.data
|
data = request.data
|
||||||
try:
|
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:
|
except Contest.DoesNotExist:
|
||||||
return error_response(u"比赛不存在")
|
return error_response(u"比赛不存在")
|
||||||
|
|
||||||
@@ -317,13 +320,23 @@ def contest_problems_list_page(request, contest_id):
|
|||||||
比赛所有题目的列表页
|
比赛所有题目的列表页
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
contest_problems = ContestProblem.objects.filter(contest=Contest.objects.get(id=contest_id)).order_by("sort_index")
|
contest = Contest.objects.get(id=contest_id)
|
||||||
except ContestProblem.DoesNotExist:
|
except Contest.DoesNotExist:
|
||||||
return error_page(request, u"比赛题目不存在")
|
return error_page(request, u"比赛不存在")
|
||||||
# 右侧的公告列表
|
contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index")
|
||||||
announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
|
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,
|
return render(request, "oj/contest/contest_problems_list.html", {"contest_problems": contest_problems,
|
||||||
"announcements": announcements,
|
|
||||||
"contest": {"id": contest_id}})
|
"contest": {"id": contest_id}})
|
||||||
|
|
||||||
|
|
||||||
@@ -341,10 +354,9 @@ def contest_list_page(request, page=1):
|
|||||||
|
|
||||||
# 筛选我能参加的比赛
|
# 筛选我能参加的比赛
|
||||||
join = request.GET.get("join", None)
|
join = request.GET.get("join", None)
|
||||||
if join:
|
if request.user.is_authenticated and join:
|
||||||
contests = contests.filter(Q(contest_type__in=[1, 2]) | Q(groups__in=request.user.group_set.all())).\
|
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())
|
filter(end_time__gt=datetime.datetime.now(), start_time__lt=datetime.datetime.now())
|
||||||
|
|
||||||
paginator = Paginator(contests, 20)
|
paginator = Paginator(contests, 20)
|
||||||
try:
|
try:
|
||||||
current_page = paginator.page(int(page))
|
current_page = paginator.page(int(page))
|
||||||
@@ -363,15 +375,10 @@ def contest_list_page(request, page=1):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 右侧的公告列表
|
|
||||||
announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
|
|
||||||
|
|
||||||
return render(request, "oj/contest/contest_list.html",
|
return render(request, "oj/contest/contest_list.html",
|
||||||
{"contests": current_page, "page": int(page),
|
{"contests": current_page, "page": int(page),
|
||||||
"previous_page": previous_page, "next_page": next_page,
|
"previous_page": previous_page, "next_page": next_page,
|
||||||
"keyword": keyword, "announcements": announcements,
|
"keyword": keyword, "join": join})
|
||||||
"join": join})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _cmp(x, y):
|
def _cmp(x, y):
|
||||||
@@ -386,26 +393,85 @@ def _cmp(x, y):
|
|||||||
return -1
|
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
|
@check_user_contest_permission
|
||||||
def contest_rank_page(request, contest_id):
|
def contest_rank_page(request, contest_id):
|
||||||
contest = Contest.objects.get(id=contest_id)
|
contest = Contest.objects.get(id=contest_id)
|
||||||
contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index")
|
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"))
|
r = redis.Redis(host=REDIS_CACHE["host"], port=REDIS_CACHE["port"], db=REDIS_CACHE["db"])
|
||||||
for i in range(0, len(result)):
|
if contest.real_time_rank:
|
||||||
# 这个人所有的提交
|
# 更新rank
|
||||||
submissions = ContestSubmission.objects.filter(user_id=result[i]["user_id"], contest_id=contest_id)
|
result = ContestSubmission.objects.filter(contest=contest).values("user_id"). \
|
||||||
result[i]["submissions"] = {}
|
annotate(total_submit=Sum("total_submission_number"))
|
||||||
for item in submissions:
|
for i in range(0, len(result)):
|
||||||
result[i]["submissions"][item.problem_id] = item
|
# 这个人所有的提交
|
||||||
result[i]["total_ac"] = submissions.filter(ac=True).count()
|
submissions = ContestSubmission.objects.filter(user_id=result[i]["user_id"], contest_id=contest_id)
|
||||||
result[i]["user"] = User.objects.get(id=result[i]["user_id"])
|
result[i]["submissions"] = {}
|
||||||
result[i]["total_time"] = submissions.filter(ac=True).aggregate(total_time=Sum("total_time"))["total_time"]
|
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",
|
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))
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
from django.core.urlresolvers import reverse
|
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 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):
|
class ContestSubmissionAPITest(APITestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -20,7 +22,8 @@ class ContestSubmissionAPITest(APITestCase):
|
|||||||
self.user2.set_password("testbb")
|
self.user2.set_password("testbb")
|
||||||
self.user2.save()
|
self.user2.save()
|
||||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
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",
|
start_time="2015-08-15T10:00:00.000Z",
|
||||||
end_time="2015-08-30T12:00:00.000Z",
|
end_time="2015-08-30T12:00:00.000Z",
|
||||||
created_by=User.objects.get(username="test1"))
|
created_by=User.objects.get(username="test1"))
|
||||||
@@ -70,7 +73,8 @@ class ContestProblemMySubmissionListTest(TestCase):
|
|||||||
self.user2.set_password("testbb")
|
self.user2.set_password("testbb")
|
||||||
self.user2.save()
|
self.user2.save()
|
||||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
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",
|
start_time="2015-08-15T10:00:00.000Z",
|
||||||
end_time="2015-08-30T12:00:00.000Z",
|
end_time="2015-08-30T12:00:00.000Z",
|
||||||
created_by=User.objects.get(username="test1"))
|
created_by=User.objects.get(username="test1"))
|
||||||
@@ -104,7 +108,8 @@ class SubmissionAPITest(APITestCase):
|
|||||||
self.userS.set_password("testbb")
|
self.userS.set_password("testbb")
|
||||||
self.userS.save()
|
self.userS.save()
|
||||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
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",
|
start_time="2015-08-15T10:00:00.000Z",
|
||||||
end_time="2015-08-15T12:00:00.000Z",
|
end_time="2015-08-15T12:00:00.000Z",
|
||||||
password="aacc", created_by=self.userS
|
password="aacc", created_by=self.userS
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
import json
|
import json
|
||||||
|
from datetime import datetime
|
||||||
import redis
|
import redis
|
||||||
|
import pytz
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
|
from django.utils import timezone
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from judge.judger_controller.tasks import judge
|
from judge.judger_controller.tasks import judge
|
||||||
from judge.judger_controller.settings import redis_config
|
from judge.judger_controller.settings import redis_config
|
||||||
|
|
||||||
from account.decorators import login_required
|
from account.decorators import login_required
|
||||||
from account.models import SUPER_ADMIN
|
from account.models import SUPER_ADMIN
|
||||||
|
|
||||||
from contest.decorators import check_user_contest_permission
|
from contest.decorators import check_user_contest_permission
|
||||||
|
|
||||||
from problem.models import Problem
|
from problem.models import Problem
|
||||||
from contest.models import Contest, ContestProblem
|
from contest.models import Contest, ContestProblem
|
||||||
|
|
||||||
from utils.shortcuts import serializer_invalid_response, error_response, success_response, error_page, paginate
|
from utils.shortcuts import serializer_invalid_response, error_response, success_response, error_page, paginate
|
||||||
|
|
||||||
from submission.models import Submission
|
from submission.models import Submission
|
||||||
from .serializers import CreateContestSubmissionSerializer
|
from .serializers import CreateContestSubmissionSerializer
|
||||||
from submission.serializers import SubmissionSerializer
|
from submission.serializers import SubmissionSerializer
|
||||||
@@ -43,20 +43,16 @@ class ContestSubmissionAPIView(APIView):
|
|||||||
problem.save()
|
problem.save()
|
||||||
except ContestProblem.DoesNotExist:
|
except ContestProblem.DoesNotExist:
|
||||||
return error_response(u"题目不存在")
|
return error_response(u"题目不存在")
|
||||||
|
|
||||||
submission = Submission.objects.create(user_id=request.user.id, language=int(data["language"]),
|
submission = Submission.objects.create(user_id=request.user.id, language=int(data["language"]),
|
||||||
contest_id=contest.id, code=data["code"], problem_id=problem.id)
|
contest_id=contest.id, code=data["code"], problem_id=problem.id)
|
||||||
try:
|
try:
|
||||||
judge.delay(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
judge.delay(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
return error_response(u"提交判题任务失败")
|
return error_response(u"提交判题任务失败")
|
||||||
|
|
||||||
# 增加redis 中判题队列长度的计数器
|
# 增加redis 中判题队列长度的计数器
|
||||||
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
|
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
|
||||||
r.incr("judge_queue_length")
|
r.incr("judge_queue_length")
|
||||||
|
|
||||||
return success_response({"submission_id": submission.id})
|
return success_response({"submission_id": submission.id})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return serializer_invalid_response(serializer)
|
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})
|
{"submissions": submissions, "problem": contest_problem})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@check_user_contest_permission
|
||||||
def contest_problem_submissions_list_page(request, contest_id, page=1):
|
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)
|
contest = Contest.objects.get(id=contest_id)
|
||||||
except Contest.DoesNotExist:
|
except Contest.DoesNotExist:
|
||||||
return error_page(request, u"比赛不存在")
|
return error_page(request, u"比赛不存在")
|
||||||
# 以下是本场比赛中所有的提交
|
|
||||||
submissions = Submission.objects.filter(contest_id=contest_id). \
|
submissions = Submission.objects.filter(contest_id=contest_id).\
|
||||||
values("id", "result", "create_time", "accepted_answer_time", "language", "user_id").order_by("-create_time")
|
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)
|
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:
|
try:
|
||||||
current_page = paginator.page(int(page))
|
current_page = paginator.page(int(page))
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -108,10 +130,18 @@ def contest_problem_submissions_list_page(request, contest_id, page=1):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
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",
|
return render(request, "oj/contest/submissions_list.html",
|
||||||
{"submissions": current_page, "page": int(page),
|
{"submissions": current_page, "page": int(page),
|
||||||
"previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20,
|
"previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20,
|
||||||
"contest": contest})
|
"contest": contest, "filter": filter})
|
||||||
|
|
||||||
|
|
||||||
class ContestSubmissionAdminAPIView(APIView):
|
class ContestSubmissionAdminAPIView(APIView):
|
||||||
@@ -144,6 +174,4 @@ class ContestSubmissionAdminAPIView(APIView):
|
|||||||
return error_response(u"参数错误!")
|
return error_response(u"参数错误!")
|
||||||
if problem_id:
|
if problem_id:
|
||||||
submissions = submissions.filter(problem_id=problem_id)
|
submissions = submissions.filter(problem_id=problem_id)
|
||||||
|
|
||||||
return paginate(request, submissions, SubmissionSerializer)
|
return paginate(request, submissions, SubmissionSerializer)
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ services:
|
|||||||
- mysql
|
- mysql
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- oj_env="daocloud"
|
- oj_env="local"
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
- mkdir LOG
|
- mkdir log
|
||||||
- python manage.py test
|
- python manage.py test
|
||||||
@@ -3,6 +3,8 @@ MAINTAINER virusdefender<qduliyang@outlook.com>
|
|||||||
RUN mkdir /var/install/
|
RUN mkdir /var/install/
|
||||||
WORKDIR /var/install/
|
WORKDIR /var/install/
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
RUN rm /etc/apt/sources.list
|
||||||
|
COPY sources.list /etc/apt/
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get -y install software-properties-common python-software-properties
|
RUN apt-get -y install software-properties-common python-software-properties
|
||||||
RUN add-apt-repository -y ppa:webupd8team/java
|
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 cd lrun && make install
|
||||||
RUN mkdir -p /var/judger/run/ && mkdir /var/judger/test_case/ && mkdir /var/judger/code/
|
RUN mkdir -p /var/judger/run/ && mkdir /var/judger/test_case/ && mkdir /var/judger/code/
|
||||||
RUN chmod -R 777 /var/judger/run/
|
RUN chmod -R 777 /var/judger/run/
|
||||||
|
COPY policy /var/judger/run/
|
||||||
WORKDIR /var/judger/code/
|
WORKDIR /var/judger/code/
|
||||||
3
dockerfiles/judger/policy
Normal file
3
dockerfiles/judger/policy
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
grant {
|
||||||
|
permission java.io.FilePermission "/tmp", "read";
|
||||||
|
};
|
||||||
10
dockerfiles/judger/sources.list
Normal file
10
dockerfiles/judger/sources.list
Normal 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
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
FROM python:2.7
|
FROM python:2.7
|
||||||
ENV PYTHONBUFFERED 1
|
ENV PYTHONBUFFERED 1
|
||||||
RUN mkdir -p /code/log /code/test_case
|
RUN mkdir -p /code/log /code/test_case /code/upload
|
||||||
WORKDIR /code
|
WORKDIR /code
|
||||||
ADD requirements.txt /code/
|
ADD requirements.txt /code/
|
||||||
RUN pip install -r requirements.txt
|
RUN pip install -r requirements.txt
|
||||||
EXPOSE 8010
|
EXPOSE 8010
|
||||||
|
CMD supervisord
|
||||||
16
dockerfiles/oj_web_server/gunicorn.conf
Normal file
16
dockerfiles/oj_web_server/gunicorn.conf
Normal 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
|
||||||
16
dockerfiles/oj_web_server/mq.conf
Normal file
16
dockerfiles/oj_web_server/mq.conf
Normal 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
|
||||||
@@ -8,4 +8,5 @@ celery
|
|||||||
gunicorn
|
gunicorn
|
||||||
coverage
|
coverage
|
||||||
django-extensions
|
django-extensions
|
||||||
supervisor
|
supervisor
|
||||||
|
pillow
|
||||||
26
dockerfiles/oj_web_server/supervisord.conf
Normal file
26
dockerfiles/oj_web_server/supervisord.conf
Normal 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
|
||||||
@@ -254,9 +254,6 @@ class JoinGroupRequestAdminAPIView(APIView, GroupAPIViewBase):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def group_list_page(request, page=1):
|
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)
|
groups = Group.objects.filter(visible=True, join_group_setting__lte=2)
|
||||||
# 搜索的情况
|
# 搜索的情况
|
||||||
keyword = request.GET.get("keyword", None)
|
keyword = request.GET.get("keyword", None)
|
||||||
@@ -282,10 +279,10 @@ def group_list_page(request, page=1):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
return render(request, "oj/group/group_list.html", {
|
return render(request, "oj/group/group_list.html", {
|
||||||
"groups": groups, "announcements": announcements,
|
"groups": groups,
|
||||||
"contests": current_page, "page": int(page),
|
"contests": current_page, "page": int(page),
|
||||||
"previous_page": previous_page, "next_page": next_page,
|
"previous_page": previous_page, "next_page": next_page,
|
||||||
"keyword": keyword, "announcements": announcements,
|
"keyword": keyword
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
||||||
@@ -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")
|
|
||||||
@@ -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
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
import os
|
||||||
import json
|
import json
|
||||||
import commands
|
import commands
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -7,9 +8,9 @@ from multiprocessing import Pool
|
|||||||
from settings import max_running_number, lrun_gid, lrun_uid, judger_workspace
|
from settings import max_running_number, lrun_gid, lrun_uid, judger_workspace
|
||||||
from language import languages
|
from language import languages
|
||||||
from result import result
|
from result import result
|
||||||
from compiler import compile_
|
from judge_exceptions import JudgeClientError
|
||||||
from judge_exceptions import JudgeClientError, CompileError
|
|
||||||
from utils import parse_lrun_output
|
from utils import parse_lrun_output
|
||||||
|
from logger import logger
|
||||||
|
|
||||||
|
|
||||||
# 下面这个函数作为代理访问实例变量,否则Python2会报错,是Python2的已知问题
|
# 下面这个函数作为代理访问实例变量,否则Python2会报错,是Python2的已知问题
|
||||||
@@ -61,6 +62,8 @@ class JudgeClient(object):
|
|||||||
" --max-real-time " + str(self._max_real_time / 1000.0 * 2) + \
|
" --max-real-time " + str(self._max_real_time / 1000.0 * 2) + \
|
||||||
" --max-memory " + str(self._max_memory * 1000 * 1000) + \
|
" --max-memory " + str(self._max_memory * 1000 * 1000) + \
|
||||||
" --network false" + \
|
" --network false" + \
|
||||||
|
" --syscalls '" + self._language["syscalls"] + "'" + \
|
||||||
|
" --max-nprocess 20" + \
|
||||||
" --uid " + str(lrun_uid) + \
|
" --uid " + str(lrun_uid) + \
|
||||||
" --gid " + str(lrun_gid)
|
" --gid " + str(lrun_gid)
|
||||||
|
|
||||||
@@ -82,6 +85,8 @@ class JudgeClient(object):
|
|||||||
# 倒序找到MEMORY的位置
|
# 倒序找到MEMORY的位置
|
||||||
output_start = output.rfind("MEMORY")
|
output_start = output.rfind("MEMORY")
|
||||||
if output_start == -1:
|
if output_start == -1:
|
||||||
|
logger.error("Lrun result parse error")
|
||||||
|
logger.error(output)
|
||||||
raise JudgeClientError("Lrun result parse error")
|
raise JudgeClientError("Lrun result parse error")
|
||||||
# 如果不是0,说明lrun输出前面有输出,也就是程序的stderr有内容
|
# 如果不是0,说明lrun输出前面有输出,也就是程序的stderr有内容
|
||||||
if output_start != 0:
|
if output_start != 0:
|
||||||
@@ -92,26 +97,35 @@ class JudgeClient(object):
|
|||||||
return error, parse_lrun_output(output)
|
return error, parse_lrun_output(output)
|
||||||
|
|
||||||
def _compare_output(self, test_case_id):
|
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"
|
output_path = judger_workspace + str(test_case_id) + ".out"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
f = open(output_path, "rb")
|
f = open(output_path, "rb")
|
||||||
except IOError:
|
except IOError:
|
||||||
# 文件不存在等引发的异常 返回结果错误
|
# 文件不存在等引发的异常 返回结果错误
|
||||||
return False
|
return "", False
|
||||||
|
|
||||||
# 计算输出文件的md5 和之前测试用例文件的md5进行比较
|
if "striped_output_md5" not in test_case_config:
|
||||||
md5 = hashlib.md5()
|
# 计算输出文件的md5 和之前测试用例文件的md5进行比较
|
||||||
while True:
|
# 兼容之前没有striped_output_md5的测试用例
|
||||||
data = f.read(2 ** 8)
|
# 现在比较的是完整的文件
|
||||||
if not data:
|
md5 = hashlib.md5()
|
||||||
break
|
while True:
|
||||||
md5.update(data)
|
data = f.read(2 ** 8)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
md5.update(data)
|
||||||
|
output_md5 = md5.hexdigest()
|
||||||
|
|
||||||
# 对比文件是否一致
|
return output_md5, output_md5 == test_case_config["output_md5"]
|
||||||
# todo 去除最后的空行
|
else:
|
||||||
return md5.hexdigest() == test_case_md5
|
# 这时候需要去除用户输出最后的空格和换行 再去比较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):
|
def _judge_one(self, test_case_id):
|
||||||
# 运行lrun程序 接收返回值
|
# 运行lrun程序 接收返回值
|
||||||
@@ -123,26 +137,30 @@ class JudgeClient(object):
|
|||||||
|
|
||||||
run_result["test_case_id"] = test_case_id
|
run_result["test_case_id"] = test_case_id
|
||||||
|
|
||||||
# 如果返回值非0 或者信号量不是0 或者程序的stderr有输出 代表非正常结束
|
# 代表内存或者时间超过限制了 程序被终止掉 要在runtime error 之前判断
|
||||||
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 run_result["exceed"]:
|
if run_result["exceed"]:
|
||||||
if run_result["exceed"] == "memory":
|
if run_result["exceed"] == "memory":
|
||||||
run_result["result"] = result["memory_limit_exceeded"]
|
run_result["result"] = result["memory_limit_exceeded"]
|
||||||
elif run_result["exceed"] in ["cpu_time", "real_time"]:
|
elif run_result["exceed"] in ["cpu_time", "real_time"]:
|
||||||
run_result["result"] = result["time_limit_exceeded"]
|
run_result["result"] = result["time_limit_exceeded"]
|
||||||
else:
|
else:
|
||||||
|
logger.error("Error exceeded type: " + run_result["exceed"])
|
||||||
|
logger.error(output)
|
||||||
raise JudgeClientError("Error exceeded type: " + run_result["exceed"])
|
raise JudgeClientError("Error exceeded type: " + run_result["exceed"])
|
||||||
return run_result
|
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"]
|
run_result["result"] = result["accepted"]
|
||||||
else:
|
else:
|
||||||
run_result["result"] = result["wrong_answer"]
|
run_result["result"] = result["wrong_answer"]
|
||||||
|
run_result["output_md5"] = output_md5
|
||||||
|
|
||||||
return run_result
|
return run_result
|
||||||
|
|
||||||
@@ -160,8 +178,8 @@ class JudgeClient(object):
|
|||||||
try:
|
try:
|
||||||
results.append(item.get())
|
results.append(item.get())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# todo logging
|
logger.error("system error")
|
||||||
print e
|
logger.error(e)
|
||||||
results.append({"result": result["system_error"]})
|
results.append({"result": result["system_error"]})
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import commands
|
|||||||
from settings import lrun_uid, lrun_gid
|
from settings import lrun_uid, lrun_gid
|
||||||
from judge_exceptions import CompileError, JudgeClientError
|
from judge_exceptions import CompileError, JudgeClientError
|
||||||
from utils import parse_lrun_output
|
from utils import parse_lrun_output
|
||||||
|
from logger import logger
|
||||||
|
|
||||||
|
|
||||||
def compile_(language_item, src_path, exe_path):
|
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")
|
output_start = output.rfind("MEMORY")
|
||||||
|
|
||||||
if output_start == -1:
|
if output_start == -1:
|
||||||
|
logger.error("Compiler error")
|
||||||
|
logger.error(output)
|
||||||
raise JudgeClientError("Error running compiler in lrun")
|
raise JudgeClientError("Error running compiler in lrun")
|
||||||
|
|
||||||
# 返回值不为0 或者 stderr中lrun的输出之前有东西
|
# 返回值不为 0 或者 stderr 中 lrun 的输出之前有 erro r字符串
|
||||||
if status or output_start:
|
# 判断 error 字符串的原因是链接的时候可能会有一些不推荐使用的函数的的警告,
|
||||||
|
# 但是 -w 参数并不能关闭链接时的警告
|
||||||
|
if status or "error" in output[0:output_start]:
|
||||||
raise CompileError(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"]:
|
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")
|
raise CompileError("Compile error")
|
||||||
return exe_path
|
return exe_path
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ languages = {
|
|||||||
"name": "c",
|
"name": "c",
|
||||||
"src_name": "main.c",
|
"src_name": "main.c",
|
||||||
"code": 1,
|
"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",
|
"compile_command": "gcc -DONLINE_JUDGE -O2 -w -std=c99 {src_path} -lm -o {exe_path}main",
|
||||||
"execute_command": "{exe_path}main"
|
"execute_command": "{exe_path}main"
|
||||||
},
|
},
|
||||||
@@ -13,6 +14,7 @@ languages = {
|
|||||||
"name": "cpp",
|
"name": "cpp",
|
||||||
"src_name": "main.cpp",
|
"src_name": "main.cpp",
|
||||||
"code": 2,
|
"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",
|
"compile_command": "g++ -DONLINE_JUDGE -O2 -w -std=c++11 {src_path} -lm -o {exe_path}main",
|
||||||
"execute_command": "{exe_path}main"
|
"execute_command": "{exe_path}main"
|
||||||
},
|
},
|
||||||
@@ -20,8 +22,9 @@ languages = {
|
|||||||
"name": "java",
|
"name": "java",
|
||||||
"src_name": "Main.java",
|
"src_name": "Main.java",
|
||||||
"code": 3,
|
"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}",
|
"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
8
judge/judger/logger.py
Normal 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
|
||||||
@@ -7,9 +7,8 @@ from client import JudgeClient
|
|||||||
from language import languages
|
from language import languages
|
||||||
from compiler import compile_
|
from compiler import compile_
|
||||||
from result import result
|
from result import result
|
||||||
from settings import judger_workspace
|
from settings import judger_workspace, submission_db
|
||||||
|
from logger import logger
|
||||||
from settings import submission_db
|
|
||||||
|
|
||||||
|
|
||||||
# 简单的解析命令行参数
|
# 简单的解析命令行参数
|
||||||
@@ -60,7 +59,6 @@ except Exception as e:
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
print "Compile successfully"
|
|
||||||
# 运行
|
# 运行
|
||||||
try:
|
try:
|
||||||
client = JudgeClient(language_code=language_code,
|
client = JudgeClient(language_code=language_code,
|
||||||
@@ -80,16 +78,13 @@ try:
|
|||||||
judge_result["accepted_answer_time"] = l[-1]["cpu_time"]
|
judge_result["accepted_answer_time"] = l[-1]["cpu_time"]
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print e
|
logger.error(e)
|
||||||
conn = db_conn()
|
conn = db_conn()
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute("update submission set result=%s, info=%s where id=%s", (result["system_error"], str(e), submission_id))
|
cur.execute("update submission set result=%s, info=%s where id=%s", (result["system_error"], str(e), submission_id))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
print "Run successfully"
|
|
||||||
print judge_result
|
|
||||||
|
|
||||||
conn = db_conn()
|
conn = db_conn()
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute("update submission set result=%s, info=%s, accepted_answer_time=%s where id=%s",
|
cur.execute("update submission set result=%s, info=%s, accepted_answer_time=%s where id=%s",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
import os
|
||||||
# 单个判题端最多同时运行的程序个数,因为判题端会同时运行多组测试数据,比如一共有5组测试数据
|
# 单个判题端最多同时运行的程序个数,因为判题端会同时运行多组测试数据,比如一共有5组测试数据
|
||||||
# 如果MAX_RUNNING_NUMBER大于等于5,那么这5组数据就会同时进行评测,然后返回结果。
|
# 如果MAX_RUNNING_NUMBER大于等于5,那么这5组数据就会同时进行评测,然后返回结果。
|
||||||
# 如果MAX_RUNNING_NUMBER小于5,为3,那么就会同时运行前三组测试数据,然后再运行后两组数据
|
# 如果MAX_RUNNING_NUMBER小于5,为3,那么就会同时运行前三组测试数据,然后再运行后两组数据
|
||||||
@@ -14,12 +15,10 @@ lrun_gid = 1002
|
|||||||
# judger工作目录
|
# judger工作目录
|
||||||
judger_workspace = "/var/judger/"
|
judger_workspace = "/var/judger/"
|
||||||
|
|
||||||
|
|
||||||
# 这个是在docker 中访问数据库 ip 不一定和web服务器还有celery的一样
|
|
||||||
submission_db = {
|
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,
|
"port": 3306,
|
||||||
"db": "oj_submission",
|
"db": "oj_submission",
|
||||||
"user": "root",
|
"user": "root",
|
||||||
"password": "mypwd"
|
"password": os.environ.get("MYSQL_ENV_MYSQL_ROOT_PASSWORD", "root")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
注意:
|
||||||
|
此文件包含 celery 的部分配置,但是 celery 并不是运行在docker 中的,所以本配置文件中的 redis和 MySQL 的地址就应该是
|
||||||
|
运行 redis 和 MySQL 的 docker 容器的地址了。怎么获取这个地址见帮助文档。测试用例的路径和源代码路径同理。
|
||||||
|
"""
|
||||||
|
import os
|
||||||
# 这个redis 是 celery 使用的,包括存储队列信息还有部分统计信息
|
# 这个redis 是 celery 使用的,包括存储队列信息还有部分统计信息
|
||||||
redis_config = {
|
redis_config = {
|
||||||
"host": "127.0.0.1",
|
"host": os.environ.get("REDIS_PORT_6379_TCP_ADDR"),
|
||||||
"port": 6379,
|
"port": 6379,
|
||||||
"db": 0
|
"db": 0
|
||||||
}
|
}
|
||||||
@@ -9,8 +15,7 @@ redis_config = {
|
|||||||
|
|
||||||
# 判题的 docker 容器的配置参数
|
# 判题的 docker 容器的配置参数
|
||||||
docker_config = {
|
docker_config = {
|
||||||
|
"image_name": "judger",
|
||||||
"image_name": " 819d3da18dc1",
|
|
||||||
"docker_path": "docker",
|
"docker_path": "docker",
|
||||||
"shell": True
|
"shell": True
|
||||||
}
|
}
|
||||||
@@ -19,12 +24,14 @@ docker_config = {
|
|||||||
# 测试用例的路径,是主机上的实际路径
|
# 测试用例的路径,是主机上的实际路径
|
||||||
test_case_dir = "/root/test_case/"
|
test_case_dir = "/root/test_case/"
|
||||||
# 源代码路径,也就是 manage.py 所在的实际路径
|
# 源代码路径,也就是 manage.py 所在的实际路径
|
||||||
source_code_dir = "/var/mnt/source/OnlineJudge/"
|
source_code_dir = "/root/qduoj/"
|
||||||
|
# 日志文件夹路径
|
||||||
|
log_dir = "/root/log/"
|
||||||
|
|
||||||
|
|
||||||
# 存储提交信息的数据库,是 celery 使用的,与 oj.settings/local_settings 等区分,那是 web 服务器访问的地址
|
# 存储提交信息的数据库,是 celery 使用的,与 oj.settings/local_settings 等区分,那是 web 服务器访问的地址
|
||||||
submission_db = {
|
submission_db = {
|
||||||
"host": "127.0.0.1",
|
"host": os.environ.get("submission_db_host"),
|
||||||
"port": 3306,
|
"port": 3306,
|
||||||
"db": "oj_submission",
|
"db": "oj_submission",
|
||||||
"user": "root",
|
"user": "root",
|
||||||
|
|||||||
@@ -5,32 +5,34 @@ import MySQLdb
|
|||||||
import subprocess
|
import subprocess
|
||||||
from ..judger.result import result
|
from ..judger.result import result
|
||||||
from ..judger_controller.celery import app
|
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
|
@app.task
|
||||||
def judge(submission_id, time_limit, memory_limit, test_case_id):
|
def judge(submission_id, time_limit, memory_limit, test_case_id):
|
||||||
try:
|
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/test_case/ " \
|
||||||
"-v %s:/var/judger/code/ " \
|
"-v %s:/var/judger/code/ " \
|
||||||
|
"-v %s:/var/judger/code/log/ " \
|
||||||
"%s " \
|
"%s " \
|
||||||
"python judge/judger/run.py " \
|
"python judge/judger/run.py " \
|
||||||
"--solution_id %s --time_limit %s --memory_limit %s --test_case_id %s" % \
|
"--solution_id %s --time_limit %s --memory_limit %s --test_case_id %s" % \
|
||||||
(docker_config["docker_path"],
|
(docker_config["docker_path"],
|
||||||
test_case_dir,
|
test_case_dir,
|
||||||
source_code_dir,
|
source_code_dir,
|
||||||
|
log_dir,
|
||||||
docker_config["image_name"],
|
docker_config["image_name"],
|
||||||
submission_id, str(time_limit), str(memory_limit), test_case_id)
|
submission_id, str(time_limit), str(memory_limit), test_case_id)
|
||||||
subprocess.call(command, shell=docker_config["shell"])
|
subprocess.call(command, shell=docker_config["shell"])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print e
|
|
||||||
conn = MySQLdb.connect(db=submission_db["db"],
|
conn = MySQLdb.connect(db=submission_db["db"],
|
||||||
user=submission_db["user"],
|
user=submission_db["user"],
|
||||||
passwd=submission_db["password"],
|
passwd=submission_db["password"],
|
||||||
host=submission_db["host"],
|
host=submission_db["host"],
|
||||||
port=submission_db["port"],
|
port=submission_db["port"],
|
||||||
character="utf8")
|
charset="utf8")
|
||||||
|
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute("update submission set result=%s, info=%s where id=%s",
|
cur.execute("update submission set result=%s, info=%s where id=%s",
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
# include <stdio.h>
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
int a, b;
|
|
||||||
scanf("%d %d", &a, &b);
|
|
||||||
printf("%d", a + b);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -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
114
mq/scripts/mq.py
Normal 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()
|
||||||
@@ -3,10 +3,6 @@ import os
|
|||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
# 下面是需要自己修改的
|
|
||||||
LOG_PATH = "log/"
|
|
||||||
|
|
||||||
# 注意这是web 服务器访问的地址,判题端访问的地址不一定一样,因为可能不在一台机器上
|
# 注意这是web 服务器访问的地址,判题端访问的地址不一定一样,因为可能不在一台机器上
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
@@ -17,17 +13,27 @@ DATABASES = {
|
|||||||
'submission': {
|
'submission': {
|
||||||
'NAME': 'oj_submission',
|
'NAME': 'oj_submission',
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
'HOST': "121.42.32.129",
|
'CONN_MAX_AGE': 0.1,
|
||||||
|
'HOST': "127.0.0.1",
|
||||||
'PORT': 3306,
|
'PORT': 3306,
|
||||||
'USER': 'root',
|
'USER': 'root',
|
||||||
'PASSWORD': 'mypwd',
|
'PASSWORD': 'root',
|
||||||
'CONN_MAX_AGE': 0.1,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
REDIS_CACHE = {
|
||||||
|
"host": "121.42.32.129",
|
||||||
|
"port": 6379,
|
||||||
|
"db": 1
|
||||||
|
}
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
# 同理 这是 web 服务器的上传路径
|
|
||||||
TEST_CASE_DIR = os.path.join(BASE_DIR, 'test_case/')
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
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/')]
|
||||||
@@ -3,35 +3,41 @@ import os
|
|||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
# 下面是需要自己修改的
|
|
||||||
LOG_PATH = "/var/log/oj/"
|
|
||||||
|
|
||||||
# 注意这是web 服务器访问的地址,判题端访问的地址不一定一样,因为可能不在一台机器上
|
# 注意这是web 服务器访问的地址,判题端访问的地址不一定一样,因为可能不在一台机器上
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
'NAME': "oj",
|
'NAME': "oj",
|
||||||
'CONN_MAX_AGE': 0.1,
|
'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,
|
'PORT': 3306,
|
||||||
'USER': 'root',
|
'USER': 'root',
|
||||||
'PASSWORD': 'mypwd'
|
'PASSWORD': os.environ.get("MYSQL_ENV_MYSQL_ROOT_PASSWORD", "root")
|
||||||
},
|
},
|
||||||
'submission': {
|
'submission': {
|
||||||
'NAME': 'oj_submission',
|
'NAME': 'oj_submission',
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
'CONN_MAX_AGE': 0.1,
|
'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,
|
'PORT': 3306,
|
||||||
'USER': 'root',
|
'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 服务器的上传路径
|
DEBUG = False
|
||||||
TEST_CASE_DIR = '/root/test_case/'
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['*']
|
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/')]
|
||||||
|
|||||||
@@ -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'
|
SECRET_KEY = 'hzfp^8mbgapc&x%$#xv)0=t8s7_ilingw(q3!@h&2fty6v6fxz'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
@@ -76,7 +74,7 @@ ROOT_URLCONF = 'oj.urls'
|
|||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [os.path.join(BASE_DIR, 'template/src')],
|
'DIRS': TEMPLATE_DIRS,
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
@@ -91,7 +89,6 @@ TEMPLATES = [
|
|||||||
|
|
||||||
WSGI_APPLICATION = 'oj.wsgi.application'
|
WSGI_APPLICATION = 'oj.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
||||||
|
|
||||||
@@ -111,10 +108,11 @@ USE_TZ = True
|
|||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
STATICFILES_DIRS = (os.path.join(BASE_DIR, "static/src/"),)
|
|
||||||
|
|
||||||
AUTH_USER_MODEL = 'account.User'
|
AUTH_USER_MODEL = 'account.User'
|
||||||
|
|
||||||
|
LOG_PATH = "log/"
|
||||||
|
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
'disable_existing_loggers': True,
|
'disable_existing_loggers': True,
|
||||||
@@ -166,4 +164,8 @@ REST_FRAMEWORK = {
|
|||||||
'TEST_REQUEST_DEFAULT_FORMAT': 'json'
|
'TEST_REQUEST_DEFAULT_FORMAT': 'json'
|
||||||
}
|
}
|
||||||
|
|
||||||
DATABASE_ROUTERS = ['oj.db_router.DBRouter']
|
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/')
|
||||||
24
oj/urls.py
24
oj/urls.py
@@ -4,11 +4,12 @@ from django.views.generic import TemplateView
|
|||||||
|
|
||||||
from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView,
|
from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView,
|
||||||
UserChangePasswordAPIView, EmailCheckAPIView,
|
UserChangePasswordAPIView, EmailCheckAPIView,
|
||||||
UserAdminAPIView, UserInfoAPIView)
|
UserAdminAPIView, UserInfoAPIView, AccountSecurityAPIView)
|
||||||
|
|
||||||
from announcement.views import AnnouncementAdminAPIView
|
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,
|
from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView,
|
||||||
JoinGroupAPIView, JoinGroupRequestAdminAPIView)
|
JoinGroupAPIView, JoinGroupRequestAdminAPIView)
|
||||||
@@ -16,16 +17,16 @@ from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView,
|
|||||||
from admin.views import AdminTemplateView
|
from admin.views import AdminTemplateView
|
||||||
|
|
||||||
from problem.views import TestCaseUploadAPIView, ProblemTagAdminAPIView, ProblemAdminAPIView
|
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 contest_submission.views import ContestSubmissionAPIView, ContestSubmissionAdminAPIView
|
||||||
from monitor.views import QueueLengthMonitorAPIView
|
from monitor.views import QueueLengthMonitorAPIView
|
||||||
|
from utils.views import SimditorImageUploadAPIView
|
||||||
|
|
||||||
from contest_submission.views import contest_problem_my_submissions_list_page
|
from contest_submission.views import contest_problem_my_submissions_list_page
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^install/$', "install.views.install"),
|
url("^$", "account.views.index_page", name="index_page"),
|
||||||
url("^$", TemplateView.as_view(template_name="oj/index.html"), name="index_page"),
|
|
||||||
url(r'^docs/', include('rest_framework_swagger.urls')),
|
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/$', 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"),
|
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/submission/$', SubmissionAPIView.as_view(), name="submission_api"),
|
||||||
url(r'^api/group_join/$', JoinGroupAPIView.as_view(), name="group_join_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/announcement/$', AnnouncementAdminAPIView.as_view(), name="announcement_admin_api"),
|
||||||
url(r'^api/admin/contest/$', ContestAdminAPIView.as_view(), name="contest_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"),
|
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'^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+)/$', "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/(?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"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class CreateProblemSerializer(serializers.Serializer):
|
|||||||
difficulty = serializers.IntegerField()
|
difficulty = serializers.IntegerField()
|
||||||
tags = serializers.ListField(child=serializers.CharField(max_length=10))
|
tags = serializers.ListField(child=serializers.CharField(max_length=10))
|
||||||
hint = serializers.CharField(max_length=3000, allow_blank=True)
|
hint = serializers.CharField(max_length=3000, allow_blank=True)
|
||||||
|
visible = visible = serializers.BooleanField()
|
||||||
|
|
||||||
|
|
||||||
class ProblemTagSerializer(serializers.ModelSerializer):
|
class ProblemTagSerializer(serializers.ModelSerializer):
|
||||||
|
|||||||
@@ -13,12 +13,16 @@ from rest_framework.views import APIView
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
from announcement.models import Announcement
|
from announcement.models import Announcement
|
||||||
from utils.shortcuts import (serializer_invalid_response, error_response,
|
from utils.shortcuts import (serializer_invalid_response, error_response,
|
||||||
success_response, paginate, rand_str, error_page)
|
success_response, paginate, rand_str, error_page)
|
||||||
from .serizalizers import (CreateProblemSerializer, EditProblemSerializer, ProblemSerializer,
|
from .serizalizers import (CreateProblemSerializer, EditProblemSerializer, ProblemSerializer,
|
||||||
ProblemTagSerializer, CreateProblemTagSerializer)
|
ProblemTagSerializer, CreateProblemTagSerializer)
|
||||||
from .models import Problem, ProblemTag
|
from .models import Problem, ProblemTag
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger("app_info")
|
||||||
|
|
||||||
|
|
||||||
def problem_page(request, problem_id):
|
def problem_page(request, problem_id):
|
||||||
@@ -56,7 +60,8 @@ class ProblemAdminAPIView(APIView):
|
|||||||
memory_limit=data["memory_limit"],
|
memory_limit=data["memory_limit"],
|
||||||
difficulty=data["difficulty"],
|
difficulty=data["difficulty"],
|
||||||
created_by=request.user,
|
created_by=request.user,
|
||||||
hint=data["hint"])
|
hint=data["hint"],
|
||||||
|
visible=data["visible"])
|
||||||
for tag in data["tags"]:
|
for tag in data["tags"]:
|
||||||
try:
|
try:
|
||||||
tag = ProblemTag.objects.get(name=tag)
|
tag = ProblemTag.objects.get(name=tag)
|
||||||
@@ -147,9 +152,13 @@ class TestCaseUploadAPIView(APIView):
|
|||||||
f = request.FILES["file"]
|
f = request.FILES["file"]
|
||||||
|
|
||||||
tmp_zip = "/tmp/" + rand_str() + ".zip"
|
tmp_zip = "/tmp/" + rand_str() + ".zip"
|
||||||
with open(tmp_zip, "wb") as test_case_zip:
|
try:
|
||||||
for chunk in f:
|
with open(tmp_zip, "wb") as test_case_zip:
|
||||||
test_case_zip.write(chunk)
|
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')
|
test_case_file = zipfile.ZipFile(tmp_zip, 'r')
|
||||||
name_list = test_case_file.namelist()
|
name_list = test_case_file.namelist()
|
||||||
@@ -198,16 +207,24 @@ class TestCaseUploadAPIView(APIView):
|
|||||||
# 计算输出文件的md5
|
# 计算输出文件的md5
|
||||||
for i in range(len(l) / 2):
|
for i in range(len(l) / 2):
|
||||||
md5 = hashlib.md5()
|
md5 = hashlib.md5()
|
||||||
|
striped_md5 = hashlib.md5()
|
||||||
f = open(test_case_dir + str(i + 1) + ".out", "r")
|
f = open(test_case_dir + str(i + 1) + ".out", "r")
|
||||||
|
# 完整文件的md5
|
||||||
while True:
|
while True:
|
||||||
data = f.read(2 ** 8)
|
data = f.read(2 ** 8)
|
||||||
if not data:
|
if not data:
|
||||||
break
|
break
|
||||||
md5.update(data)
|
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",
|
file_info["test_cases"][str(i + 1)] = {"input_name": str(i + 1) + ".in",
|
||||||
"output_name": str(i + 1) + ".out",
|
"output_name": str(i + 1) + ".out",
|
||||||
"output_md5": md5.hexdigest(),
|
"output_md5": md5.hexdigest(),
|
||||||
|
"striped_output_md5": striped_md5.hexdigest(),
|
||||||
"output_size": os.path.getsize(test_case_dir + str(i + 1) + ".out")}
|
"output_size": os.path.getsize(test_case_dir + str(i + 1) + ".out")}
|
||||||
# 写入配置文件
|
# 写入配置文件
|
||||||
open(test_case_dir + "info", "w").write(json.dumps(file_info))
|
open(test_case_dir + "info", "w").write(json.dumps(file_info))
|
||||||
@@ -228,6 +245,17 @@ def problem_list_page(request, page=1):
|
|||||||
if keyword:
|
if keyword:
|
||||||
problems = problems.filter(Q(title__contains=keyword) | Q(description__contains=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)
|
tag_text = request.GET.get("tag", None)
|
||||||
if tag_text:
|
if tag_text:
|
||||||
@@ -235,7 +263,7 @@ def problem_list_page(request, page=1):
|
|||||||
tag = ProblemTag.objects.get(name=tag_text)
|
tag = ProblemTag.objects.get(name=tag_text)
|
||||||
except ProblemTag.DoesNotExist:
|
except ProblemTag.DoesNotExist:
|
||||||
return error_page(request, u"标签不存在")
|
return error_page(request, u"标签不存在")
|
||||||
problems = tag.problem_set.all()
|
problems = tag.problem_set.all().filter(visible=True)
|
||||||
|
|
||||||
paginator = Paginator(problems, 20)
|
paginator = Paginator(problems, 20)
|
||||||
try:
|
try:
|
||||||
@@ -255,13 +283,15 @@ def problem_list_page(request, page=1):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 右侧的公告列表
|
if request.user.is_authenticated():
|
||||||
announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
|
problems_status = json.loads(request.user.problems_status)
|
||||||
|
else:
|
||||||
|
problems_status = {}
|
||||||
# 右侧标签列表 按照关联的题目的数量排序 排除题目数量为0的
|
# 右侧标签列表 按照关联的题目的数量排序 排除题目数量为0的
|
||||||
tags = ProblemTag.objects.annotate(problem_number=Count("problem")).filter(problem_number__gt=0).order_by("-problem_number")
|
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",
|
return render(request, "oj/problem/problem_list.html",
|
||||||
{"problems": current_page, "page": int(page),
|
{"problems": current_page, "page": int(page),
|
||||||
"previous_page": previous_page, "next_page": next_page,
|
"previous_page": previous_page, "next_page": next_page,
|
||||||
"keyword": keyword, "tag": tag_text,
|
"keyword": keyword, "tag": tag_text,"problems_status": problems_status,
|
||||||
"announcements": announcements, "tags": tags})
|
"tags": tags, "difficulty_order": difficulty_order})
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
/**
|
|
||||||
* Created by virusdefender on 8/25/15.
|
|
||||||
*/
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
@import url("global.css");
|
|
||||||
@import url("bootstrap/bootstrap.min.css");
|
@import url("bootstrap/bootstrap.min.css");
|
||||||
@import url("bootstrap/todc-bootstrap.min.css");
|
@import url("bootstrap/todc-bootstrap.min.css");
|
||||||
@import url("codeMirror/codemirror.css");
|
@import url("codeMirror/codemirror.css");
|
||||||
@@ -6,6 +5,7 @@
|
|||||||
@import url("webuploader/webuploader.css");
|
@import url("webuploader/webuploader.css");
|
||||||
@import url("datetime_picker/bootstrap-datetimepicker.css");
|
@import url("datetime_picker/bootstrap-datetimepicker.css");
|
||||||
@import url("tagEditor/jquery.tag-editor.css");
|
@import url("tagEditor/jquery.tag-editor.css");
|
||||||
|
@import url("global.css");
|
||||||
|
|
||||||
#loading-gif {
|
#loading-gif {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
|
|||||||
@@ -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%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body{
|
body {
|
||||||
height:100%; /*使内容高度和body一样*/
|
height: 100%; /*使内容高度和body一样*/
|
||||||
margin-bottom:-80px;/*向上缩减80像素,不至于footer超出屏幕可视范围*/
|
margin-bottom: -80px; /*向上缩减80像素,不至于footer超出屏幕可视范围*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.main{
|
.main {
|
||||||
padding-bottom: 120px;
|
padding-bottom: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,10 +34,17 @@ label {
|
|||||||
display: none
|
display: none
|
||||||
}
|
}
|
||||||
|
|
||||||
.right{
|
.right {
|
||||||
float: 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
@import url("global.css");
|
|
||||||
@import url("bootstrap/bootstrap.min.css");
|
@import url("bootstrap/bootstrap.min.css");
|
||||||
@import url("bootstrap/todc-bootstrap.min.css");
|
@import url("bootstrap/todc-bootstrap.min.css");
|
||||||
@import url("codeMirror/codemirror.css");
|
@import url("codeMirror/codemirror.css");
|
||||||
|
@import url("global.css");
|
||||||
|
|
||||||
#language-selector {
|
#language-selector {
|
||||||
width: 130px;
|
width: 130px;
|
||||||
@@ -73,6 +72,10 @@ li.list-group-item {
|
|||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dealing-flag {
|
||||||
|
color: #FF9933;
|
||||||
|
}
|
||||||
|
|
||||||
.CodeMirror{
|
.CodeMirror{
|
||||||
min-height: 250px;
|
min-height: 250px;
|
||||||
_height:250px;
|
_height:250px;
|
||||||
@@ -91,4 +94,21 @@ li.list-group-item {
|
|||||||
|
|
||||||
.contest-tab{
|
.contest-tab{
|
||||||
margin-bottom: 15px;
|
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
BIN
static/src/img/ZeroClipboard.swf
Executable file
Binary file not shown.
BIN
static/src/img/acm_logo.png
Normal file
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
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
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
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
BIN
static/src/img/ie.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
37
static/src/img/unsupported_browser.html
Normal file
37
static/src/img/unsupported_browser.html
Normal 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>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
define("admin", ["jquery", "avalon"], function ($, avalon) {
|
require(["jquery", "avalon", "bootstrap"], function ($, avalon) {
|
||||||
|
|
||||||
avalon.ready(function () {
|
avalon.ready(function () {
|
||||||
|
|
||||||
|
|||||||
@@ -2,40 +2,46 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
|
|||||||
"validator"],
|
"validator"],
|
||||||
function ($, avalon, editor, uploader, bsAlert, csrfTokenHeader) {
|
function ($, avalon, editor, uploader, bsAlert, csrfTokenHeader) {
|
||||||
|
|
||||||
//avalon.vmodels.add_contest = null;
|
|
||||||
$("#add-contest-form").validator().on('submit', function (e) {
|
$("#add-contest-form").validator().on('submit', function (e) {
|
||||||
if (!e.isDefaultPrevented()){
|
if (!e.isDefaultPrevented()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var ajaxData = {
|
var ajaxData = {
|
||||||
title: vm.title,
|
title: vm.title,
|
||||||
description: vm.description,
|
description: vm.description,
|
||||||
mode: vm.mode,
|
mode: vm.mode,
|
||||||
contest_type: 0,
|
contest_type: 0,
|
||||||
show_rank: vm.showRank,
|
real_time_rank: vm.realTimeRank,
|
||||||
show_user_submission: vm.showSubmission,
|
show_user_submission: vm.showSubmission,
|
||||||
start_time: vm.startTime,
|
start_time: vm.startTime,
|
||||||
end_time: vm.endTime,
|
end_time: vm.endTime,
|
||||||
visible: false
|
visible: false
|
||||||
};
|
};
|
||||||
if (vm.choseGroupList.length == 0) {
|
|
||||||
bsAlert("你没有选择参赛用户!");
|
var selectedGroups = [];
|
||||||
return false;
|
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) {
|
if (vm.password) {
|
||||||
ajaxData.password = vm.password;
|
ajaxData.password = vm.password;
|
||||||
ajaxData.contest_type = 2;
|
ajaxData.contest_type = 2;
|
||||||
}
|
}
|
||||||
else{
|
else
|
||||||
ajaxData.contest_type = 1;
|
ajaxData.contest_type = 1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else { // Add groups info
|
if (!vm.isGlobal && !selectedGroups.length) {
|
||||||
ajaxData.groups = [];
|
bsAlert("你没有选择参赛用户!");
|
||||||
for (var i = 0; vm.choseGroupList[i]; i++)
|
return false;
|
||||||
ajaxData.groups.push(parseInt(vm.choseGroupList[i].id))
|
}
|
||||||
|
if (vm.editDescription == "") {
|
||||||
|
bsAlert("比赛描述不能为空!");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$.ajax({ // Add contest
|
$.ajax({ // Add contest
|
||||||
beforeSend: csrfTokenHeader,
|
beforeSend: csrfTokenHeader,
|
||||||
url: "/api/admin/contest/",
|
url: "/api/admin/contest/",
|
||||||
@@ -45,20 +51,18 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
|
|||||||
method: "post",
|
method: "post",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
if (!data.code) {
|
if (!data.code) {
|
||||||
bsAlert("添加成功!将转到比赛列表页以便为比赛添加问题(注意比赛当前状态为:隐藏)");
|
bsAlert("添加成功!将转到比赛列表页以便为比赛添加问题(注意比赛当前状态为:隐藏)");
|
||||||
vm.title = "";
|
vm.title = "";
|
||||||
vm.description = "";
|
vm.description = "";
|
||||||
vm.startTime = "";
|
vm.startTime = "";
|
||||||
vm.endTime = "";
|
vm.endTime = "";
|
||||||
vm.password = "";
|
vm.password = "";
|
||||||
vm.mode = "";
|
vm.mode = "0";
|
||||||
vm.showRank = false;
|
vm.showSubmission = true;
|
||||||
vm.showSubmission = false;
|
location.hash = "#contest/contest_list";
|
||||||
vm.group = "-1";
|
vm.isGlobal = true;
|
||||||
vm.groupList = [];
|
vm.allGroups = [];
|
||||||
vm.choseGroupList = [];
|
vm.showGlobalViewRadio = true;
|
||||||
vm.passwordUsable = false;
|
|
||||||
location.hash = "#contest/contest_list";
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
bsAlert(data.data);
|
bsAlert(data.data);
|
||||||
@@ -70,80 +74,59 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
|
|||||||
});
|
});
|
||||||
|
|
||||||
editor("#editor");
|
editor("#editor");
|
||||||
if (avalon.vmodels.add_contest)
|
if (avalon.vmodels.add_contest)
|
||||||
var vm = avalon.vmodels.add_contest;
|
var vm = avalon.vmodels.add_contest;
|
||||||
else
|
else
|
||||||
var vm = avalon.define({
|
var vm = avalon.define({
|
||||||
$id: "add_contest",
|
$id: "add_contest",
|
||||||
title: "",
|
title: "",
|
||||||
description: "",
|
description: "",
|
||||||
startTime: "",
|
startTime: "",
|
||||||
endTime: "",
|
endTime: "",
|
||||||
password: "",
|
password: "",
|
||||||
mode: "",
|
mode: "0",
|
||||||
showRank: false,
|
showSubmission: true,
|
||||||
showSubmission: false,
|
isGlobal: true,
|
||||||
group: "-1",
|
allGroups: [],
|
||||||
groupList: [],
|
showGlobalViewRadio: true,
|
||||||
choseGroupList: [],
|
realTimeRank: true
|
||||||
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]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$.ajax({ // Get current user type
|
$.ajax({
|
||||||
url: "/api/user/",
|
url: "/api/user/",
|
||||||
method: "get",
|
method: "get",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
if (!data.code) {
|
if (!data.code) {
|
||||||
if (data.data.admin_type == 2) { // Is super user
|
var admin_type = data.data.admin_type;
|
||||||
vm.isGlobal = true;
|
if (data.data.admin_type == 1) {
|
||||||
vm.groupList.push({id:0,name:"所有人",chose:false});
|
vm.isGlobal = false;
|
||||||
|
vm.showGlobalViewRadio = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
$.ajax({ // Get the group list of current user
|
}
|
||||||
beforeSend: csrfTokenHeader,
|
$.ajax({
|
||||||
url: "/api/admin/group/",
|
url: "/api/admin/group/",
|
||||||
method: "get",
|
method: "get",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
if (!data.code) {
|
if (!data.code) {
|
||||||
if (!data.data.length) {
|
if (!data.data.length) {
|
||||||
return;
|
if (admin_type != 2)
|
||||||
}
|
bsAlert("您的用户权限只能创建小组内比赛,但是您还没有创建过小组");
|
||||||
for (var i = 0; i < data.data.length; i++) {
|
return;
|
||||||
var item = data.data[i];
|
|
||||||
item["chose"] = false;
|
|
||||||
vm.groupList.push(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
for (var i = 0; i < data.data.length; i++) {
|
||||||
bsAlert(data.data);
|
var item = data.data[i];
|
||||||
|
item["isSelected"] = false;
|
||||||
|
vm.allGroups.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
else {
|
||||||
}
|
bsAlert(data.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,21 +3,39 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
|
|||||||
avalon.ready(function () {
|
avalon.ready(function () {
|
||||||
|
|
||||||
$("#edit-contest-form").validator().on('submit', function (e) {
|
$("#edit-contest-form").validator().on('submit', function (e) {
|
||||||
if (!e.isDefaultPrevented()){
|
if (!e.isDefaultPrevented()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var ajaxData = {
|
var ajaxData = {
|
||||||
id: vm.contestList[vm.editingContestId-1].id,
|
id: vm.contestList[vm.editingContestId - 1].id,
|
||||||
title: vm.editTitle,
|
title: vm.editTitle,
|
||||||
description: vm.editDescription,
|
description: vm.editDescription,
|
||||||
mode: vm.editMode,
|
mode: vm.editMode,
|
||||||
contest_type: 0,
|
contest_type: 0,
|
||||||
show_rank: vm.editShowRank,
|
real_time_rank: vm.editRealTimeRank,
|
||||||
show_user_submission: vm.editShowSubmission,
|
show_user_submission: vm.editShowSubmission,
|
||||||
start_time: vm.editStartTime,
|
start_time: vm.editStartTime,
|
||||||
end_time: vm.editEndTime,
|
end_time: vm.editEndTime,
|
||||||
visible: vm.editVisible
|
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("你没有选择参赛用户!");
|
bsAlert("你没有选择参赛用户!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -25,22 +43,8 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
|
|||||||
bsAlert("比赛描述不能为空!");
|
bsAlert("比赛描述不能为空!");
|
||||||
return false;
|
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,
|
beforeSend: csrfTokenHeader,
|
||||||
url: "/api/admin/contest/",
|
url: "/api/admin/contest/",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
@@ -52,7 +56,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
|
|||||||
if (!data.code) {
|
if (!data.code) {
|
||||||
bsAlert("修改成功!");
|
bsAlert("修改成功!");
|
||||||
vm.editingContestId = 0; // Hide the editor
|
vm.editingContestId = 0; // Hide the editor
|
||||||
vm.getPage(1); // Refresh the contest list
|
vm.getPage(1); // Refresh the contest list
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
bsAlert(data.data);
|
bsAlert(data.data);
|
||||||
@@ -63,138 +67,124 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if(avalon.vmodels.contestList){
|
if (avalon.vmodels.contestList) {
|
||||||
// this page has been loaded before, so set the default value
|
// this page has been loaded before, so set the default value
|
||||||
var vm = avalon.vmodels.contestList;
|
var vm = avalon.vmodels.contestList;
|
||||||
vm.contestList= [];
|
vm.contestList = [];
|
||||||
vm.previousPage= 0;
|
vm.previousPage = 0;
|
||||||
vm.nextPage= 0;
|
vm.nextPage = 0;
|
||||||
vm.page= 1;
|
vm.page = 1;
|
||||||
vm.totalPage= 1;
|
vm.totalPage = 1;
|
||||||
vm.group= "-1";
|
vm.keyword = "";
|
||||||
vm.groupList= [];
|
vm.editingContestId = 0;
|
||||||
vm.choseGroupList= [];
|
vm.editTitle = "";
|
||||||
vm.passwordUsable= false;
|
vm.editDescription = "";
|
||||||
vm.keyword= "";
|
vm.editProblemList = [];
|
||||||
vm.editingContestId= 0;
|
vm.editPassword = "";
|
||||||
vm.editTitle= "";
|
vm.editStartTime = "";
|
||||||
vm.editDescription= "";
|
vm.editEndTime = "";
|
||||||
vm.editProblemList= [];
|
vm.editMode = "";
|
||||||
vm.editPassword= "";
|
vm.editShowSubmission = false;
|
||||||
vm.editStartTime= "";
|
vm.editVisible = false;
|
||||||
vm.editEndTime= "";
|
vm.editingProblemContestIndex = 0;
|
||||||
vm.editMode= "";
|
vm.editRealTimeRank = true;
|
||||||
vm.editShowRank= false;
|
}
|
||||||
vm.editShowSubmission= false;
|
else {
|
||||||
vm.editProblemList= [];
|
var vm = avalon.define({
|
||||||
vm.editVisible= false;
|
$id: "contestList",
|
||||||
vm.editChoseGroupList= [];
|
contestList: [],
|
||||||
vm.editingProblemContestIndex= 0;
|
previousPage: 0,
|
||||||
}
|
nextPage: 0,
|
||||||
else {
|
page: 1,
|
||||||
var vm = avalon.define({
|
totalPage: 1,
|
||||||
$id: "contestList",
|
showVisibleOnly: false,
|
||||||
contestList: [],
|
keyword: "",
|
||||||
previousPage: 0,
|
editingContestId: 0,
|
||||||
nextPage: 0,
|
editTitle: "",
|
||||||
page: 1,
|
editDescription: "",
|
||||||
totalPage: 1,
|
editProblemList: [],
|
||||||
showVisibleOnly: false,
|
editPassword: "",
|
||||||
group: "-1",
|
editStartTime: "",
|
||||||
groupList: [],
|
editEndTime: "",
|
||||||
choseGroupList: [],
|
editMode: "",
|
||||||
passwordUsable: false,
|
editShowSubmission: false,
|
||||||
keyword: "",
|
editVisible: false,
|
||||||
editingContestId: 0,
|
editRealTimeRank: true,
|
||||||
editTitle: "",
|
editingProblemContestIndex: 0,
|
||||||
editDescription: "",
|
isGlobal: true,
|
||||||
editProblemList: [],
|
allGroups: [],
|
||||||
editPassword: "",
|
showGlobalViewRadio: true,
|
||||||
editStartTime: "",
|
admin_type: 1,
|
||||||
editEndTime: "",
|
getNext: function () {
|
||||||
editMode: "",
|
if (!vm.nextPage)
|
||||||
editShowRank: false,
|
return;
|
||||||
editShowSubmission: false,
|
getPageData(vm.page + 1);
|
||||||
editProblemList: [],
|
},
|
||||||
editVisible: false,
|
getPrevious: function () {
|
||||||
editChoseGroupList: [],
|
if (!vm.previousPage)
|
||||||
editingProblemContestIndex: 0,
|
return;
|
||||||
getNext: function () {
|
getPageData(vm.page - 1);
|
||||||
if (!vm.nextPage)
|
},
|
||||||
return;
|
getBtnClass: function (btn) {
|
||||||
getPageData(vm.page + 1);
|
if (btn == "next") {
|
||||||
},
|
return vm.nextPage ? "btn btn-primary" : "btn btn-primary disabled";
|
||||||
getPrevious: function () {
|
}
|
||||||
if (!vm.previousPage)
|
else {
|
||||||
return;
|
return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled";
|
||||||
getPageData(vm.page - 1);
|
}
|
||||||
},
|
},
|
||||||
getBtnClass: function (btn) {
|
getPage: function (page_index) {
|
||||||
if (btn == "next") {
|
getPageData(page_index);
|
||||||
return vm.nextPage ? "btn btn-primary" : "btn btn-primary disabled";
|
},
|
||||||
}
|
showEditContestArea: function (contestId) {
|
||||||
else {
|
if (vm.editingContestId && !confirm("如果继续将丢失未保存的信息,是否继续?"))
|
||||||
return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled";
|
return;
|
||||||
}
|
if (contestId == vm.editingContestId)
|
||||||
},
|
vm.editingContestId = 0;
|
||||||
getPage: function (page_index) {
|
else {
|
||||||
getPageData(page_index);
|
vm.editingContestId = contestId;
|
||||||
},
|
vm.editTitle = vm.contestList[contestId - 1].title;
|
||||||
showEditContestArea: function (contestId) {
|
vm.editPassword = vm.contestList[contestId - 1].password;
|
||||||
if (vm.editingContestId && !confirm("如果继续将丢失未保存的信息,是否继续?"))
|
vm.editStartTime = vm.contestList[contestId - 1].start_time.substring(0, 16).replace("T", " ");
|
||||||
return;
|
vm.editEndTime = vm.contestList[contestId - 1].end_time.substring(0, 16).replace("T", " ");
|
||||||
if (contestId == vm.editingContestId)
|
vm.editMode = vm.contestList[contestId - 1].mode;
|
||||||
vm.editingContestId = 0;
|
vm.editVisible = vm.contestList[contestId - 1].visible;
|
||||||
else {
|
vm.editRealTimeRank = vm.contestList[contestId - 1].real_time_rank;
|
||||||
vm.editingContestId = contestId;
|
if (vm.contestList[contestId - 1].contest_type == 0) { //contest type == 0, contest in group
|
||||||
vm.editTitle = vm.contestList[contestId-1].title;
|
vm.isGlobal = false;
|
||||||
vm.editPassword = vm.contestList[contestId-1].password;
|
for (var i = 0; i < vm.allGroups.length; i++) {
|
||||||
vm.editStartTime = vm.contestList[contestId-1].start_time.substring(0,16).replace("T"," ");
|
vm.allGroups[i].isSelected = false;
|
||||||
vm.editEndTime = vm.contestList[contestId-1].end_time.substring(0,16).replace("T"," ");
|
}
|
||||||
vm.editMode = vm.contestList[contestId-1].mode;
|
for (var i = 0; i < vm.contestList[contestId - 1].groups.length; i++) {
|
||||||
vm.editVisible = vm.contestList[contestId-1].visible;
|
var id = parseInt(vm.contestList[contestId - 1].groups[i]);
|
||||||
if (vm.contestList[contestId-1].contest_type == 0) { //contest type == 0, contest in group
|
|
||||||
//Clear the choseGroupList
|
for (var index = 0; vm.allGroups[index]; index++) {
|
||||||
while (vm.choseGroupList.length) {
|
if (vm.allGroups[index].id == id) {
|
||||||
vm.removeGroup(0);
|
vm.allGroups[index].isSelected = true;
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
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";
|
showEditProblemArea: function (contestId) {
|
||||||
vm.addGroup()//vm.editChoseGroupList = [0]; id 0 is for the group of everyone~
|
if (vm.editingProblemContestIndex == contestId) {
|
||||||
|
vm.editingProblemContestIndex = 0;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
vm.editShowRank = vm.contestList[contestId-1].show_rank;
|
if (vm.editingContestId && !confirm("如果继续将丢失未保存的信息,是否继续?")) {
|
||||||
vm.editShowSubmission = vm.contestList[contestId-1].show_user_submission;
|
return;
|
||||||
editor("#editor").setValue(vm.contestList[contestId-1].description);
|
}
|
||||||
vm.editingProblemContestIndex = 0;
|
$.ajax({ // Get the problem list of current contest
|
||||||
}
|
|
||||||
},
|
|
||||||
showEditProblemArea: function(contestId) {
|
|
||||||
if (vm.editingProblemContestIndex == contestId) {
|
|
||||||
vm.editingProblemContestIndex = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (vm.editingContestId&&!confirm("如果继续将丢失未保存的信息,是否继续?")){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$.ajax({ // Get the problem list of current contest
|
|
||||||
beforeSend: csrfTokenHeader,
|
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",
|
method: "get",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
@@ -206,51 +196,60 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
vm.editingContestId = 0;
|
vm.editingContestId = 0;
|
||||||
vm.editingProblemContestIndex = contestId;
|
vm.editingProblemContestIndex = contestId;
|
||||||
vm.editMode = vm.contestList[contestId-1].mode;
|
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]);
|
|
||||||
},
|
},
|
||||||
addProblem: function () {
|
addProblem: function () {
|
||||||
vm.$fire("up!showContestProblemPage", 0, vm.contestList[vm.editingProblemContestIndex-1].id, vm.editMode);
|
vm.$fire("up!showContestProblemPage", 0, vm.contestList[vm.editingProblemContestIndex - 1].id, vm.editMode);
|
||||||
},
|
},
|
||||||
showProblemEditPage: function(el) {
|
showProblemEditPage: function (el) {
|
||||||
vm.$fire("up!showContestProblemPage", el.id, vm.contestList[vm.editingProblemContestIndex-1].id, vm.editMode);
|
vm.$fire("up!showContestProblemPage", el.id, vm.contestList[vm.editingProblemContestIndex - 1].id, vm.editMode);
|
||||||
},
|
},
|
||||||
showSubmissionPage: function(el) {
|
showSubmissionPage: function (el) {
|
||||||
var problemId = 0
|
var problemId = 0
|
||||||
if (el)
|
if (el)
|
||||||
problemId = el.id;
|
problemId = el.id;
|
||||||
vm.$fire("up!showContestSubmissionPage", problemId, vm.contestList[vm.editingProblemContestIndex-1].id, vm.editMode);
|
vm.$fire("up!showContestSubmissionPage", problemId, vm.contestList[vm.editingProblemContestIndex - 1].id, vm.editMode);
|
||||||
}
|
},
|
||||||
});
|
addToProblemList: function (problem) {
|
||||||
vm.$watch("showVisibleOnly", function() {
|
var ajaxData = {
|
||||||
getPageData(1);
|
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);
|
getPageData(1);
|
||||||
|
|
||||||
//init time picker
|
//init time picker
|
||||||
@@ -293,39 +292,42 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get group list
|
// Get group list
|
||||||
$.ajax({ // Get current user type
|
$.ajax({
|
||||||
url: "/api/user/",
|
url: "/api/user/",
|
||||||
method: "get",
|
method: "get",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
if (!data.code) {
|
if (!data.code) {
|
||||||
if (data.data.admin_type == 2) { // Is super user
|
var admin_type = data.data.admin_type;
|
||||||
vm.isGlobal = true;
|
vm.admin_type = admin_type;
|
||||||
vm.groupList.push({id:0,name:"所有人",chose:false});
|
if (data.data.admin_type == 1) {
|
||||||
|
vm.isGlobal = false;
|
||||||
|
vm.showGlobalViewRadio = false;
|
||||||
}
|
}
|
||||||
$.ajax({ // Get the group list of current user
|
}
|
||||||
beforeSend: csrfTokenHeader,
|
$.ajax({
|
||||||
url: "/api/admin/group/",
|
url: "/api/admin/group/",
|
||||||
method: "get",
|
method: "get",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
if (!data.code) {
|
if (!data.code) {
|
||||||
if (!data.data.length) {
|
if (!data.data.length) {
|
||||||
//this user have no group can use
|
|
||||||
return;
|
if (admin_type != 2)
|
||||||
}
|
bsAlert("您的用户权限只能创建小组内比赛,但是您还没有创建过小组");
|
||||||
for (var i = 0; i < data.data.length; i++) {
|
return;
|
||||||
var item = data.data[i];
|
|
||||||
item["chose"] = false;
|
|
||||||
vm.groupList.push(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
for (var i = 0; i < data.data.length; i++) {
|
||||||
bsAlert(data.data);
|
var item = data.data[i];
|
||||||
|
item["isSelected"] = false;
|
||||||
|
vm.allGroups.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
else {
|
||||||
}
|
bsAlert(data.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
|
|||||||
bsAlert("题目描述不能为空!");
|
bsAlert("题目描述不能为空!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (vm.timeLimit < 100 || vm.timeLimit > 5000) {
|
||||||
|
bsAlert("保证时间限制是一个100-5000的合法整数");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (vm.samples.length == 0) {
|
if (vm.samples.length == 0) {
|
||||||
bsAlert("请至少添加一组样例!");
|
bsAlert("请至少添加一组样例!");
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
|
|||||||
bsAlert("题目描述不能为空!");
|
bsAlert("题目描述不能为空!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (vm.timeLimit < 1000 || vm.timeLimit > 5000) {
|
if (vm.timeLimit < 100 || vm.timeLimit > 5000) {
|
||||||
bsAlert("保证时间限制是一个1000-5000的合法整数");
|
bsAlert("保证时间限制是一个100-5000的合法整数");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (vm.samples.length == 0) {
|
if (vm.samples.length == 0) {
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, csrfTokenHeader) {
|
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) {
|
$('form').validator().on('submit', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var newPassword = $("#new_password ").val();
|
var newPassword = $("#new_password ").val();
|
||||||
var password = $("#password").val();
|
var password = $("#password").val();
|
||||||
|
var captcha = $("#captcha").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
beforeSend: csrfTokenHeader,
|
beforeSend: csrfTokenHeader,
|
||||||
url: "/api/change_password/",
|
url: "/api/change_password/",
|
||||||
data: {new_password: newPassword, old_password: password},
|
data: {new_password: newPassword, old_password: password, captcha: captcha},
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
method: "post",
|
method: "post",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
@@ -15,6 +24,7 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
|
|||||||
window.location.href = "/login/";
|
window.location.href = "/login/";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
refresh_captcha();
|
||||||
bsAlert(data.data);
|
bsAlert(data.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,31 @@
|
|||||||
require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, csrfTokenHeader) {
|
require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, csrfTokenHeader) {
|
||||||
|
var applied_captcha = false;
|
||||||
$('form').validator().on('submit', function (e) {
|
$('form').validator().on('submit', function (e) {
|
||||||
if (!e.isDefaultPrevented()) {
|
if (!e.isDefaultPrevented()) {
|
||||||
var username = $("#username").val();
|
var username = $("#username").val();
|
||||||
var password = $("#password").val();
|
var password = $("#password").val();
|
||||||
|
var ajaxData = {username: username, password: password};
|
||||||
|
if (applied_captcha) {
|
||||||
|
ajaxData.captcha = $("#captcha").val();
|
||||||
|
}
|
||||||
$.ajax({
|
$.ajax({
|
||||||
beforeSend: csrfTokenHeader,
|
beforeSend: csrfTokenHeader,
|
||||||
url: "/api/login/",
|
url: "/api/login/",
|
||||||
data: {username: username, password: password},
|
data: ajaxData,
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
method: "post",
|
method: "post",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
if (!data.code) {
|
if (!data.code) {
|
||||||
//成功登陆
|
//成功登陆
|
||||||
var ref = document.referrer;
|
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 = "/";
|
location.href = "/";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 判断来源,只有同域下才跳转
|
// 判断来源,只有同域下才跳转
|
||||||
if(ref.split("/")[2].split(":")[0] == location.hostname){
|
if (ref.split("/")[2].split(":")[0] == location.hostname) {
|
||||||
location.href = ref;
|
location.href = ref;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -28,6 +33,7 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
|
|||||||
location.href = "/";
|
location.href = "/";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
refresh_captcha();
|
||||||
bsAlert(data.data);
|
bsAlert(data.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,5 +41,34 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
|
|||||||
});
|
});
|
||||||
return false;
|
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> <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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -5,10 +5,11 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
|
|||||||
var realName = $("#real_name").val();
|
var realName = $("#real_name").val();
|
||||||
var password = $("#password").val();
|
var password = $("#password").val();
|
||||||
var email = $("#email").val();
|
var email = $("#email").val();
|
||||||
|
var captcha = $("#captcha").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
beforeSend: csrfTokenHeader,
|
beforeSend: csrfTokenHeader,
|
||||||
url: "/api/register/",
|
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",
|
dataType: "json",
|
||||||
method: "post",
|
method: "post",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
@@ -16,11 +17,20 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
|
|||||||
window.location.href = "/login/";
|
window.location.href = "/login/";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
refresh_captcha();
|
||||||
bsAlert(data.data);
|
bsAlert(data.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
function refresh_captcha() {
|
||||||
|
$("#captcha-img")[0].src = "/captcha/?" + Math.random();
|
||||||
|
$("#captcha")[0].value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#captcha-img").click(function () {
|
||||||
|
refresh_captcha();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -1,157 +1,252 @@
|
|||||||
require(["jquery", "codeMirror", "csrfToken", "bsAlert"], function ($, codeMirror, csrfTokenHeader, bsAlert) {
|
require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"],
|
||||||
var codeEditor = codeMirror($("#code-editor")[0], "text/x-csrc");
|
function ($, codeMirror, csrfTokenHeader, bsAlert, ZeroClipboard) {
|
||||||
var language = $("input[name='language'][checked]").val();
|
// 复制样例需要 Flash 的支持 检测浏览器是否安装了 Flash
|
||||||
var submissionId;
|
function detect_flash() {
|
||||||
|
var ie_flash;
|
||||||
$("input[name='language']").change(function () {
|
try {
|
||||||
language = this.value;
|
ie_flash = (window.ActiveXObject && (new ActiveXObject("ShockwaveFlash.ShockwaveFlash")) !== false)
|
||||||
var languageTypes = {"1": "text/x-csrc", "2": "text/x-c++src", "3": "text/x-java"};
|
} catch (err) {
|
||||||
codeEditor.setOption("mode", languageTypes[language]);
|
ie_flash = false;
|
||||||
});
|
}
|
||||||
|
var _flash_installed = ((typeof navigator.plugins != "undefined" && typeof navigator.plugins["Shockwave Flash"] == "object") || ie_flash);
|
||||||
$("#show-more-btn").click(function () {
|
return _flash_installed;
|
||||||
$(".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 +
|
|
||||||
'! ';
|
|
||||||
if (!data.result) {
|
|
||||||
html += "CPU time: " + data.accepted_answer_time + "ms ";
|
|
||||||
}
|
}
|
||||||
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;
|
var codeEditorSelector = $("#code-editor")[0];
|
||||||
|
// 部分界面逻辑会隐藏代码输入框,先判断有没有。
|
||||||
function getResult() {
|
if (codeEditorSelector == undefined) {
|
||||||
if (counter++ > 10) {
|
|
||||||
hideLoading();
|
|
||||||
bsAlert("抱歉,服务器可能出现了故障,请稍后到我的提交列表中查看");
|
|
||||||
counter = 0;
|
|
||||||
return;
|
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 +
|
||||||
|
'! ';
|
||||||
|
if (!data.result) {
|
||||||
|
html += "CPU time: " + data.accepted_answer_time + "ms ";
|
||||||
|
}
|
||||||
|
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({
|
$.ajax({
|
||||||
url: "/api/submission/?submission_id=" + submissionId,
|
url: "/api/user/",
|
||||||
method: "get",
|
method: "get",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
if (!data.code) {
|
if (data.code) {
|
||||||
// 8是还没有完成判题
|
$("#submit-code-button").attr("disabled", "disabled");
|
||||||
if (data.data.result == 8) {
|
$("#result").html('<div class="alert alert-danger" role="alert"><div class="alert-link">请先<a href="/login/" target="_blank">登录</a>!</div> </div>');
|
||||||
// 1秒之后重新去获取
|
|
||||||
setTimeout(getResult, 1000);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
counter = 0;
|
|
||||||
hideLoading();
|
|
||||||
$("#result").html(getResultHtml(data.data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
bsAlert(data.data);
|
|
||||||
hideLoading();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
});
|
||||||
|
|
||||||
$("#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>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
require(["jquery", "avalon"], function($, avalon){
|
|
||||||
var vm = avalon.define({
|
|
||||||
$id: "problem_list",
|
|
||||||
problem_list: []
|
|
||||||
})
|
|
||||||
});
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
({
|
({
|
||||||
// RequireJS 通过一个相对的路径 baseUrl来加载所有代码。baseUrl通常被设置成data-main属性指定脚本的同级目录。
|
// RequireJS 通过一个相对的路径 baseUrl来加载所有代码。baseUrl通常被设置成data-main属性指定脚本的同级目录。
|
||||||
baseUrl: "js/",
|
baseUrl: "./js",
|
||||||
// 第三方脚本模块的别名,jquery比libs/jquery-1.11.1.min.js简洁明了;
|
// 第三方脚本模块的别名,jquery比libs/jquery-1.11.1.min.js简洁明了;
|
||||||
paths: {
|
paths: {
|
||||||
jquery: "lib/jquery/jquery",
|
jquery: "empty:",
|
||||||
avalon: "lib/avalon/avalon",
|
avalon: "empty:",
|
||||||
editor: "utils/editor",
|
editor: "utils/editor",
|
||||||
uploader: "utils/uploader",
|
uploader: "utils/uploader",
|
||||||
formValidation: "utils/formValidation",
|
formValidation: "utils/formValidation",
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
tagEditor: "lib/tagEditor/jquery.tag-editor.min",
|
tagEditor: "lib/tagEditor/jquery.tag-editor.min",
|
||||||
jqueryUI: "lib/jqueryUI/jquery-ui",
|
jqueryUI: "lib/jqueryUI/jquery-ui",
|
||||||
bootstrap: "lib/bootstrap/bootstrap",
|
bootstrap: "lib/bootstrap/bootstrap",
|
||||||
datetimePicker: "lib/datetime_picker/bootstrap-datetimepicker.zh-CN",
|
datetimePicker: "lib/datetime_picker/bootstrap-datetimepicker",
|
||||||
validator: "lib/validator/validator",
|
validator: "lib/validator/validator",
|
||||||
|
ZeroClipboard: "lib/ZeroClipboard/ZeroClipboard",
|
||||||
|
|
||||||
// ------ 下面写的都不要直接用,而是使用上面的封装版本 ------
|
// ------ 下面写的都不要直接用,而是使用上面的封装版本 ------
|
||||||
//富文本编辑器simditor -> editor
|
//富文本编辑器simditor -> editor
|
||||||
@@ -33,35 +33,109 @@
|
|||||||
_codeMirror: "lib/codeMirror/codemirror",
|
_codeMirror: "lib/codeMirror/codemirror",
|
||||||
codeMirrorClang: "lib/codeMirror/language/clike",
|
codeMirrorClang: "lib/codeMirror/language/clike",
|
||||||
|
|
||||||
|
// bootstrap组件
|
||||||
|
modal: "lib/bootstrap/modal",
|
||||||
|
dropdown: "lib/bootstrap/dropdown",
|
||||||
|
transition: "lib/bootstrap/transition",
|
||||||
|
|
||||||
//百度webuploader -> uploader
|
//百度webuploader -> uploader
|
||||||
webUploader: "lib/webuploader/webuploader",
|
webUploader: "lib/webuploader/webuploader",
|
||||||
|
|
||||||
"_datetimePicker": "lib/datetime_picker/bootstrap-datetimepicker"
|
//"_datetimePicker": "lib/datetime_picker/bootstrap-datetimepicker",
|
||||||
|
|
||||||
},
|
//以下都是页面 script 标签引用的js
|
||||||
shim: {
|
addProblem_0_pack: "app/admin/problem/addProblem",
|
||||||
"bootstrap": {"deps": ['jquery']},
|
addContest_1_pack: "app/admin/contest/addContest",
|
||||||
"_datetimepicker": {"deps": ["jquery"]},
|
problem_2_pack: "app/admin/problem/problem",
|
||||||
"datetimepicker": {"deps": ["_datetimepicker"]}
|
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,
|
findNestedDependencies: true,
|
||||||
appDir: "../",
|
appDir: "../",
|
||||||
dir: "../../release/",
|
dir: "../../release/",
|
||||||
modules: [
|
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",
|
||||||
})
|
})
|
||||||
@@ -2,7 +2,6 @@ var require = {
|
|||||||
// RequireJS 通过一个相对的路径 baseUrl来加载所有代码。baseUrl通常被设置成data-main属性指定脚本的同级目录。
|
// RequireJS 通过一个相对的路径 baseUrl来加载所有代码。baseUrl通常被设置成data-main属性指定脚本的同级目录。
|
||||||
baseUrl: "/static/js/",
|
baseUrl: "/static/js/",
|
||||||
paths: {
|
paths: {
|
||||||
|
|
||||||
jquery: "lib/jquery/jquery",
|
jquery: "lib/jquery/jquery",
|
||||||
avalon: "lib/avalon/avalon",
|
avalon: "lib/avalon/avalon",
|
||||||
editor: "utils/editor",
|
editor: "utils/editor",
|
||||||
@@ -18,8 +17,9 @@ var require = {
|
|||||||
tagEditor: "lib/tagEditor/jquery.tag-editor.min",
|
tagEditor: "lib/tagEditor/jquery.tag-editor.min",
|
||||||
jqueryUI: "lib/jqueryUI/jquery-ui",
|
jqueryUI: "lib/jqueryUI/jquery-ui",
|
||||||
bootstrap: "lib/bootstrap/bootstrap",
|
bootstrap: "lib/bootstrap/bootstrap",
|
||||||
datetimePicker: "lib/datetime_picker/bootstrap-datetimepicker.zh-CN",
|
datetimePicker: "lib/datetime_picker/bootstrap-datetimepicker",
|
||||||
validator: "lib/validator/validator",
|
validator: "lib/validator/validator",
|
||||||
|
ZeroClipboard: "lib/ZeroClipboard/ZeroClipboard",
|
||||||
|
|
||||||
|
|
||||||
// ------ 下面写的都不要直接用,而是使用上面的封装版本 ------
|
// ------ 下面写的都不要直接用,而是使用上面的封装版本 ------
|
||||||
@@ -33,15 +33,37 @@ var require = {
|
|||||||
_codeMirror: "lib/codeMirror/codemirror",
|
_codeMirror: "lib/codeMirror/codemirror",
|
||||||
codeMirrorClang: "lib/codeMirror/language/clike",
|
codeMirrorClang: "lib/codeMirror/language/clike",
|
||||||
|
|
||||||
|
// bootstrap组件
|
||||||
|
modal: "lib/bootstrap/modal",
|
||||||
|
dropdown: "lib/bootstrap/dropdown",
|
||||||
|
transition: "lib/bootstrap/transition",
|
||||||
|
|
||||||
//百度webuploader -> uploader
|
//百度webuploader -> uploader
|
||||||
webUploader: "lib/webuploader/webuploader",
|
webUploader: "lib/webuploader/webuploader",
|
||||||
|
|
||||||
"_datetimePicker": "lib/datetime_picker/bootstrap-datetimepicker"
|
// "_datetimePicker": "lib/datetime_picker/bootstrap-datetimepicker",
|
||||||
},
|
|
||||||
shim: {
|
//以下都是页面 script 标签引用的js
|
||||||
bootstrap: {deps: ["jquery"]},
|
addProblem_0_pack: "app/admin/problem/addProblem",
|
||||||
_datetimePicker: {dep: ["jquery"]},
|
addContest_1_pack: "app/admin/contest/addContest",
|
||||||
datetimePicker: {deps: ["_datetimePicker"]},
|
problem_2_pack: "app/admin/problem/problem",
|
||||||
validator: ["jquery"]
|
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"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
2610
static/src/js/lib/ZeroClipboard/ZeroClipboard.js
Executable file
2610
static/src/js/lib/ZeroClipboard/ZeroClipboard.js
Executable file
File diff suppressed because it is too large
Load Diff
2364
static/src/js/lib/bootstrap/bootstrap.js
vendored
2364
static/src/js/lib/bootstrap/bootstrap.js
vendored
File diff suppressed because it is too large
Load Diff
168
static/src/js/lib/bootstrap/dropdown.js
Normal file
168
static/src/js/lib/bootstrap/dropdown.js
Normal 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);
|
||||||
|
|
||||||
|
});
|
||||||
340
static/src/js/lib/bootstrap/modal.js
Normal file
340
static/src/js/lib/bootstrap/modal.js
Normal 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);
|
||||||
|
|
||||||
|
});
|
||||||
62
static/src/js/lib/bootstrap/transition.js
Normal file
62
static/src/js/lib/bootstrap/transition.js
Normal 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);
|
||||||
|
|
||||||
|
});
|
||||||
@@ -26,8 +26,15 @@
|
|||||||
*
|
*
|
||||||
* Make it work in bootstrap v3
|
* Make it work in bootstrap v3
|
||||||
*/
|
*/
|
||||||
|
(function(factory){
|
||||||
!function ($) {
|
if (typeof define === "function" && define.amd) {
|
||||||
|
define(["jquery"], factory);
|
||||||
|
} else if (typeof exports === 'object') {
|
||||||
|
factory(require('jquery'));
|
||||||
|
} else {
|
||||||
|
factory(jQuery);
|
||||||
|
}
|
||||||
|
}(function ($, undefined) {
|
||||||
|
|
||||||
function UTCDate() {
|
function UTCDate() {
|
||||||
return new Date(Date.UTC.apply(Date, arguments));
|
return new Date(Date.UTC.apply(Date, arguments));
|
||||||
@@ -1764,6 +1771,17 @@
|
|||||||
'</div>';
|
'</div>';
|
||||||
$.fn.datetimepicker.DPGlobal = DPGlobal;
|
$.fn.datetimepicker.DPGlobal = DPGlobal;
|
||||||
|
|
||||||
|
$.fn.datetimepicker.dates['zh-CN'] = {
|
||||||
|
days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
|
||||||
|
daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"],
|
||||||
|
daysMin: ["日", "一", "二", "三", "四", "五", "六", "日"],
|
||||||
|
months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
|
||||||
|
monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
|
||||||
|
today: "今天",
|
||||||
|
suffix: [],
|
||||||
|
meridiem: ["上午", "下午"]
|
||||||
|
};
|
||||||
|
|
||||||
/* DATETIMEPICKER NO CONFLICT
|
/* DATETIMEPICKER NO CONFLICT
|
||||||
* =================== */
|
* =================== */
|
||||||
|
|
||||||
@@ -1790,4 +1808,4 @@
|
|||||||
$('[data-provide="datetimepicker-inline"]').datetimepicker();
|
$('[data-provide="datetimepicker-inline"]').datetimepicker();
|
||||||
});
|
});
|
||||||
|
|
||||||
}(window.jQuery);
|
}));
|
||||||
|
|||||||
@@ -2,7 +2,13 @@
|
|||||||
* Simplified Chinese translation for bootstrap-datetimepicker
|
* Simplified Chinese translation for bootstrap-datetimepicker
|
||||||
* Yuan Cheung <advanimal@gmail.com>
|
* 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'] = {
|
$.fn.datetimepicker.dates['zh-CN'] = {
|
||||||
days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
|
days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
|
||||||
daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"],
|
daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"],
|
||||||
@@ -13,4 +19,4 @@
|
|||||||
suffix: [],
|
suffix: [],
|
||||||
meridiem: ["上午", "下午"]
|
meridiem: ["上午", "下午"]
|
||||||
};
|
};
|
||||||
}(jQuery));
|
});
|
||||||
|
|||||||
@@ -143,6 +143,16 @@ Uploader = (function(superClass) {
|
|||||||
processData: false,
|
processData: false,
|
||||||
contentType: false,
|
contentType: false,
|
||||||
type: 'POST',
|
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: {
|
headers: {
|
||||||
'X-File-Name': encodeURIComponent(file.name)
|
'X-File-Name': encodeURIComponent(file.name)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,8 +25,13 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
|
!function(root, factory) {
|
||||||
+function ($) {
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
define(['jquery'], factory);
|
||||||
|
} else {
|
||||||
|
factory(root.jQuery);
|
||||||
|
}
|
||||||
|
}(this, function ($) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// VALIDATOR CLASS DEFINITION
|
// VALIDATOR CLASS DEFINITION
|
||||||
@@ -322,4 +327,4 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
}(jQuery);
|
});
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ define("editor", ["simditor"], function(Simditor){
|
|||||||
toolbarFloat: false,
|
toolbarFloat: false,
|
||||||
defaultImage: null,
|
defaultImage: null,
|
||||||
upload: {
|
upload: {
|
||||||
url: "",
|
url: "/api/admin/upload_image/",
|
||||||
params: null,
|
params: null,
|
||||||
fileKey: "image",
|
fileKey: "image",
|
||||||
connectionCount: 3,
|
connectionCount: 3,
|
||||||
|
|||||||
19
submission/migrations/0006_submission_shared.py
Normal file
19
submission/migrations/0006_submission_shared.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -19,6 +19,8 @@ class Submission(models.Model):
|
|||||||
accepted_answer_time = models.IntegerField(blank=True, null=True)
|
accepted_answer_time = models.IntegerField(blank=True, null=True)
|
||||||
# 这个字段只有在题目是accepted 的时候才会用到,比赛题目的提交可能还会有得分等信息,存储在这里面
|
# 这个字段只有在题目是accepted 的时候才会用到,比赛题目的提交可能还会有得分等信息,存储在这里面
|
||||||
accepted_answer_info = models.TextField(blank=True, null=True)
|
accepted_answer_info = models.TextField(blank=True, null=True)
|
||||||
|
# 是否可以分享
|
||||||
|
shared = models.BooleanField(default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "submission"
|
db_table = "submission"
|
||||||
|
|||||||
@@ -22,3 +22,7 @@ class SubmissionSerializer(serializers.ModelSerializer):
|
|||||||
return User.objects.get(id=obj.user_id).username
|
return User.objects.get(id=obj.user_id).username
|
||||||
|
|
||||||
|
|
||||||
|
class SubmissionhareSerializer(serializers.Serializer):
|
||||||
|
submission_id = serializers.CharField(max_length=40)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from django.core.urlresolvers import reverse
|
|||||||
from account.models import User, REGULAR_USER, ADMIN, SUPER_ADMIN
|
from account.models import User, REGULAR_USER, ADMIN, SUPER_ADMIN
|
||||||
from problem.models import Problem
|
from problem.models import Problem
|
||||||
from contest.models import Contest
|
from contest.models import Contest
|
||||||
|
from contest.models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PUBLIC_CONTEST
|
||||||
from submission.models import Submission
|
from submission.models import Submission
|
||||||
from rest_framework.test import APITestCase, APIClient
|
from rest_framework.test import APITestCase, APIClient
|
||||||
|
|
||||||
@@ -82,7 +83,8 @@ class SubmissionAPITest(APITestCase):
|
|||||||
hint="hint1",
|
hint="hint1",
|
||||||
created_by=User.objects.get(username="test2"))
|
created_by=User.objects.get(username="test2"))
|
||||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
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",
|
start_time="2015-08-15T10:00:00.000Z",
|
||||||
end_time="2015-08-15T12:00:00.000Z",
|
end_time="2015-08-15T12:00:00.000Z",
|
||||||
password="aacc", created_by=User.objects.get(username="test2"))
|
password="aacc", created_by=User.objects.get(username="test2"))
|
||||||
@@ -151,7 +153,8 @@ class SubmissionAdminAPITest(APITestCase):
|
|||||||
hint="hint1",
|
hint="hint1",
|
||||||
created_by=self.user)
|
created_by=self.user)
|
||||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
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",
|
start_time="2015-08-15T10:00:00.000Z",
|
||||||
end_time="2015-08-15T12:00:00.000Z",
|
end_time="2015-08-15T12:00:00.000Z",
|
||||||
password="aacc", created_by=self.user)
|
password="aacc", created_by=self.user)
|
||||||
@@ -190,7 +193,8 @@ class SubmissionPageTest(TestCase):
|
|||||||
hint="hint1",
|
hint="hint1",
|
||||||
created_by=User.objects.get(username="test1"))
|
created_by=User.objects.get(username="test1"))
|
||||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
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",
|
start_time="2015-08-15T10:00:00.000Z",
|
||||||
end_time="2015-08-15T12:00:00.000Z",
|
end_time="2015-08-15T12:00:00.000Z",
|
||||||
password="aacc", created_by=User.objects.get(username="test1"))
|
password="aacc", created_by=User.objects.get(username="test1"))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
import redis
|
import redis
|
||||||
from django.shortcuts import render
|
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.decorators import login_required
|
||||||
from account.models import SUPER_ADMIN
|
from account.models import SUPER_ADMIN
|
||||||
|
|
||||||
|
|
||||||
from problem.models import Problem
|
from problem.models import Problem
|
||||||
from contest.models import ContestProblem
|
from contest.models import ContestProblem, Contest
|
||||||
|
|
||||||
from utils.shortcuts import serializer_invalid_response, error_response, success_response, error_page, paginate
|
|
||||||
from .models import Submission
|
|
||||||
from .serializers import CreateSubmissionSerializer, SubmissionSerializer
|
|
||||||
from announcement.models import Announcement
|
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):
|
class SubmissionAPIView(APIView):
|
||||||
@login_required
|
@login_required
|
||||||
@@ -44,9 +48,14 @@ class SubmissionAPIView(APIView):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
judge.delay(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
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"提交判题任务失败")
|
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 中判题队列长度的计数器
|
# 增加redis 中判题队列长度的计数器
|
||||||
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
|
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
|
||||||
r.incr("judge_queue_length")
|
r.incr("judge_queue_length")
|
||||||
@@ -80,24 +89,37 @@ def problem_my_submissions_list_page(request, problem_id):
|
|||||||
except Problem.DoesNotExist:
|
except Problem.DoesNotExist:
|
||||||
return error_page(request, u"问题不存在")
|
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")
|
values("id", "result", "create_time", "accepted_answer_time", "language")
|
||||||
|
|
||||||
return render(request, "oj/problem/my_submissions_list.html",
|
return render(request, "oj/problem/my_submissions_list.html",
|
||||||
{"submissions": submissions, "problem": problem})
|
{"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
|
@login_required
|
||||||
def my_submission(request, submission_id):
|
def my_submission(request, submission_id):
|
||||||
"""
|
"""
|
||||||
单个题目的提交详情页
|
单个题目的提交详情页
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 超级管理员可以查看所有的提交
|
submission = _get_submission(submission_id, request.user)
|
||||||
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)
|
|
||||||
except Submission.DoesNotExist:
|
except Submission.DoesNotExist:
|
||||||
return error_page(request, u"提交不存在")
|
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). \
|
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)
|
language = request.GET.get("language", None)
|
||||||
filter = None
|
filter = None
|
||||||
if language:
|
if language:
|
||||||
@@ -149,6 +171,16 @@ def my_submission_list_page(request, page=1):
|
|||||||
if result:
|
if result:
|
||||||
submissions = submissions.filter(result=int(result))
|
submissions = submissions.filter(result=int(result))
|
||||||
filter = {"name": "result", "content": 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)
|
paginator = Paginator(submissions, 20)
|
||||||
try:
|
try:
|
||||||
current_page = paginator.page(int(page))
|
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",
|
return render(request, "oj/submission/my_submissions_list.html",
|
||||||
{"submissions": current_page, "page": int(page),
|
{"submissions": current_page, "page": int(page),
|
||||||
"previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20,
|
"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)
|
||||||
|
|||||||
@@ -31,9 +31,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="navbar" class="navbar-collapse collapse">
|
<div id="navbar" class="navbar-collapse collapse">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
<li class="active"><a href="#">主页</a></li>
|
<li class="active"><a href="/" target="_blank">主页</a></li>
|
||||||
<li><a href="#about">题目</a></li>
|
<li><a href="#problem">题目</a></li>
|
||||||
<li><a href="#contact">提交</a></li>
|
<li><a href="#">提交</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
@@ -54,13 +54,11 @@
|
|||||||
</nav>
|
</nav>
|
||||||
<!-- nav end -->
|
<!-- nav end -->
|
||||||
|
|
||||||
<!--browser happy begin -->
|
<script>
|
||||||
<!--[if lt IE 9]>
|
if(navigator.userAgent.indexOf("MSIE") > -1){
|
||||||
<div class="alert alert-danger text-center" role="alert">
|
location.href = "/static/img/unsupported_browser.html";
|
||||||
当前网页 <strong>不支持</strong> 你正在使用的浏览器. 为了正常的访问, 请 <a href="http://browsehappy.com/">升级你的浏览器</a>.
|
}
|
||||||
</div>
|
</script>
|
||||||
<![endif]-->
|
|
||||||
<!-- browser happy end -->
|
|
||||||
|
|
||||||
<div class="container main">
|
<div class="container main">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -127,9 +125,7 @@
|
|||||||
|
|
||||||
<script src="/static/js/config.js"></script>
|
<script src="/static/js/config.js"></script>
|
||||||
<script src="/static/js/require.js"></script>
|
<script src="/static/js/require.js"></script>
|
||||||
<script>
|
<script src="/static/js/app/admin/admin.js"></script>
|
||||||
require(["bootstrap", "admin"]);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- footer begin -->
|
<!-- footer begin -->
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" name="name" class="form-control" ms-duplex="title"
|
<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 class="help-block with-errors"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,7 +26,6 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" name="start_time" id="contest_start_time"
|
<input type="text" class="form-control" name="start_time" id="contest_start_time"
|
||||||
ms-duplex="startTime" data-error="请填写比赛开始时间" required>
|
ms-duplex="startTime" data-error="请填写比赛开始时间" required>
|
||||||
|
|
||||||
<div class="help-block with-errors"></div>
|
<div class="help-block with-errors"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,61 +34,66 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" name="end_time" id="contest_end_time"
|
<input type="text" class="form-control" name="end_time" id="contest_end_time"
|
||||||
ms-duplex="endTime" data-error="请填写比赛结束时间" required>
|
ms-duplex="endTime" data-error="请填写比赛结束时间" required>
|
||||||
|
|
||||||
<div class="help-block with-errors"></div>
|
<div class="help-block with-errors"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label>允许参加的用户</label>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<select class="form-control" ms-duplex="group" ms-change="addGroup" value="-1">
|
<label>可见范围</label>
|
||||||
<option value="-1">请选择</option>
|
|
||||||
<option ms-repeat="groupList" ms-attr-value="$index" ms-visible="!el.chose">{{el.name}}</option>
|
<div>
|
||||||
</select>
|
<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>
|
</div>
|
||||||
<div class="col-md-6" ms-visible="passwordUsable">
|
<div class="col-md-6" ms-visible="isGlobal">
|
||||||
<label>密码保护</label>
|
<label>密码保护</label>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="password">
|
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="password">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12">
|
<div class="form-group col-md-12" ms-visible="!isGlobal">
|
||||||
<div ms-repeat="choseGroupList" class="group-tag" ms-click="removeGroup($index)">{{el.name}}</div>
|
<!-- radio 的value 没有用 但是没有的话,表单验证会出错-->
|
||||||
|
<div ms-repeat="allGroups" class="col-md-4">
|
||||||
|
<input type="checkbox" value="group_id" ms-duplex-checked="el.isSelected"> {{ el.name }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-3">
|
||||||
<label>排名方式</label>
|
<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">
|
<div class="form-group">
|
||||||
<label><input type="radio" name="mode" ms-duplex-string="mode" value="0">
|
<label><input type="radio" name="mode" ms-duplex-string="mode" value="0">
|
||||||
<small>ACM</small>
|
<small>ACM</small>
|
||||||
</label>
|
</label>
|
||||||
<label><input type="radio" name="mode" ms-duplex-string="mode" value="1">
|
<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>
|
<small>分数</small>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
|
<label>公开提交记录</label>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="text"><input type="checkbox" ms-duplex-checked="showRank">
|
<label class="text"><input type="checkbox" ms-duplex-checked="showSubmission">
|
||||||
<small>开放排名</small>
|
<small>公开</small>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
|
<label>实时排名</label>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="text"><input type="checkbox" ms-duplex-checked="showSubmission">
|
<label class="text"><input type="checkbox" ms-duplex-checked="realTimeRank">
|
||||||
<small>允许查看提交记录</small>
|
<small>是</small>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<tr ms-repeat="contestList">
|
<tr ms-repeat="contestList">
|
||||||
<td>{{ el.id }}</td>
|
<td>{{ el.id }}</td>
|
||||||
<td>{{ el.title }}</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.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
|
||||||
<td>{{ el.created_by.username }}</td>
|
<td>{{ el.created_by.username }}</td>
|
||||||
<td ms-text="el.visible?'可见':'不可见'"></td>
|
<td ms-text="el.visible?'可见':'不可见'"></td>
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
<textarea id="editor" placeholder="这里输入内容" autofocus ms-duplex="editDescription"></textarea>
|
<textarea id="editor" placeholder="这里输入内容" autofocus ms-duplex="editDescription"></textarea>
|
||||||
|
|
||||||
<div class="help-block with-errors"></div>
|
<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>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@@ -87,32 +87,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label>允许参加的用户</label>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<select class="form-control" ms-duplex="group" ms-change="addGroup" value="-1">
|
<label>可见范围</label>
|
||||||
<option value="-1">请选择</option>
|
<span ms-if="showGlobalViewRadio">
|
||||||
<option ms-repeat="groupList" ms-attr-value="$index" ms-visible="!el.chose">{{el.name}}</option>
|
<label>
|
||||||
</select>
|
<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>
|
||||||
<div class="col-md-6" ms-visible="passwordUsable">
|
<div class="col-md-6" ms-visible="isGlobal">
|
||||||
<label>密码保护</label>
|
<label>密码保护</label>
|
||||||
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="editPassword">
|
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="editPassword">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12">
|
<div class="form-group col-md-12" ms-visible="!isGlobal">
|
||||||
<div ms-repeat="choseGroupList" class="group-tag" ms-click="removeGroup($index)">{{el.name}}</div>
|
<!-- radio 的value 没有用 但是没有的话,表单验证会出错-->
|
||||||
</div>
|
<div ms-repeat="allGroups" class="col-md-4">
|
||||||
<div class="col-md-6">
|
<input type="checkbox" value="group_id" ms-duplex-checked="el.isSelected"> {{ el.name }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<label>排名方式</label>
|
<label>排名方式</label>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -120,34 +121,31 @@
|
|||||||
<small>ACM</small>
|
<small>ACM</small>
|
||||||
</label>
|
</label>
|
||||||
<label><input type="radio" name="mode" ms-duplex-string="editMode" value="1">
|
<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>
|
<small>分数</small>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<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">
|
<div class="form-group">
|
||||||
<label class="text"><input type="checkbox" ms-duplex-checked="editShowRank">
|
<label>是否可见</label>
|
||||||
<small>开放排名</small>
|
<label><input type="checkbox" ms-duplex-checked="editVisible">
|
||||||
|
<small> 可见</small>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<label>是否公开提交记录</label>
|
<label>公开提交记录</label>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="text"><input type="checkbox" ms-duplex-checked="editShowSubmission">
|
<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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -172,7 +170,7 @@
|
|||||||
<tr ms-repeat="editProblemList">
|
<tr ms-repeat="editProblemList">
|
||||||
<td>{{ el.sort_index }}</td>
|
<td>{{ el.sort_index }}</td>
|
||||||
<td>{{ el.title }}</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 ms-text="el.visible?'可见':'不可见'"></td>
|
||||||
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss") }}</td>
|
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss") }}</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -180,6 +178,8 @@
|
|||||||
ms-click="showProblemEditPage(el)">编辑</a>
|
ms-click="showProblemEditPage(el)">编辑</a>
|
||||||
<a href="javascript:void(0)" class="btn-sm btn-info"
|
<a href="javascript:void(0)" class="btn-sm btn-info"
|
||||||
ms-click="showSubmissionPage(el)">提交</a>
|
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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="form-group"><label>时间限制(ms)</label>
|
<div class="form-group"><label>时间限制(ms)</label>
|
||||||
<input type="number" name="timeLimit" class="form-control" ms-duplex="timeLimit"
|
<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 class="help-block with-errors"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,8 +31,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="form-group"><label>难度</label>
|
<div class="form-group"><label>难度</label>
|
||||||
<input type="number" name="difficulty" class="form-control" ms-duplex="difficulty"
|
<select name="difficulty" class="form-control" ms-duplex="difficulty"
|
||||||
data-error="请输入难度(保证是一个合法整数)" required>
|
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 class="help-block with-errors"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="form-group"><label>时间限制(ms)</label>
|
<div class="form-group"><label>时间限制(ms)</label>
|
||||||
<input type="number" name="timeLimit" class="form-control" ms-duplex="timeLimit"
|
<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 class="help-block with-errors"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -37,8 +37,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="form-group"><label>难度</label>
|
<div class="form-group"><label>难度</label>
|
||||||
<input type="number" name="difficulty" class="form-control" ms-duplex="difficulty"
|
<select name="difficulty" class="form-control" ms-duplex="difficulty"
|
||||||
data-error="请输入难度(保证是一个合法整数)" required>
|
data-error="请选择难度" required>
|
||||||
|
<option value="1">简单</option>
|
||||||
|
<option value="2">中等</option>
|
||||||
|
<option value="3">难</option>
|
||||||
|
</select>
|
||||||
<div class="help-block with-errors"></div>
|
<div class="help-block with-errors"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user