Merge branch 'sxw-dev' of https://git.coding.net/virusdefender/qduoj into sxw-dev

This commit is contained in:
esp 2015-08-11 15:09:44 +08:00
commit be8821bf49
110 changed files with 11082 additions and 457 deletions

2
.gitignore vendored
View File

@ -56,3 +56,5 @@ db.sqlite3
.DS_Store .DS_Store
log/ log/
release/ release/
tmp/
test_case/

32
account/decorators.py Normal file
View File

@ -0,0 +1,32 @@
# coding=utf-8
from django.http import HttpResponse
from django.shortcuts import render
from utils.shortcuts import error_response
from .models import User
def login_required(func):
def check(*args, **kwargs):
# 在class based views 里面args 有两个元素一个是self, 第二个才是request
# 在function based views 里面args 只有request 一个参数
request = args[-1]
if request.user.is_authenticated():
return func(*args, **kwargs)
if request.is_ajax():
return error_response(u"请先登录")
else:
return render(request, "utils/error.html", {"error": u"请先登录"})
return check
def admin_required(func):
def check(*args, **kwargs):
request = args[-1]
if request.user.is_authenticated() and request.user.admin_type:
return func(*args, **kwargs)
if request.is_ajax():
return error_response(u"需要管理员权限")
else:
return render(request, "utils/error.html", {"error": "需要管理员权限"})
return check

View File

@ -2,7 +2,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
import django.db.models.deletion import account.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -18,10 +18,17 @@ class Migration(migrations.Migration):
('password', models.CharField(max_length=128, verbose_name='password')), ('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)), ('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)),
('username', models.CharField(unique=True, max_length=30)), ('username', models.CharField(unique=True, max_length=30)),
('real_name', models.CharField(max_length=30, null=True, blank=True)),
('email', models.EmailField(max_length=254, null=True, blank=True)),
('create_time', models.DateTimeField(auto_now_add=True)),
('admin_type', models.IntegerField(default=0)),
], ],
options={ options={
'db_table': 'user', 'db_table': 'user',
}, },
managers=[
('objects', account.models.UserManager()),
],
), ),
migrations.CreateModel( migrations.CreateModel(
name='AdminGroup', name='AdminGroup',
@ -29,9 +36,4 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
], ],
), ),
migrations.AddField(
model_name='user',
name='admin_group',
field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, to='account.AdminGroup', null=True),
),
] ]

View File

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import account.models
class Migration(migrations.Migration):
dependencies = [
('account', '0001_initial'),
]
operations = [
migrations.AlterModelManagers(
name='user',
managers=[
(b'objects', account.models.UserManager()),
],
),
migrations.AddField(
model_name='user',
name='real_name',
field=models.CharField(max_length=30, null=True, blank=True),
),
]

View File

@ -14,12 +14,22 @@ class UserManager(models.Manager):
return self.get(**{self.model.USERNAME_FIELD: username}) return self.get(**{self.model.USERNAME_FIELD: username})
REGULAR_USER = 0
ADMIN = 1
SUPER_ADMIN = 2
class User(AbstractBaseUser): class User(AbstractBaseUser):
# 用户名 # 用户名
username = models.CharField(max_length=30, unique=True) username = models.CharField(max_length=30, unique=True)
# 真实姓名 # 真实姓名
real_name = models.CharField(max_length=30, blank=True, null=True) real_name = models.CharField(max_length=30, blank=True, null=True)
admin_group = models.ForeignKey(AdminGroup, null=True, on_delete=models.SET_NULL) # 用户邮箱
email = models.EmailField(max_length=254, blank=True, null=True)
# 用户注册时间
create_time = models.DateTimeField(auto_now_add=True)
# 0代表不是管理员 1是普通管理员 2是超级管理员
admin_type = models.IntegerField(default=0)
USERNAME_FIELD = 'username' USERNAME_FIELD = 'username'
REQUIRED_FIELDS = [] REQUIRED_FIELDS = []

View File

@ -1,6 +1,8 @@
# coding=utf-8 # coding=utf-8
from rest_framework import serializers from rest_framework import serializers
from .models import User
class UserLoginSerializer(serializers.Serializer): class UserLoginSerializer(serializers.Serializer):
username = serializers.CharField(max_length=30) username = serializers.CharField(max_length=30)
@ -11,10 +13,15 @@ class UsernameCheckSerializer(serializers.Serializer):
username = serializers.CharField(max_length=30) username = serializers.CharField(max_length=30)
class EmailCheckSerializer(serializers.Serializer):
email = serializers.EmailField(max_length=254)
class UserRegisterSerializer(serializers.Serializer): class UserRegisterSerializer(serializers.Serializer):
username = serializers.CharField(max_length=30) username = serializers.CharField(max_length=30)
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)
class UserChangePasswordSerializer(serializers.Serializer): class UserChangePasswordSerializer(serializers.Serializer):
@ -22,3 +29,18 @@ class UserChangePasswordSerializer(serializers.Serializer):
old_password = serializers.CharField(max_length=30, min_length=6) old_password = serializers.CharField(max_length=30, min_length=6)
new_password = serializers.CharField(max_length=30, min_length=6) new_password = serializers.CharField(max_length=30, min_length=6)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
exclude = ["password"]
class EditUserSerializer(serializers.Serializer):
id = serializers.IntegerField()
username = serializers.CharField(max_length=30)
real_name = serializers.CharField(max_length=30)
password = serializers.CharField(max_length=30, min_length=6, required=False, default=None)
email = serializers.EmailField(max_length=254)
admin_type = serializers.IntegerField(default=0)

18
account/test_urls.py Normal file
View File

@ -0,0 +1,18 @@
# coding=utf-8
from django.conf.urls import include, url
from .tests import (LoginRequiredCBVTestWithArgs, LoginRequiredCBVTestWithoutArgs,
AdminRequiredCBVTestWithArgs, AdminRequiredCBVTestWithoutArgs)
urlpatterns = [
url(r'^login_required_test/fbv/1/$', "account.tests.login_required_FBV_test_without_args"),
url(r'^login_required_test/fbv/(?P<problem_id>\d+)/$', "account.tests.login_required_FBC_test_with_args"),
url(r'^login_required_test/cbv/1/$', LoginRequiredCBVTestWithoutArgs.as_view()),
url(r'^login_required_test/cbv/(?P<problem_id>\d+)/$', LoginRequiredCBVTestWithArgs.as_view()),
url(r'^admin_required_test/fbv/1/$', "account.tests.admin_required_FBV_test_without_args"),
url(r'^admin_required_test/fbv/(?P<problem_id>\d+)/$', "account.tests.admin_required_FBC_test_with_args"),
url(r'^admin_required_test/cbv/1/$', AdminRequiredCBVTestWithoutArgs.as_view()),
url(r'^admin_required_test/cbv/(?P<problem_id>\d+)/$', AdminRequiredCBVTestWithArgs.as_view()),
]

View File

@ -1,10 +1,17 @@
# coding=utf-8 # coding=utf-8
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 django.contrib import auth
from rest_framework.test import APITestCase, APIClient from rest_framework.test import APITestCase, APIClient
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import User from .models import User, SUPER_ADMIN
from .decorators import login_required, admin_required
class UserLoginTest(TestCase): class UserLoginTest(TestCase):
@ -57,6 +64,25 @@ class UsernameCheckTest(APITestCase):
self.assertEqual(response.data, {"code": 0, "data": False}) self.assertEqual(response.data, {"code": 0, "data": False})
class EmailCheckTest(APITestCase):
def setUp(self):
self.client = APIClient()
self.url = reverse("email_check_api")
User.objects.create(email="11@qq.com")
def test_invalid_data(self):
response = self.client.post(self.url, data={"email000": "11@qq.com"})
self.assertEqual(response.data["code"], 1)
def test_email_exists(self):
response = self.client.post(self.url, data={"email": "11@qq.com"})
self.assertEqual(response.data, {"code": 0, "data": True})
def test_email_does_not_exist(self):
response = self.client.post(self.url, data={"email": "33@qq.com"})
self.assertEqual(response.data, {"code": 0, "data": False})
class UserRegisterAPITest(APITestCase): class UserRegisterAPITest(APITestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
@ -68,22 +94,35 @@ class UserRegisterAPITest(APITestCase):
self.assertEqual(response.data["code"], 1) self.assertEqual(response.data["code"], 1)
def test_short_password(self): def test_short_password(self):
data = {"username": "test", "real_name": "TT", "password": "qq"} data = {"username": "test", "real_name": "TT", "password": "qq", "email": "6060@qq.com"}
response = self.client.post(self.url, data=data) response = self.client.post(self.url, data=data)
self.assertEqual(response.data["code"], 1) self.assertEqual(response.data["code"], 1)
def test_same_username(self): def test_same_username(self):
User.objects.create(username="aa", real_name="ww") User.objects.create(username="aa")
data = {"username": "aa", "real_name": "ww", "password": "zzzzzzz"} data = {"username": "aa", "real_name": "ww", "password": "zzzzzzz", "email": "6060@qq.com"}
response = self.client.post(self.url, data=data) response = self.client.post(self.url, data=data)
self.assertEqual(response.data, {"code": 1, "data": u"用户名已存在"}) self.assertEqual(response.data, {"code": 1, "data": u"用户名已存在"})
def test_same_email(self):
User.objects.create(username="bb", email="8080@qq.com")
data = {"username": "aa", "real_name": "ww", "password": "zzzzzzz", "email": "8080@qq.com"}
response = self.client.post(self.url, data=data)
self.assertEqual(response.data, {"code": 1, "data": u"该邮箱已被注册,请换其他邮箱进行注册"})
def test_success_email(self):
data = {"username": "cc", "real_name": "dd", "password": "xxxxxx", "email": "9090@qq.com"}
response = self.client.post(self.url, data=data)
self.assertEqual(response.data, {"code": 0, "data": u"注册成功!"})
class UserChangePasswordAPITest(APITestCase): class UserChangePasswordAPITest(APITestCase):
def setUp(self): def setUp(self):
self.client = APIClient() self.client = APIClient()
self.url = reverse("user_change_password_api") self.url = reverse("user_change_password_api")
User.objects.create(username="test", password="aaabbb") user = User.objects.create(username="test")
user.set_password("aaabbb")
user.save()
def test_error_old_password(self): def test_error_old_password(self):
data = {"username": "test", "old_password": "aaaccc", "new_password": "aaaddd"} data = {"username": "test", "old_password": "aaaccc", "new_password": "aaaddd"}
@ -99,3 +138,225 @@ class UserChangePasswordAPITest(APITestCase):
data = {"username": "test1", "old_password": "aaabbb", "new_password": "aaaddd"} data = {"username": "test1", "old_password": "aaabbb", "new_password": "aaaddd"}
response = self.client.post(self.url, data=data) response = self.client.post(self.url, data=data)
self.assertEqual(response.data["code"], 1) self.assertEqual(response.data["code"], 1)
def test_success_change_password(self):
data = {"username": "test", "old_password": "aaabbb", "new_password": "aaaccc"}
response = self.client.post(self.url, data=data)
self.assertEqual(response.data, {"code": 0, "data": u"用户密码修改成功!"})
class UserAPITest(APITestCase):
def setUp(self):
self.client = APIClient()
self.url = reverse("user_list_api")
user = User.objects.create(username="testx", real_name="xx", admin_type=SUPER_ADMIN)
user.set_password("testxx")
user.save()
def test_success_get_data(self):
self.client.login(username="testx", password="testxx")
self.assertEqual(self.client.get(self.url).data["code"], 0)
def test_error_admin_type(self):
self.client.login(username="testx", password="testxx")
response = self.client.get(self.url + "?admin_type=error")
self.assertEqual(response.data, {"code": 1, "data": u"参数错误"})
def test_query_by_keyword(self):
self.client.login(username="testx", password="testxx")
user1 = User.objects.create(username="test1", real_name="aa")
user1.set_password("testaa")
user1.save()
user2 = User.objects.create(username="test2", real_name="bb")
user2.set_password("testbb")
user2.save()
user3 = User.objects.create(username="test3", real_name="cc")
user3.set_password("testcc")
user3.save()
response = self.client.get(self.url + "?keyword=test1")
self.assertEqual(response.data["code"], 0)
class UserAdminAPITest(APITestCase):
def setUp(self):
self.client = APIClient()
self.url = reverse("user_admin_api")
user = User.objects.create(username="test", admin_type=SUPER_ADMIN)
user.set_password("testaa")
user.save()
def test_put_invalid_data(self):
self.client.login(username="test", password="testaa")
data = {"username": "test", "password": "testaa", "email": "60@qq.com", "admin_type": "2"}
response = self.client.put(self.url, data=data)
self.assertEqual(response.data["code"], 1)
def test_user_does_not_exist(self):
self.client.login(username="test", password="testaa")
data = {"id": 2, "username": "test0", "real_name": "test00",
"password": "testaa","email": "60@qq.com", "admin_type": "2"}
response = self.client.put(self.url, data=data)
self.assertEqual(response.data, {"code": 1, "data": u"该用户不存在!"})
def test_success_user_edit_not_password(self):
self.client.login(username="test", password="testaa")
data = {"id": 1, "username": "test0", "real_name": "test00",
"email": "60@qq.com", "admin_type": "2"}
response = self.client.put(self.url, data=data)
self.assertEqual(response.data["code"], 0)
def test_success_user_edit_change_password(self):
self.client.login(username="test", password="testaa")
data = {"id": 1, "username": "test0", "real_name": "test00", "password": "111111",
"email": "60@qq.com", "admin_type": "2"}
response = self.client.put(self.url, data=data)
self.assertEqual(response.data["code"], 0)
self.assertIsNotNone(auth.authenticate(username="test0", password="111111"))
@login_required
def login_required_FBV_test_without_args(request):
return HttpResponse("function based view test1")
@login_required
def login_required_FBC_test_with_args(request, problem_id):
return HttpResponse(problem_id)
class LoginRequiredCBVTestWithoutArgs(APIView):
@login_required
def get(self, request):
return HttpResponse("class based view login required test1")
class LoginRequiredCBVTestWithArgs(APIView):
@login_required
def get(self, request, problem_id):
return HttpResponse(problem_id)
class LoginRequiredDecoratorTest(TestCase):
urls = 'account.test_urls'
def setUp(self):
self.client = Client()
user = User.objects.create(username="test")
user.set_password("test")
user.save()
def test_fbv_without_args(self):
# 没登陆
response = self.client.get("/login_required_test/fbv/1/")
self.assertTemplateUsed(response, "utils/error.html")
# 登陆后
self.client.login(username="test", password="test")
response = self.client.get("/login_required_test/fbv/1/")
self.assertEqual(response.content, "function based view test1")
def test_fbv_with_args(self):
# 没登陆
response = self.client.get("/login_required_test/fbv/1024/")
self.assertTemplateUsed(response, "utils/error.html")
# 登陆后
self.client.login(username="test", password="test")
response = self.client.get("/login_required_test/fbv/1024/")
self.assertEqual(response.content, "1024")
def test_cbv_without_args(self):
# 没登陆
response = self.client.get("/login_required_test/cbv/1/")
self.assertTemplateUsed(response, "utils/error.html")
# 登陆后
self.client.login(username="test", password="test")
response = self.client.get("/login_required_test/cbv/1/")
self.assertEqual(response.content, "class based view login required test1")
def test_cbv_with_args(self):
# 没登陆
response = self.client.get("/login_required_test/cbv/1024/", HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(json.loads(response.content), {"code": 1, "data": u"请先登录"})
# 登陆后
self.client.login(username="test", password="test")
response = self.client.get("/login_required_test/cbv/1024/")
self.assertEqual(response.content, "1024")
@admin_required
def admin_required_FBV_test_without_args(request):
return HttpResponse("function based view test1")
@admin_required
def admin_required_FBC_test_with_args(request, problem_id):
return HttpResponse(problem_id)
class AdminRequiredCBVTestWithoutArgs(APIView):
@admin_required
def get(self, request):
return HttpResponse("class based view login required test1")
class AdminRequiredCBVTestWithArgs(APIView):
@admin_required
def get(self, request, problem_id):
return HttpResponse(problem_id)
class AdminRequiredDecoratorTest(TestCase):
urls = 'account.test_urls'
def setUp(self):
self.client = Client()
user = User.objects.create(username="test")
user.admin_type = 1
user.set_password("test")
user.save()
def test_fbv_without_args(self):
# 没登陆
response = self.client.get("/admin_required_test/fbv/1/")
self.assertTemplateUsed(response, "utils/error.html")
# 登陆后
self.client.login(username="test", password="test")
response = self.client.get("/admin_required_test/fbv/1/")
self.assertEqual(response.content, "function based view test1")
def test_fbv_with_args(self):
# 没登陆
response = self.client.get("/admin_required_test/fbv/1024/")
self.assertTemplateUsed(response, "utils/error.html")
# 登陆后
self.client.login(username="test", password="test")
response = self.client.get("/admin_required_test/fbv/1024/")
self.assertEqual(response.content, "1024")
def test_cbv_without_args(self):
# 没登陆
response = self.client.get("/admin_required_test/cbv/1/")
self.assertTemplateUsed(response, "utils/error.html")
# 登陆后
self.client.login(username="test", password="test")
response = self.client.get("/admin_required_test/cbv/1/")
self.assertEqual(response.content, "class based view login required test1")
def test_cbv_with_args(self):
# 没登陆
response = self.client.get("/admin_required_test/cbv/1024/", HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(json.loads(response.content), {"code": 1, "data": u"需要管理员权限"})
# 登陆后
self.client.login(username="test", password="test")
response = self.client.get("/admin_required_test/cbv/1024/")
self.assertEqual(response.content, "1024")

View File

@ -1,13 +1,15 @@
# coding=utf-8 # coding=utf-8
from django.contrib import auth from django.contrib import auth
from django.shortcuts import render from django.shortcuts import render
from django.db.models import Q
from rest_framework.views import APIView from rest_framework.views import APIView
from utils.shortcuts import serializer_invalid_response, error_response, success_response from utils.shortcuts import serializer_invalid_response, error_response, success_response, paginate
from .models import User from .models import User
from .serializers import UserLoginSerializer, UsernameCheckSerializer, UserRegisterSerializer, \ from .serializers import UserLoginSerializer, UsernameCheckSerializer, UserRegisterSerializer, \
UserChangePasswordSerializer UserChangePasswordSerializer, EmailCheckSerializer, UserSerializer, EditUserSerializer
class UserLoginAPIView(APIView): class UserLoginAPIView(APIView):
@ -17,7 +19,7 @@ class UserLoginAPIView(APIView):
--- ---
request_serializer: UserLoginSerializer request_serializer: UserLoginSerializer
""" """
serializer = UserLoginSerializer(data=request.DATA) serializer = UserLoginSerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
data = serializer.data data = serializer.data
user = auth.authenticate(username=data["username"], password=data["password"]) user = auth.authenticate(username=data["username"], password=data["password"])
@ -38,14 +40,20 @@ class UserRegisterAPIView(APIView):
--- ---
request_serializer: UserRegisterSerializer request_serializer: UserRegisterSerializer
""" """
serializer = UserRegisterSerializer(data=request.DATA) serializer = UserRegisterSerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
data = serializer.data data = serializer.data
try: try:
User.objects.get(username=data["username"]) User.objects.get(username=data["username"])
return error_response(u"用户名已存在") return error_response(u"用户名已存在")
except User.DoesNotExist: except User.DoesNotExist:
user = User.objects.create(username=data["username"], real_name=data["real_name"]) pass
try:
User.objects.get(email=data["email"])
return error_response(u"该邮箱已被注册,请换其他邮箱进行注册")
except User.DoesNotExist:
user = User.objects.create(username=data["username"], real_name=data["real_name"],
email=data["email"])
user.set_password(data["password"]) user.set_password(data["password"])
user.save() user.save()
return success_response(u"注册成功!") return success_response(u"注册成功!")
@ -60,7 +68,7 @@ class UserChangePasswordAPIView(APIView):
--- ---
request_serializer: UserChangePasswordSerializer request_serializer: UserChangePasswordSerializer
""" """
serializer = UserChangePasswordSerializer(data=request.DATA) serializer = UserChangePasswordSerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
data = serializer.data data = serializer.data
user = auth.authenticate(username=data["username"], password=data["old_password"]) user = auth.authenticate(username=data["username"], password=data["old_password"])
@ -81,7 +89,7 @@ class UsernameCheckAPIView(APIView):
--- ---
request_serializer: UsernameCheckSerializer request_serializer: UsernameCheckSerializer
""" """
serializer = UsernameCheckSerializer(data=request.DATA) serializer = UsernameCheckSerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
try: try:
User.objects.get(username=serializer.data["username"]) User.objects.get(username=serializer.data["username"])
@ -90,3 +98,70 @@ class UsernameCheckAPIView(APIView):
return success_response(False) return success_response(False)
else: else:
return serializer_invalid_response(serializer) return serializer_invalid_response(serializer)
class EmailCheckAPIView(APIView):
def post(self, request):
"""
检测邮箱是否存在存在返回True不存在返回False
---
request_serializer: EmailCheckSerializer
"""
serializer = EmailCheckSerializer(data=request.data)
if serializer.is_valid():
try:
User.objects.get(email=serializer.data["email"])
return success_response(True)
except User.DoesNotExist:
return success_response(False)
else:
return serializer_invalid_response(serializer)
class UserAPIView(APIView):
def get(self, request):
"""
用户分页json api接口
---
response_serializer: UserSerializer
"""
user = User.objects.all().order_by("-create_time")
admin_type = request.GET.get("admin_type", None)
if admin_type:
try:
user = user.filter(admin_type__gte=int(admin_type))
except ValueError:
return error_response(u"参数错误")
keyword = request.GET.get("keyword", None)
if keyword:
user = user.filter(Q(username__contains=keyword) |
Q(real_name__contains=keyword) |
Q(email__contains=keyword))
return paginate(request, user, UserSerializer)
class UserAdminAPIView(APIView):
def put(self, request):
"""
用户编辑json api接口
---
request_serializer: EditUserSerializer
response_serializer: UserSerializer
"""
serializer = EditUserSerializer(data=request.data)
if serializer.is_valid():
data = serializer.data
try:
user = User.objects.get(id=data["id"])
except User.DoesNotExist:
return error_response(u"该用户不存在!")
user.username = data["username"]
user.real_name = data["real_name"]
user.email = data["email"]
user.admin_type = data["admin_type"]
if data["password"]:
user.set_password(data["password"])
user.save()
return success_response(UserSerializer(user).data)
else:
return serializer_invalid_response(serializer)

16
admin/middleware.py Normal file
View File

@ -0,0 +1,16 @@
# coding=utf-8
import json
from django.http import HttpResponse, HttpResponseRedirect
class AdminRequiredMiddleware(object):
def process_request(self, request):
path = request.path_info
if path.startswith("/admin/") or path.startswith("/api/admin/"):
if not(request.user.is_authenticated() and request.user.admin_type):
if request.is_ajax():
return HttpResponse(json.dumps({"code": 1, "data": u"请先登录"}),
content_type="application/json")
else:
return HttpResponseRedirect("/login/")

10
admin/test_urls.py Normal file
View File

@ -0,0 +1,10 @@
# coding=utf-8
from django.conf.urls import include, url
from django.views.generic import TemplateView
urlpatterns = [
url(r'^admin/$', TemplateView.as_view(template_name="admin/admin.html"), name="admin_spa_page"),
url(r'^api/admin/test/$', "admin.tests.middleware_test_func"),
url(r'^login/$', TemplateView.as_view(template_name="oj/account/login.html"), name="user_login_page"),
]

View File

@ -0,0 +1,68 @@
# coding=utf-8
import json
from django.test import TestCase, Client
from django.core.urlresolvers import reverse
from django.http import HttpResponse
from account.models import User
def middleware_test_func(request):
return HttpResponse(json.dumps({"code": 0}))
class AdminRequiredMidlewareTest(TestCase):
urls = "admin.test_urls"
def setUp(self):
admin_user = User.objects.create(username="test", admin_type=0)
admin_user.set_password("test")
admin_user.save()
admin_user = User.objects.create(username="test1", admin_type=1)
admin_user.set_password("test")
admin_user.save()
super_admin_user = User.objects.create(username="test2", admin_type=2)
super_admin_user.set_password("test")
super_admin_user.save()
self.client = Client()
def test_need_admin_login(self):
url = "/admin/"
response = self.client.get(url)
self.assertRedirects(response, "/login/")
self.client.login(username="test", password="test")
response = self.client.get(url)
self.assertRedirects(response, "/login/")
self.client.logout()
self.client.login(username="test1", password="test")
response = self.client.get(url)
self.assertTemplateUsed(response, "admin/admin.html")
self.client.logout()
self.client.login(username="test2", password="test")
response = self.client.get(url)
self.assertTemplateUsed(response, "admin/admin.html")
def test_need_admin_login_ajax(self):
url = "/api/admin/test/"
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(json.loads(response.content), {"code": 1, "data": u"请先登录"})
self.client.login(username="test", password="test")
rresponse = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(json.loads(response.content), {"code": 1, "data": u"请先登录"})
self.client.logout()
self.client.login(username="test1", password="test")
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(json.loads(response.content)["code"], 0)
self.client.logout()
self.client.login(username="test2", password="test")
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(json.loads(response.content)["code"], 0)

View File

@ -1,3 +1,14 @@
from django.shortcuts import render # coding=utf-8
from django.conf import settings
from django.http import HttpResponse, Http404
# Create your views here. from rest_framework.views import APIView
class AdminTemplateView(APIView):
def get(self, request, template_dir, template_name):
path = settings.TEMPLATE_DIRS[0] + "/admin/" + template_dir + "/" + template_name + ".html"
try:
return HttpResponse(open(path).read(), content_type="text/html")
except IOError:
return HttpResponse(u"模板不存在", content_type="text/html")

0
announcement/__init__.py Normal file
View File

3
announcement/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Announcement',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('title', models.CharField(max_length=50)),
('content', models.TextField()),
('create_time', models.DateTimeField(auto_now_add=True)),
('last_update_time', models.DateTimeField(auto_now=True)),
('visible', models.BooleanField(default=True)),
('created_by', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'announcement',
},
),
]

View File

22
announcement/models.py Normal file
View File

@ -0,0 +1,22 @@
# coding=utf-8
from django.db import models
from account.models import User
class Announcement(models.Model):
# 标题
title = models.CharField(max_length=50)
# 公告的内容 HTML 格式
content = models.TextField()
# 创建时间
create_time = models.DateTimeField(auto_now_add=True)
# 这个公告是谁创建的
created_by = models.ForeignKey(User)
# 最后更新时间
last_update_time = models.DateTimeField(auto_now=True)
# 是否可见 false的话相当于删除
visible = models.BooleanField(default=True)
class Meta:
db_table = "announcement"

View File

@ -0,0 +1,30 @@
# coding=utf-8
from rest_framework import serializers
from account.models import User
from .models import Announcement
class CreateAnnouncementSerializer(serializers.Serializer):
title = serializers.CharField(max_length=50)
content = serializers.CharField(max_length=10000)
class AnnouncementSerializer(serializers.ModelSerializer):
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["username"]
created_by = UserSerializer()
class Meta:
model = Announcement
class EditAnnouncementSerializer(serializers.Serializer):
id = serializers.IntegerField()
title = serializers.CharField(max_length=50)
content = serializers.CharField(max_length=10000)
visible = serializers.BooleanField()

89
announcement/tests.py Normal file
View File

@ -0,0 +1,89 @@
# coding=utf-8
from django.core.urlresolvers import reverse
from rest_framework.test import APITestCase, APIClient
from account.models import User
from announcement.models import Announcement
from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN
class AnnouncementAdminAPITest(APITestCase):
def setUp(self):
self.client = APIClient()
self.url = reverse("announcement_admin_api")
user = User.objects.create(username="test", admin_type=SUPER_ADMIN)
user.set_password("testaa")
user.save()
# 以下是发布公告的测试
def test_invalid_format(self):
self.client.login(username="test", password="testaa")
data = {"title": "test1"}
response = self.client.post(self.url, data=data)
self.assertEqual(response.data["code"], 1)
def test_success_announcement(self):
self.client.login(username="test", password="testaa")
data = {"title": "title0", "content": "content0"}
response = self.client.post(self.url, data=data)
self.assertEqual(response.data, {"code": 0, "data": u"公告发布成功!"})
def test_post_invalid_data(self):
self.client.login(username="test", password="testaa")
data = {"title": "test"}
response = self.client.post(self.url, data=data)
self.assertEqual(response.data["code"], 1)
# 以下是编辑公告的测试
def test_put_invalid_data(self):
self.client.login(username="test", password="testaa")
data = {"title": "test0", "content": "test0", "visible": "True"}
response = self.client.put(self.url, data=data)
self.assertEqual(response.data["code"], 1)
def test_announcement_does_not_exist(self):
self.client.login(username="test", password="testaa")
announcement = Announcement.objects.create(title="aa",
content="AA",
created_by=User.objects.get(username="test"))
data = {"id": announcement.id + 1, "title": "11", "content": "22", "visible": True}
response = self.client.put(self.url, data=data)
self.assertEqual(response.data, {"code": 1, "data": u"该公告不存在!"})
def test_success_edit_announcement(self):
self.client.login(username="test", password="testaa")
announcement = Announcement.objects.create(title="bb",
content="BB",
created_by=User.objects.get(username="test"))
data = {"id": announcement.id, "title": "11", "content": "22", "visible": True}
response = self.client.put(self.url, data=data)
self.assertEqual(response.data["code"], 0)
class AnnouncementAPITest(APITestCase):
def setUp(self):
self.client = APIClient()
self.url = reverse("announcement_list_api")
user = User.objects.create(username="test")
user.set_password("testaa")
user.save()
def test_success_get_data(self):
self.assertEqual(self.client.get(self.url).data["code"], 0)
def test_keyword_announcement(self):
Announcement.objects.create(title="aa",
content="AA",
created_by=User.objects.get(username="test"),
visible=True)
Announcement.objects.create(title="bb",
content="BB",
created_by=User.objects.get(username="test"),
visible=False)
response = self.client.get(self.url + "?visible=true")
self.assertEqual(response.data["code"], 0)
for item in response.data["data"]:
self.assertEqual(item["visible"], True)

72
announcement/views.py Normal file
View File

@ -0,0 +1,72 @@
# coding=utf-8
from rest_framework.views import APIView
from django.shortcuts import render
from utils.shortcuts import serializer_invalid_response, error_response, success_response
from utils.shortcuts import paginate
from .models import Announcement
from .serializers import (CreateAnnouncementSerializer, AnnouncementSerializer,
EditAnnouncementSerializer)
def announcement_page(request, announcement_id):
try:
announcement = Announcement.objects.get(id=announcement_id, visible=True)
except Announcement.DoesNotExist:
return render(request, "utils/error.html", {"error": u"模板不存在"})
return render(request, "oj/announcement/announcement.html", {"announcement": announcement})
class AnnouncementAdminAPIView(APIView):
def post(self, request):
"""
公告发布json api接口
---
request_serializer: CreateAnnouncementSerializer
"""
serializer = CreateAnnouncementSerializer(data=request.data)
if serializer.is_valid():
data = serializer.data
Announcement.objects.create(title=data["title"],
content=data["content"],
created_by=request.user)
return success_response(u"公告发布成功!")
else:
return serializer_invalid_response(serializer)
def put(self, request):
"""
公告编辑json api接口
---
request_serializer: EditAnnouncementSerializer
response_serializer: AnnouncementSerializer
"""
serializer = EditAnnouncementSerializer(data=request.data)
if serializer.is_valid():
data = serializer.data
try:
announcement = Announcement.objects.get(id=data["id"])
except Announcement.DoesNotExist:
return error_response(u"该公告不存在!")
announcement.title = data["title"]
announcement.content = data["content"]
announcement.visible = data["visible"]
announcement.save()
return success_response(AnnouncementSerializer(announcement).data)
else:
return serializer_invalid_response(serializer)
class AnnouncementAPIView(APIView):
def get(self, request):
"""
公告分页json api接口
---
response_serializer: AnnouncementSerializer
"""
announcement = Announcement.objects.all().order_by("-last_update_time")
visible = request.GET.get("visible", None)
if visible:
announcement = announcement.filter(visible=(visible == "true"))
return paginate(request, announcement, AnnouncementSerializer)

View File

@ -8,12 +8,44 @@ from problem.models import AbstractProblem
class Contest(models.Model): class Contest(models.Model):
title = models.CharField(max_length=40) title = models.CharField(max_length=40)
description = models.TextField() description = models.TextField()
is_public = models.BooleanField() # 比赛模式 现在有 acm 模式,按照 ac 题目数量得分模式,
# 按照 ac 的题目的总分得分模式和按照通过的测试用例总分得分模式等
mode = models.IntegerField()
# 是否显示排名结果
show_rank = models.BooleanField()
# 如果这一项不为空,那这就不是公开赛,需要密码才能进入
password = models.CharField(max_length=30, blank=True, null=True) password = models.CharField(max_length=30, blank=True, null=True)
# 开始时间
start_time = models.DateTimeField() start_time = models.DateTimeField()
# 结束时间
end_time = models.DateTimeField() end_time = models.DateTimeField()
# 创建时间
create_time = models.DateTimeField(auto_now_add=True)
# 最后修改时间
last_updated_time = models.DateTimeField(auto_now=True)
created_by = models.ForeignKey(User) created_by = models.ForeignKey(User)
class Meta:
db_table = "contest"
class ContestProblem(AbstractProblem): class ContestProblem(AbstractProblem):
contest = models.ForeignKey(Contest) contest = models.ForeignKey(Contest)
# 比如A B 或者1 2 或者 a b 将按照这个排序
sort_index = models.CharField(max_length=30)
class Meta:
db_table = "contest_problem"
class ContestProblemTestCase(models.Model):
"""
如果比赛是按照通过的测试用例总分计算的话就需要这个model 记录每个测试用例的分数
"""
# 测试用例的id 这个还在测试用例的配置文件里面有对应
id = models.CharField(max_length=40, primary_key=True, db_index=True)
problem = models.ForeignKey(ContestProblem)
score = models.IntegerField()
class Meta:
db_table = "contest_problem_test_case"

0
group/__init__.py Normal file
View File

3
group/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Group',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=30)),
('description', models.TextField()),
('create_time', models.DateTimeField(auto_now_add=True)),
('join_group_setting', models.IntegerField()),
('visible', models.BooleanField(default=True)),
('admin', models.ForeignKey(related_name='my_groups', to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'group',
},
),
migrations.CreateModel(
name='JoinGroupRequest',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('message', models.TextField()),
('create_time', models.DateTimeField(auto_now_add=True)),
('status', models.BooleanField(default=False)),
('group', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
('user', models.ForeignKey(related_name='my_join_group_requests', to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'join_group_request',
},
),
migrations.CreateModel(
name='UserGroupRelation',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('join_time', models.DateTimeField(auto_now_add=True)),
('group', models.ForeignKey(to='group.Group')),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'user_group_relation',
},
),
migrations.AddField(
model_name='group',
name='members',
field=models.ManyToManyField(to=settings.AUTH_USER_MODEL, through='group.UserGroupRelation'),
),
]

View File

40
group/models.py Normal file
View File

@ -0,0 +1,40 @@
# coding=utf-8
from django.db import models
from account.models import User
class Group(models.Model):
name = models.CharField(max_length=30)
description = models.TextField()
create_time = models.DateTimeField(auto_now_add=True)
admin = models.ForeignKey(User, related_name="my_groups")
# 0是公开 1是需要申请后加入 2是不允许任何人加入
join_group_setting = models.IntegerField()
members = models.ManyToManyField(User, through="UserGroupRelation")
# 解散小组后这一项改为False
visible = models.BooleanField(default=True)
class Meta:
db_table = "group"
class UserGroupRelation(models.Model):
group = models.ForeignKey(Group)
user = models.ForeignKey(User)
join_time = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "user_group_relation"
class JoinGroupRequest(models.Model):
group = models.ForeignKey(User)
user = models.ForeignKey(User, related_name="my_join_group_requests")
message = models.TextField()
create_time = models.DateTimeField(auto_now_add=True)
# 是否处理
status = models.BooleanField(default=False)
class Meta:
db_table = "join_group_request"

42
group/serializers.py Normal file
View File

@ -0,0 +1,42 @@
# coding=utf-8
from rest_framework import serializers
from account.serializers import UserSerializer
from .models import Group, UserGroupRelation
class CreateGroupSerializer(serializers.Serializer):
name = serializers.CharField(max_length=20)
description = serializers.CharField(max_length=300)
join_group_setting = serializers.IntegerField(min_value=0, max_value=2)
class EditGroupSerializer(serializers.Serializer):
group_id = serializers.IntegerField()
name = serializers.CharField(max_length=20)
description = serializers.CharField(max_length=300)
join_group_setting = serializers.IntegerField()
class JoinGroupRequestSerializer(serializers.Serializer):
group_id = serializers.IntegerField()
message = serializers.CharField(max_length=30)
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
exclude = ["members"]
class GroupMemberSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = UserGroupRelation
exclude = ["id", "group"]
class EditGroupMemberSerializer(serializers.Serializer):
group_id = serializers.IntegerField()
members = serializers.ListField(child=serializers.IntegerField())

3
group/tests.py Normal file
View File

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

175
group/views.py Normal file
View File

@ -0,0 +1,175 @@
# coding=utf-8
from django.shortcuts import render
from rest_framework.views import APIView
from utils.shortcuts import error_response, serializer_invalid_response, success_response, paginate
from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN
from account.decorators import login_required
from .models import Group, JoinGroupRequest, UserGroupRelation
from .serializers import (CreateGroupSerializer, EditGroupSerializer,
JoinGroupRequestSerializer, GroupSerializer,
GroupMemberSerializer, EditGroupMemberSerializer)
class GroupAPIViewBase(object):
def get_group(self, request, group_id):
"""
根据group_id查询指定的小组的信息结合判断用户权限
管理员可以查询所有的小组其他用户查询自己创建的自傲组
"""
if request.user.admin_type == SUPER_ADMIN:
group = Group.objects.get(id=group_id, visible=True)
else:
group = Group.objects.get(id=group_id, visible=True, admin=request.user)
return group
def get_groups(self, request):
"""
如果是超级管理员就返回全部的小组
如果是管理员就返回他创建的全部小组
"""
if request.user.admin_type == SUPER_ADMIN:
groups = Group.objects.filter(visible=True)
else:
groups = Group.objects.filter(admin=request.user, visible=True)
return groups
class GroupAdminAPIView(APIView, GroupAPIViewBase):
def post(self, request):
"""
创建小组的api
---
request_serializer: CreateGroupSerializer
response_serializer: GroupSerializer
"""
serializer = CreateGroupSerializer(data=request.data)
if serializer.is_valid():
data = serializer.data
group = Group.objects.create(name=data["name"],
description=data["description"],
join_group_setting=data["join_group_setting"],
admin=request.user)
return success_response(GroupSerializer(group).data)
else:
return serializer_invalid_response(serializer)
def put(self, request):
"""
修改小组信息的api
---
request_serializer: EditGroupSerializer
response_serializer: GroupSerializer
"""
serializer = EditGroupSerializer(data=request.data)
if serializer.is_valid():
data = serializer.data
try:
group = self.get_group(request, data["group_id"])
except Group.DoesNotExist:
return error_response(u"小组不存在")
group.name = data["name"]
group.description = data["description"]
group.join_group_setting = data["join_group_setting"]
group.save()
return success_response(GroupSerializer(group).data)
else:
return serializer_invalid_response(serializer)
def get(self, request):
"""
查询小组列表或者单个小组的信息查询单个小组需要传递group_id参数否则返回全部
---
response_serializer: GroupSerializer
"""
group_id = request.GET.get("group_id", None)
if group_id:
try:
group = self.get_group(request, group_id)
return success_response(GroupSerializer(group).data)
except Group.DoesNotExist:
return error_response(u"小组不存在")
else:
groups = self.get_groups(request)
return paginate(request, groups, GroupSerializer)
class GroupMemberAdminAPIView(APIView, GroupAPIViewBase):
def get(self, request):
"""
查询小组成员的api需要传递group_id参数
---
response_serializer: GroupMemberSerializer
"""
group_id = request.GET.get("group_id", None)
if not group_id:
return error_response(u"参数错误")
try:
group = self.get_group(request, group_id)
except Group.DoesNotExist:
return error_response(u"小组不存在")
return paginate(request, UserGroupRelation.objects.filter(group=group), GroupMemberSerializer)
def put(self, request):
"""
删除小组成员的api接口
---
request_serializer: EditGroupMemberSerializer
"""
serializer = EditGroupMemberSerializer(data=request.data)
if serializer.is_valid():
try:
group = self.get_group(request, serializer.data["group_id"])
except Group.DoesNotExist:
return error_response(u"小组不存在")
user_id_list = serializer.data["members"]
UserGroupRelation.objects.filter(group=group, user__id__in=user_id_list).delete()
return success_response(u"删除成功")
else:
return serializer_invalid_response(serializer)
def join_group(user, group):
return UserGroupRelation.objects.create(user=user, group=group)
class JoinGroupAPIView(APIView):
@login_required
def post(self, request):
"""
加入某个小组的api
---
request_serializer: JoinGroupRequestSerializer
"""
serializer = JoinGroupRequestSerializer(data=request.data)
if serializer.is_valid():
data = serializer.data
try:
group = Group.objects.get(id=data["group_id"])
except Group.DesoNotExist:
return error_response(u"小组不存在")
if group.join_group_setting == 0:
join_group(request.user, group)
return success_response(u"你已经成功的加入该小组")
elif group.join_group_setting == 1:
return success_response(u"申请提交成功,请等待审核")
elif group.join_group_setting == 2:
return error_response(u"该小组不允许任何人加入")
else:
return serializer_invalid_response(serializer)
def get(self, request):
"""
搜素小组的api需要传递keyword参数
---
response_serializer: GroupSerializer
"""
keyword = request.GET.get("keyword", None)
if not keyword:
return error_response(u"参数错误")
# 搜索包含这个关键词的 没有解散的 而且允许加入的小组
groups = Group.objects.filter(name__contains=keyword, visible=True, join_group_setting__lte=2)
return paginate(request, groups, GroupSerializer)

0
install/__init__.py Normal file
View File

3
install/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

3
install/models.py Normal file
View File

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

3
install/tests.py Normal file
View File

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

12
install/views.py Normal file
View File

@ -0,0 +1,12 @@
# coding=utf-8
from django.shortcuts import render
from django.http import HttpResponse
from account.models import User
def install(request):
user = User.objects.create(username="root", admin_type=2)
user.set_password("root")
user.save()
return HttpResponse("success")

View File

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

View File

@ -0,0 +1 @@
celery -A judge.controller worker -l DEBUG

View File

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

View File

@ -0,0 +1,5 @@
# coding=utf-8
from __future__ import absolute_import
from celery import Celery
app = Celery("judge", broker="redis://localhost:6379/0", include=["judge.controller.tasks"])

View File

@ -0,0 +1,8 @@
# coding=utf-8
from __future__ import absolute_import
from judge.controller.celery import app
@app.task
def judge(source_code, language, test_case_id):
print source_code, language, test_case_id

0
judge/judger/__init__.py Normal file
View File

View File

@ -39,7 +39,6 @@ ALLOWED_HOSTS = []
# Application definition # Application definition
INSTALLED_APPS = ( INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
@ -47,6 +46,11 @@ INSTALLED_APPS = (
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'account', 'account',
'announcement',
'utils',
'group',
'problem',
'admin',
'rest_framework', 'rest_framework',
'rest_framework_swagger', 'rest_framework_swagger',
@ -61,6 +65,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'admin.middleware.AdminRequiredMiddleware'
) )
ROOT_URLCONF = 'oj.urls' ROOT_URLCONF = 'oj.urls'

View File

@ -3,19 +3,43 @@ from django.conf.urls import include, url
from django.contrib import admin from django.contrib import admin
from django.views.generic import TemplateView from django.views.generic import TemplateView
from account.views import UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView, UserChangePasswordAPIView from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView,
UserChangePasswordAPIView, EmailCheckAPIView,
UserAPIView, UserAdminAPIView)
from announcement.views import AnnouncementAPIView, AnnouncementAdminAPIView
from group.views import GroupAdminAPIView
from admin.views import AdminTemplateView
from problem.views import ProblemAdminAPIView
from problem.views import TestCaseUploadAPIView, ProblemTagAdminAPIView
urlpatterns = [ urlpatterns = [
url(r'^install/$', "install.views.install"),
url("^$", TemplateView.as_view(template_name="oj/index.html"), 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/index.html"), name="admin_index_page"), url(r'^admin/$', TemplateView.as_view(template_name="admin/admin.html"), name="admin_spa_page"),
url(r'^login/$', TemplateView.as_view(template_name="oj/account/login.html"), name="user_login_page"), url(r'^login/$', TemplateView.as_view(template_name="oj/account/login.html"), name="user_login_page"),
url(r'^register/$', TemplateView.as_view(template_name="oj/account/register.html"), name="user_register_page"),
url(r'^change_password/$', TemplateView.as_view(template_name="oj/account/change_password.html"), name="user_change_password_page"),
url(r'^api/login/$', UserLoginAPIView.as_view(), name="user_login_api"), url(r'^api/login/$', UserLoginAPIView.as_view(), name="user_login_api"),
url(r'^api/register/$', UserRegisterAPIView.as_view(), name="user_register_api"), url(r'^api/register/$', UserRegisterAPIView.as_view(), name="user_register_api"),
url(r'^api/change_password/$', UserChangePasswordAPIView.as_view(), name="user_change_password_api"), url(r'^api/change_password/$', UserChangePasswordAPIView.as_view(), name="user_change_password_api"),
url(r'^api/username_check/$', UsernameCheckAPIView.as_view(), name="username_check_api"), url(r'^api/username_check/$', UsernameCheckAPIView.as_view(), name="username_check_api"),
url(r'^api/email_check/$', EmailCheckAPIView.as_view(), name="email_check_api"),
url(r'^api/admin/announcement/$', AnnouncementAdminAPIView.as_view(), name="announcement_admin_api"),
url(r'^api/admin/user/$', UserAdminAPIView.as_view(), name="user_admin_api"),
url(r'^problem/(?P<problem_id>\d+)/$', "problem.views.problem_page", name="problem_page"), url(r'^problem/(?P<problem_id>\d+)/$', "problem.views.problem_page", name="problem_page"),
url(r'^announcement/(?P<announcement_id>\d+)/$', "announcement.views.announcement_page", name="announcement_page"),
url(r'^api/announcements/$', AnnouncementAPIView.as_view(), name="announcement_list_api"),
url(r'^api/admin/users/$', UserAPIView.as_view(), name="user_list_api"),
url(r'^admin/contest/$', TemplateView.as_view(template_name="admin/contest/add_contest.html"), name="add_contest_page"), url(r'^admin/contest/$', TemplateView.as_view(template_name="admin/contest/add_contest.html"), name="add_contest_page"),
url(r'^problems/$', TemplateView.as_view(template_name="oj/problem/problem_list.html"), name="problem_list_page"), url(r'^problems/$', TemplateView.as_view(template_name="oj/problem/problem_list.html"), name="problem_list_page"),
url(r'^admin/template/(?P<template_dir>\w+)/(?P<template_name>\w+).html', AdminTemplateView.as_view(), name="admin_template"),
url(r'^api/admin/group/$', GroupAdminAPIView.as_view(), name="group_admin_api"),
url(r'^api/admin/problem/$', ProblemAdminAPIView.as_view(), name="problem_admin_api"),
url(r'^api/admin/test_case_upload/$', TestCaseUploadAPIView.as_view(), name="test_case_upload_api"),
url(r'^api/admin/tag/$', ProblemTagAdminAPIView.as_view(), name="problem_tag_admin_api"),
] ]

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Problem',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('title', models.CharField(max_length=50)),
('description', models.TextField()),
('sample', models.TextField(blank=True)),
('test_case_id', models.CharField(max_length=40)),
('hint', models.TextField(null=True, blank=True)),
('create_time', models.DateTimeField(auto_now_add=True)),
('last_update_time', models.DateTimeField(auto_now=True)),
('source', models.CharField(max_length=30, null=True, blank=True)),
('time_limit', models.IntegerField()),
('memory_limit', models.IntegerField()),
('visible', models.BooleanField(default=True)),
('total_submit_number', models.IntegerField(default=0)),
('total_accepted_number', models.IntegerField(default=0)),
('difficulty', models.IntegerField()),
('created_by', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='ProblemTag',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=30)),
('description', models.CharField(max_length=50)),
],
options={
'db_table': 'problem_tag',
},
),
migrations.AddField(
model_name='problem',
name='tags',
field=models.ManyToManyField(to='problem.ProblemTag', null=True),
),
]

View File

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('problem', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='problemtag',
name='description',
),
]

View File

@ -5,7 +5,11 @@ from account.models import User
class ProblemTag(models.Model): class ProblemTag(models.Model):
pass name = models.CharField(max_length=30)
# description = models.CharField(max_length=50)
class Meta:
db_table = "problem_tag"
class AbstractProblem(models.Model): class AbstractProblem(models.Model):
@ -14,21 +18,19 @@ class AbstractProblem(models.Model):
# 问题描述 HTML 格式 # 问题描述 HTML 格式
description = models.TextField() description = models.TextField()
# 样例输入 可能会存储 json 格式的数据 # 样例输入 可能会存储 json 格式的数据
sample_input = models.TextField(blank=True) sample = models.TextField(blank=True)
# 样例输出 同上
sample_output = models.TextField(blank=True)
# 测试用例id 这个id 可以用来拼接得到测试用例的文件存储位置 # 测试用例id 这个id 可以用来拼接得到测试用例的文件存储位置
test_case_id = models.CharField(max_length=40) test_case_id = models.CharField(max_length=40)
# 提示 # 提示
hint = models.TextField(blank=True) hint = models.TextField(blank=True, null=True)
# 创建时间 # 创建时间
create_time = models.DateTimeField(auth_now_add=True) create_time = models.DateTimeField(auto_now_add=True)
# 最后更新时间 # 最后更新时间
last_update_time = models.DateTimeField(auto_now=True) last_update_time = models.DateTimeField(auto_now=True)
# 这个题是谁创建的 # 这个题是谁创建的
created_by = models.ForeignKey(User) created_by = models.ForeignKey(User)
# 来源 # 来源
source = models.CharField(max_length=30, blank=True) source = models.CharField(max_length=30, blank=True, null=True)
# 时间限制 单位是毫秒 # 时间限制 单位是毫秒
time_limit = models.IntegerField() time_limit = models.IntegerField()
# 内存限制 单位是MB # 内存限制 单位是MB
@ -39,14 +41,13 @@ class AbstractProblem(models.Model):
total_submit_number = models.IntegerField(default=0) total_submit_number = models.IntegerField(default=0)
# 通过数量 # 通过数量
total_accepted_number = models.IntegerField(default=0) total_accepted_number = models.IntegerField(default=0)
# 标签
tags = models.ManyToManyField(ProblemTag, null=True)
# 难度 0 - n
difficulty = models.IntegerField()
class Meta: class Meta:
abstract = True abstract = True
class Problem(AbstractProblem): class Problem(AbstractProblem):
pass # 难度 0 - n
difficulty = models.IntegerField()
# 标签
tags = models.ManyToManyField(ProblemTag, null=True)

71
problem/serizalizers.py Normal file
View File

@ -0,0 +1,71 @@
# coding=utf-8
import json
from rest_framework import serializers
from account.models import User
from .models import Problem, ProblemTag
class ProblemSampleSerializer(serializers.ListField):
input = serializers.CharField(max_length=3000)
output = serializers.CharField(max_length=3000)
class JSONField(serializers.Field):
def to_representation(self, value):
print value, type(value)
return json.loads(value)
class CreateProblemSerializer(serializers.Serializer):
title = serializers.CharField(max_length=50)
description = serializers.CharField(max_length=10000)
# [{"input": "1 1", "output": "2"}]
sample = ProblemSampleSerializer()
test_case_id = serializers.CharField(max_length=40)
source = serializers.CharField(max_length=30, required=False, default=None)
time_limit = serializers.IntegerField()
memory_limit = serializers.IntegerField()
difficulty = serializers.IntegerField()
tags = serializers.ListField(child=serializers.IntegerField())
hint = serializers.CharField(max_length=3000, required=False, default=None)
class ProblemSerializer(serializers.ModelSerializer):
sample = JSONField()
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["username"]
created_by = UserSerializer()
class Meta:
model = Problem
class EditProblemSerializer(serializers.Serializer):
id = serializers.IntegerField()
title = serializers.CharField(max_length=50)
description = serializers.CharField(max_length=10000)
test_case_id = serializers.CharField(max_length=40)
source = serializers.CharField(max_length=30)
time_limit = serializers.IntegerField()
memory_limit = serializers.IntegerField()
difficulty = serializers.IntegerField()
tags = serializers.ListField(child=serializers.IntegerField())
sample = ProblemSampleSerializer()
hint = serializers.CharField(max_length=10000)
visible = serializers.BooleanField()
class ProblemTagSerializer(serializers.ModelSerializer):
class Meta:
model = ProblemTag
class CreateProblemTagSerializer(serializers.Serializer):
name = serializers.CharField(max_length=10)

View File

@ -1,6 +1,115 @@
# coding=utf-8 # coding=utf-8
from django.test import TestCase from django.test import TestCase
from django.core.urlresolvers import reverse
from rest_framework.test import APITestCase, APIClient
from account.models import User, SUPER_ADMIN
from problem.models import Problem, ProblemTag
class ProblemPageTest(TestCase): class ProblemPageTest(TestCase):
pass pass
class ProblemAdminTest(APITestCase):
def setUp(self):
self.client = APIClient()
self.url = reverse("problem_admin_api")
user = User.objects.create(username="test", admin_type=SUPER_ADMIN)
user.set_password("testaa")
user.save()
# 以下是发布题目的测试
def test_invalid_format(self):
self.client.login(username="test", password="testaa")
data = {"title": "test1"}
response = self.client.post(self.url, data=data)
self.assertEqual(response.data["code"], 1)
def test_success_problem(self):
self.client.login(username="test", password="testaa")
ProblemTag.objects.create(name="tag1", description="destag1")
data = {"title": "title1",
"description": "des1",
"test_case_id": "1",
"source": "source1",
"sample": [{"input": "1 1", "output": "2"}],
"time_limit": "100",
"memory_limit": "1000",
"difficulty": "1",
"hint": "hint1",
"tags": [1]}
response = self.client.post(self.url, data=data)
self.assertEqual(response.data["code"], 0)
# 以下是编辑题目的测试
def test_put_invalid_data(self):
self.client.login(username="test", password="testaa")
data = {"title": "test0"}
response = self.client.put(self.url, data=data)
self.assertEqual(response.data["code"], 1)
def test_problem_does_not_exist(self):
self.client.login(username="test", password="testaa")
ProblemTag.objects.create(name="tag1", description="destag1")
tags = ProblemTag.objects.filter(id__in=[1])
problem = Problem.objects.create(title="title1",
description="des1",
test_case_id="1",
source="source1",
sample=[{"input": "1 1", "output": "2"}],
time_limit=100,
memory_limit=1000,
difficulty=1,
hint="hint1",
created_by=User.objects.get(username="test"))
problem.tags.add(*tags)
data = {"id": 2,
"title": "title1",
"description": "des1",
"test_case_id": "1",
"source": "source1",
"sample": [{"input": "1 1", "output": "2"}],
"time_limit": "100",
"memory_limit": "1000",
"difficulty": "1",
"hint": "hint1",
"tags": [1]}
response = self.client.put(self.url, data=data)
self.assertEqual(response.data, {"code": 1, "data": u"该题目不存在!"})
def test_success_edit_problem(self):
self.client.login(username="test", password="testaa")
self.client.login(username="test", password="testaa")
ProblemTag.objects.create(name="tag1", description="destag1")
ProblemTag.objects.create(name="tag2", description="destag2")
tags = ProblemTag.objects.filter(id__in=[1])
problem0 = Problem.objects.create(title="title1",
description="des1",
test_case_id="1",
source="source1",
sample=[{"input": "1 1", "output": "2"}],
time_limit=100,
memory_limit=1000,
difficulty=1,
hint="hint1",
created_by=User.objects.get(username="test"))
problem0.tags.add(*tags)
data = {"id": 1,
"title": "title1",
"description": "des1",
"test_case_id": "1",
"source": "source1",
"sample": [{"input": "1 1", "output": "2"}],
"time_limit": "100",
"memory_limit": "1000",
"difficulty": "1",
"hint": "hint1",
"visible": True,
"tags": [1, 2]}
problem = Problem.objects.get(id=data["id"])
problem.tags.remove(*problem.tags.all())
problem.tags.add(*ProblemTag.objects.filter(id__in=data["tags"]))
response = self.client.put(self.url, data=data)
self.assertEqual(response.data["code"], 0)

View File

@ -1,7 +1,211 @@
# coding=utf-8 # coding=utf-8
import zipfile
import re
import os
import hashlib
import json
from django.shortcuts import render from django.shortcuts import render
from django.db.models import Q
from rest_framework.views import APIView
from utils.shortcuts import serializer_invalid_response, error_response, success_response, paginate, rand_str
from .serizalizers import (CreateProblemSerializer, EditProblemSerializer, ProblemSerializer,
ProblemTagSerializer, CreateProblemTagSerializer)
from .models import Problem, ProblemTag
class ProblemTagAdminAPIView(APIView):
def post(self, request):
"""
创建标签的接口
---
request_serializer: CreateProblemTagSerializer
"""
serializer = CreateProblemTagSerializer(data=request.data)
if serializer.is_valid():
try:
tag = ProblemTag.objects.get(name=serializer.data["name"])
except ProblemTag.DoesNotExist:
tag = ProblemTag.objects.create(name=serializer.data["name"])
return success_response(ProblemTagSerializer(tag).data)
else:
return error_response(serializer)
def get(self, request):
keyword = request.GET.get("keyword", None)
if not keyword:
return error_response(u"参数错误")
tags = ProblemTag.objects.filter(name__contains=keyword)
return success_response(ProblemTagSerializer(tags, many=True).data)
def problem_page(request, problem_id): def problem_page(request, problem_id):
# todo # todo
return render(request, "oj/problem/problem.html") return render(request, "oj/problem/problem.html")
class ProblemAdminAPIView(APIView):
def post(self, request):
"""
题目发布json api接口
---
request_serializer: CreateProblemSerializer
response_serializer: ProblemSerializer
"""
serializer = CreateProblemSerializer(data=request.data)
if serializer.is_valid():
data = serializer.data
problem = Problem.objects.create(title=data["title"],
description=data["description"],
test_case_id=data["test_case_id"],
source=data["source"],
sample=json.dumps(data["sample"]),
time_limit=data["time_limit"],
memory_limit=data["memory_limit"],
difficulty=data["difficulty"],
created_by=request.user,
hint=data["hint"])
tags = ProblemTag.objects.filter(id__in=data["tags"])
problem.tags.add(*tags)
return success_response(ProblemSerializer(problem).data)
else:
return serializer_invalid_response(serializer)
def put(self, request):
"""
题目编辑json api接口
---
request_serializer: EditProblemSerializer
response_serializer: ProblemSerializer
"""
serializer = EditProblemSerializer(data=request.data)
if serializer.is_valid():
data = serializer.data
try:
problem = Problem.objects.get(id=data["id"])
except Problem.DoesNotExist:
return error_response(u"该题目不存在!")
problem.title = data["title"]
problem.description = data["description"]
problem.test_case_id = data["test_case_id"]
problem.source = data["source"]
problem.time_limit = data["time_limit"]
problem.memory_limit = data["memory_limit"]
problem.difficulty = data["difficulty"]
problem.sample = json.dumps(data["sample"])
problem.hint = data["hint"]
problem.visible = data["visible"]
# 删除原有的标签的对应关系
problem.tags.remove(*problem.tags.all())
# 重新添加所有的标签
problem.tags.add(*ProblemTag.objects.filter(id__in=data["tags"]))
problem.save()
return success_response(ProblemSerializer(problem).data)
else:
return serializer_invalid_response(serializer)
class ProblemAPIView(APIView):
def get(self, request):
"""
题目分页json api接口
---
response_serializer: ProblemSerializer
"""
problem = Problem.objects.all().order_by("-last_update_time")
visible = request.GET.get("visible", None)
if visible:
problem = problem.filter(visible=(visible == "true"))
keyword = request.GET.get("keyword", None)
if keyword:
problem = problem.filter(Q(difficulty__contains=keyword))
return paginate(request, problem, ProblemSerializer)
class TestCaseUploadAPIView(APIView):
def _is_legal_test_case_file_name(self, file_name):
# 正整数开头的 .in 或者.out 结尾的
regex = r"^[1-9]\d*\.(in|out)$"
return re.compile(regex).match(file_name) is not None
def post(self, request):
if "file" not in request.FILES:
return error_response(u"文件上传失败")
f = request.FILES["file"]
tmp_zip = "tmp/" + rand_str() + ".zip"
with open(tmp_zip, "wb") as test_case_zip:
for chunk in f:
test_case_zip.write(chunk)
test_case_file = zipfile.ZipFile(tmp_zip, 'r')
name_list = test_case_file.namelist()
l = []
# 如果文件是直接打包的那么name_list 就是["1.in", "1.out"]这样的
# 如果文件还有一层文件夹test_case那么name_list就是["test_case/", "test_case/1.in", "test_case/1.out"]
# 现在暂时只支持第一种,先判断一下是什么格式的
# 第一种格式的
if "1.in" in name_list and "1.out" in name_list:
for file_name in name_list:
if self._is_legal_test_case_file_name(file_name):
name = file_name.split(".")
# 有了.in 判断对应的.out 在不在
if name[1] == "in":
if (name[0] + ".out") in name_list:
l.append(file_name)
else:
return error_response(u"测试用例文件不完整,缺少" + name[0] + ".out")
else:
# 有了.out 判断对应的 .in 在不在
if (name[0] + ".in") in name_list:
l.append(file_name)
else:
return error_response(u"测试用例文件不完整,缺少" + name[0] + ".in")
problem_test_dir = rand_str()
test_case_dir = "test_case/" + problem_test_dir + "/"
# 得到了合法的测试用例文件列表 然后去解压缩
os.mkdir(test_case_dir)
for name in l:
f = open(test_case_dir + name, "wb")
f.write(test_case_file.read(name))
f.close()
l.sort()
file_info = {"test_case_number": len(l) / 2, "test_cases": {}}
# 计算输出文件的md5
for i in range(len(l) / 2):
md5 = hashlib.md5()
f = open(test_case_dir + str(i + 1) + ".out", "r")
while True:
data = f.read(2 ** 8)
if not data:
break
md5.update(data)
file_info["test_cases"][str(i + 1)] = {"input_name": str(i + 1) + ".in",
"output_name": str(i + 1) + ".out",
"output_md5": md5.hexdigest(),
"output_size": os.path.getsize(test_case_dir + str(i + 1) + ".out")}
# 写入配置文件
open(test_case_dir + "info", "w").write(json.dumps(file_info))
return success_response({"test_case_id": problem_test_dir,
"file_list": {"input": l[0::2],
"output": l[1::2]}})
else:
return error_response(u"测试用例压缩文件格式错误,请保证测试用例文件在根目录下直接压缩")

View File

@ -6,3 +6,4 @@ djangorestframework
django-rest-swagger django-rest-swagger
celery celery
gunicorn gunicorn
coverage

View File

@ -1,25 +1,34 @@
@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("simditor/simditor.css"); @import url("simditor/simditor.css");
@import url("webuploader/webuploader.css"); @import url("webuploader/webuploader.css");
@import url("datetime_picker/bootstrap-datetimepicker.css"); @import url("datetime_picker/bootstrap-datetimepicker.css");
html, body {
height: 100%; #loading-gif{
width: 40px;
height: 40px;
margin: auto;
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
} }
img { .line-chart{
max-width: 100%; min-width:100%;
height: auto; max-height: 300px;
} }
.footer { .pie-chart{
padding-top: 30px; min-width:100%;
padding-bottom: 30px; max-height: 300px;
float: bottom;
bottom: 0;
} }
label { .chart-description{
font-size: 16px; text-align: center;
}
.pie-chart-container{
width: 50%;
float: left;
} }

31
static/src/css/global.css Normal file
View File

@ -0,0 +1,31 @@
html{
height: 100%;
}
body{
height:100%; /*使内容高度和body一样*/
margin-bottom:-80px;/*向上缩减80像素不至于footer超出屏幕可视范围*/
}
.main{
padding-bottom: 120px;
}
img {
max-width: 100%;
height: auto;
}
.footer {
left: 0;
right: 0;
height: 80px
}
label {
font-size: 16px;
}
[ms-controller] {
display: none
}

544
static/src/css/jqueryUI/jquery-ui.css vendored Normal file
View File

@ -0,0 +1,544 @@
/*! jQuery UI - v1.11.4 - 2015-08-10
* http://jqueryui.com
* Includes: core.css, autocomplete.css, menu.css, theme.css
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS%2CTahoma%2CVerdana%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=gloss_wave&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=highlight_soft&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=glass&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=glass&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=highlight_soft&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=diagonals_thick&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=diagonals_thick&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=flat&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
/* Layout helpers
----------------------------------*/
.ui-helper-hidden {
display: none;
}
.ui-helper-hidden-accessible {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.ui-helper-reset {
margin: 0;
padding: 0;
border: 0;
outline: 0;
line-height: 1.3;
text-decoration: none;
font-size: 100%;
list-style: none;
}
.ui-helper-clearfix:before,
.ui-helper-clearfix:after {
content: "";
display: table;
border-collapse: collapse;
}
.ui-helper-clearfix:after {
clear: both;
}
.ui-helper-clearfix {
min-height: 0; /* support: IE7 */
}
.ui-helper-zfix {
width: 100%;
height: 100%;
top: 0;
left: 0;
position: absolute;
opacity: 0;
filter:Alpha(Opacity=0); /* support: IE8 */
}
.ui-front {
z-index: 100;
}
/* Interaction Cues
----------------------------------*/
.ui-state-disabled {
cursor: default !important;
}
/* Icons
----------------------------------*/
/* states and images */
.ui-icon {
display: block;
text-indent: -99999px;
overflow: hidden;
background-repeat: no-repeat;
}
/* Misc visuals
----------------------------------*/
/* Overlays */
.ui-widget-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.ui-autocomplete {
position: absolute;
top: 0;
left: 0;
cursor: default;
}
.ui-menu {
list-style: none;
padding: 0;
margin: 0;
display: block;
outline: none;
}
.ui-menu .ui-menu {
position: absolute;
}
.ui-menu .ui-menu-item {
position: relative;
margin: 0;
padding: 3px 1em 3px .4em;
cursor: pointer;
min-height: 0; /* support: IE7 */
/* support: IE10, see #8844 */
list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
}
.ui-menu .ui-menu-divider {
margin: 5px 0;
height: 0;
font-size: 0;
line-height: 0;
border-width: 1px 0 0 0;
}
.ui-menu .ui-state-focus,
.ui-menu .ui-state-active {
margin: -1px;
}
/* icon support */
.ui-menu-icons {
position: relative;
}
.ui-menu-icons .ui-menu-item {
padding-left: 2em;
}
/* left-aligned */
.ui-menu .ui-icon {
position: absolute;
top: 0;
bottom: 0;
left: .2em;
margin: auto 0;
}
/* right-aligned */
.ui-menu .ui-menu-icon {
left: auto;
right: 0;
}
/* Component containers
----------------------------------*/
.ui-widget {
font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;
font-size: 1.1em;
}
.ui-widget .ui-widget {
font-size: 1em;
}
.ui-widget input,
.ui-widget select,
.ui-widget textarea,
.ui-widget button {
font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;
font-size: 1em;
}
.ui-widget-content {
border: 1px solid #dddddd;
background: #eeeeee url("images/ui-bg_highlight-soft_100_eeeeee_1x100.png") 50% top repeat-x;
color: #333333;
}
.ui-widget-content a {
color: #333333;
}
.ui-widget-header {
border: 1px solid #e78f08;
background: #f6a828 url("images/ui-bg_gloss-wave_35_f6a828_500x100.png") 50% 50% repeat-x;
color: #ffffff;
font-weight: bold;
}
.ui-widget-header a {
color: #ffffff;
}
/* Interaction states
----------------------------------*/
.ui-state-default,
.ui-widget-content .ui-state-default,
.ui-widget-header .ui-state-default {
border: 1px solid #cccccc;
background: #f6f6f6 url("images/ui-bg_glass_100_f6f6f6_1x400.png") 50% 50% repeat-x;
font-weight: bold;
color: #1c94c4;
}
.ui-state-default a,
.ui-state-default a:link,
.ui-state-default a:visited {
color: #1c94c4;
text-decoration: none;
}
.ui-state-hover,
.ui-widget-content .ui-state-hover,
.ui-widget-header .ui-state-hover,
.ui-state-focus,
.ui-widget-content .ui-state-focus,
.ui-widget-header .ui-state-focus {
border: 1px solid #fbcb09;
background: #fdf5ce url("images/ui-bg_glass_100_fdf5ce_1x400.png") 50% 50% repeat-x;
font-weight: bold;
color: #c77405;
}
.ui-state-hover a,
.ui-state-hover a:hover,
.ui-state-hover a:link,
.ui-state-hover a:visited,
.ui-state-focus a,
.ui-state-focus a:hover,
.ui-state-focus a:link,
.ui-state-focus a:visited {
color: #c77405;
text-decoration: none;
}
.ui-state-active,
.ui-widget-content .ui-state-active,
.ui-widget-header .ui-state-active {
border: 1px solid #fbd850;
background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;
font-weight: bold;
color: #eb8f00;
}
.ui-state-active a,
.ui-state-active a:link,
.ui-state-active a:visited {
color: #eb8f00;
text-decoration: none;
}
/* Interaction Cues
----------------------------------*/
.ui-state-highlight,
.ui-widget-content .ui-state-highlight,
.ui-widget-header .ui-state-highlight {
border: 1px solid #fed22f;
background: #ffe45c url("images/ui-bg_highlight-soft_75_ffe45c_1x100.png") 50% top repeat-x;
color: #363636;
}
.ui-state-highlight a,
.ui-widget-content .ui-state-highlight a,
.ui-widget-header .ui-state-highlight a {
color: #363636;
}
.ui-state-error,
.ui-widget-content .ui-state-error,
.ui-widget-header .ui-state-error {
border: 1px solid #cd0a0a;
background: #b81900 url("images/ui-bg_diagonals-thick_18_b81900_40x40.png") 50% 50% repeat;
color: #ffffff;
}
.ui-state-error a,
.ui-widget-content .ui-state-error a,
.ui-widget-header .ui-state-error a {
color: #ffffff;
}
.ui-state-error-text,
.ui-widget-content .ui-state-error-text,
.ui-widget-header .ui-state-error-text {
color: #ffffff;
}
.ui-priority-primary,
.ui-widget-content .ui-priority-primary,
.ui-widget-header .ui-priority-primary {
font-weight: bold;
}
.ui-priority-secondary,
.ui-widget-content .ui-priority-secondary,
.ui-widget-header .ui-priority-secondary {
opacity: .7;
filter:Alpha(Opacity=70); /* support: IE8 */
font-weight: normal;
}
.ui-state-disabled,
.ui-widget-content .ui-state-disabled,
.ui-widget-header .ui-state-disabled {
opacity: .35;
filter:Alpha(Opacity=35); /* support: IE8 */
background-image: none;
}
.ui-state-disabled .ui-icon {
filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
}
/* Icons
----------------------------------*/
/* states and images */
.ui-icon {
width: 16px;
height: 16px;
}
.ui-icon,
.ui-widget-content .ui-icon {
background-image: url("images/ui-icons_222222_256x240.png");
}
.ui-widget-header .ui-icon {
background-image: url("images/ui-icons_ffffff_256x240.png");
}
.ui-state-default .ui-icon {
background-image: url("images/ui-icons_ef8c08_256x240.png");
}
.ui-state-hover .ui-icon,
.ui-state-focus .ui-icon {
background-image: url("images/ui-icons_ef8c08_256x240.png");
}
.ui-state-active .ui-icon {
background-image: url("images/ui-icons_ef8c08_256x240.png");
}
.ui-state-highlight .ui-icon {
background-image: url("images/ui-icons_228ef1_256x240.png");
}
.ui-state-error .ui-icon,
.ui-state-error-text .ui-icon {
background-image: url("images/ui-icons_ffd27a_256x240.png");
}
/* positioning */
.ui-icon-blank { background-position: 16px 16px; }
.ui-icon-carat-1-n { background-position: 0 0; }
.ui-icon-carat-1-ne { background-position: -16px 0; }
.ui-icon-carat-1-e { background-position: -32px 0; }
.ui-icon-carat-1-se { background-position: -48px 0; }
.ui-icon-carat-1-s { background-position: -64px 0; }
.ui-icon-carat-1-sw { background-position: -80px 0; }
.ui-icon-carat-1-w { background-position: -96px 0; }
.ui-icon-carat-1-nw { background-position: -112px 0; }
.ui-icon-carat-2-n-s { background-position: -128px 0; }
.ui-icon-carat-2-e-w { background-position: -144px 0; }
.ui-icon-triangle-1-n { background-position: 0 -16px; }
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
.ui-icon-triangle-1-e { background-position: -32px -16px; }
.ui-icon-triangle-1-se { background-position: -48px -16px; }
.ui-icon-triangle-1-s { background-position: -64px -16px; }
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
.ui-icon-triangle-1-w { background-position: -96px -16px; }
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
.ui-icon-arrow-1-n { background-position: 0 -32px; }
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
.ui-icon-arrow-1-e { background-position: -32px -32px; }
.ui-icon-arrow-1-se { background-position: -48px -32px; }
.ui-icon-arrow-1-s { background-position: -64px -32px; }
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
.ui-icon-arrow-1-w { background-position: -96px -32px; }
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
.ui-icon-arrow-4 { background-position: 0 -80px; }
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
.ui-icon-extlink { background-position: -32px -80px; }
.ui-icon-newwin { background-position: -48px -80px; }
.ui-icon-refresh { background-position: -64px -80px; }
.ui-icon-shuffle { background-position: -80px -80px; }
.ui-icon-transfer-e-w { background-position: -96px -80px; }
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
.ui-icon-folder-collapsed { background-position: 0 -96px; }
.ui-icon-folder-open { background-position: -16px -96px; }
.ui-icon-document { background-position: -32px -96px; }
.ui-icon-document-b { background-position: -48px -96px; }
.ui-icon-note { background-position: -64px -96px; }
.ui-icon-mail-closed { background-position: -80px -96px; }
.ui-icon-mail-open { background-position: -96px -96px; }
.ui-icon-suitcase { background-position: -112px -96px; }
.ui-icon-comment { background-position: -128px -96px; }
.ui-icon-person { background-position: -144px -96px; }
.ui-icon-print { background-position: -160px -96px; }
.ui-icon-trash { background-position: -176px -96px; }
.ui-icon-locked { background-position: -192px -96px; }
.ui-icon-unlocked { background-position: -208px -96px; }
.ui-icon-bookmark { background-position: -224px -96px; }
.ui-icon-tag { background-position: -240px -96px; }
.ui-icon-home { background-position: 0 -112px; }
.ui-icon-flag { background-position: -16px -112px; }
.ui-icon-calendar { background-position: -32px -112px; }
.ui-icon-cart { background-position: -48px -112px; }
.ui-icon-pencil { background-position: -64px -112px; }
.ui-icon-clock { background-position: -80px -112px; }
.ui-icon-disk { background-position: -96px -112px; }
.ui-icon-calculator { background-position: -112px -112px; }
.ui-icon-zoomin { background-position: -128px -112px; }
.ui-icon-zoomout { background-position: -144px -112px; }
.ui-icon-search { background-position: -160px -112px; }
.ui-icon-wrench { background-position: -176px -112px; }
.ui-icon-gear { background-position: -192px -112px; }
.ui-icon-heart { background-position: -208px -112px; }
.ui-icon-star { background-position: -224px -112px; }
.ui-icon-link { background-position: -240px -112px; }
.ui-icon-cancel { background-position: 0 -128px; }
.ui-icon-plus { background-position: -16px -128px; }
.ui-icon-plusthick { background-position: -32px -128px; }
.ui-icon-minus { background-position: -48px -128px; }
.ui-icon-minusthick { background-position: -64px -128px; }
.ui-icon-close { background-position: -80px -128px; }
.ui-icon-closethick { background-position: -96px -128px; }
.ui-icon-key { background-position: -112px -128px; }
.ui-icon-lightbulb { background-position: -128px -128px; }
.ui-icon-scissors { background-position: -144px -128px; }
.ui-icon-clipboard { background-position: -160px -128px; }
.ui-icon-copy { background-position: -176px -128px; }
.ui-icon-contact { background-position: -192px -128px; }
.ui-icon-image { background-position: -208px -128px; }
.ui-icon-video { background-position: -224px -128px; }
.ui-icon-script { background-position: -240px -128px; }
.ui-icon-alert { background-position: 0 -144px; }
.ui-icon-info { background-position: -16px -144px; }
.ui-icon-notice { background-position: -32px -144px; }
.ui-icon-help { background-position: -48px -144px; }
.ui-icon-check { background-position: -64px -144px; }
.ui-icon-bullet { background-position: -80px -144px; }
.ui-icon-radio-on { background-position: -96px -144px; }
.ui-icon-radio-off { background-position: -112px -144px; }
.ui-icon-pin-w { background-position: -128px -144px; }
.ui-icon-pin-s { background-position: -144px -144px; }
.ui-icon-play { background-position: 0 -160px; }
.ui-icon-pause { background-position: -16px -160px; }
.ui-icon-seek-next { background-position: -32px -160px; }
.ui-icon-seek-prev { background-position: -48px -160px; }
.ui-icon-seek-end { background-position: -64px -160px; }
.ui-icon-seek-start { background-position: -80px -160px; }
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
.ui-icon-seek-first { background-position: -80px -160px; }
.ui-icon-stop { background-position: -96px -160px; }
.ui-icon-eject { background-position: -112px -160px; }
.ui-icon-volume-off { background-position: -128px -160px; }
.ui-icon-volume-on { background-position: -144px -160px; }
.ui-icon-power { background-position: 0 -176px; }
.ui-icon-signal-diag { background-position: -16px -176px; }
.ui-icon-signal { background-position: -32px -176px; }
.ui-icon-battery-0 { background-position: -48px -176px; }
.ui-icon-battery-1 { background-position: -64px -176px; }
.ui-icon-battery-2 { background-position: -80px -176px; }
.ui-icon-battery-3 { background-position: -96px -176px; }
.ui-icon-circle-plus { background-position: 0 -192px; }
.ui-icon-circle-minus { background-position: -16px -192px; }
.ui-icon-circle-close { background-position: -32px -192px; }
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
.ui-icon-circle-zoomin { background-position: -176px -192px; }
.ui-icon-circle-zoomout { background-position: -192px -192px; }
.ui-icon-circle-check { background-position: -208px -192px; }
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
.ui-icon-circlesmall-close { background-position: -32px -208px; }
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
.ui-icon-squaresmall-close { background-position: -80px -208px; }
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
/* Misc visuals
----------------------------------*/
/* Corner radius */
.ui-corner-all,
.ui-corner-top,
.ui-corner-left,
.ui-corner-tl {
border-top-left-radius: 4px;
}
.ui-corner-all,
.ui-corner-top,
.ui-corner-right,
.ui-corner-tr {
border-top-right-radius: 4px;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-left,
.ui-corner-bl {
border-bottom-left-radius: 4px;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-right,
.ui-corner-br {
border-bottom-right-radius: 4px;
}
/* Overlays */
.ui-widget-overlay {
background: #666666 url("images/ui-bg_diagonals-thick_20_666666_40x40.png") 50% 50% repeat;
opacity: .5;
filter: Alpha(Opacity=50); /* support: IE8 */
}
.ui-widget-shadow {
margin: -5px 0 0 -5px;
padding: 5px;
background: #000000 url("images/ui-bg_flat_10_000000_40x100.png") 50% 50% repeat-x;
opacity: .2;
filter: Alpha(Opacity=20); /* support: IE8 */
border-radius: 5px;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,152 @@
/*!
* jQuery UI CSS Framework 1.11.4
* http://jqueryui.com
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/category/theming/
*/
/* Layout helpers
----------------------------------*/
.ui-helper-hidden {
display: none;
}
.ui-helper-hidden-accessible {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.ui-helper-reset {
margin: 0;
padding: 0;
border: 0;
outline: 0;
line-height: 1.3;
text-decoration: none;
font-size: 100%;
list-style: none;
}
.ui-helper-clearfix:before,
.ui-helper-clearfix:after {
content: "";
display: table;
border-collapse: collapse;
}
.ui-helper-clearfix:after {
clear: both;
}
.ui-helper-clearfix {
min-height: 0; /* support: IE7 */
}
.ui-helper-zfix {
width: 100%;
height: 100%;
top: 0;
left: 0;
position: absolute;
opacity: 0;
filter:Alpha(Opacity=0); /* support: IE8 */
}
.ui-front {
z-index: 100;
}
/* Interaction Cues
----------------------------------*/
.ui-state-disabled {
cursor: default !important;
}
/* Icons
----------------------------------*/
/* states and images */
.ui-icon {
display: block;
text-indent: -99999px;
overflow: hidden;
background-repeat: no-repeat;
}
/* Misc visuals
----------------------------------*/
/* Overlays */
.ui-widget-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.ui-autocomplete {
position: absolute;
top: 0;
left: 0;
cursor: default;
}
.ui-menu {
list-style: none;
padding: 0;
margin: 0;
display: block;
outline: none;
}
.ui-menu .ui-menu {
position: absolute;
}
.ui-menu .ui-menu-item {
position: relative;
margin: 0;
padding: 3px 1em 3px .4em;
cursor: pointer;
min-height: 0; /* support: IE7 */
/* support: IE10, see #8844 */
list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
}
.ui-menu .ui-menu-divider {
margin: 5px 0;
height: 0;
font-size: 0;
line-height: 0;
border-width: 1px 0 0 0;
}
.ui-menu .ui-state-focus,
.ui-menu .ui-state-active {
margin: -1px;
}
/* icon support */
.ui-menu-icons {
position: relative;
}
.ui-menu-icons .ui-menu-item {
padding-left: 2em;
}
/* left-aligned */
.ui-menu .ui-icon {
position: absolute;
top: 0;
bottom: 0;
left: .2em;
margin: auto 0;
}
/* right-aligned */
.ui-menu .ui-menu-icon {
left: auto;
right: 0;
}

View File

@ -0,0 +1,410 @@
/*!
* jQuery UI CSS Framework 1.11.4
* http://jqueryui.com
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/category/theming/
*
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS%2CTahoma%2CVerdana%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=gloss_wave&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=highlight_soft&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=glass&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=glass&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=highlight_soft&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=diagonals_thick&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=diagonals_thick&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=flat&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
*/
/* Component containers
----------------------------------*/
.ui-widget {
font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;
font-size: 1.1em;
}
.ui-widget .ui-widget {
font-size: 1em;
}
.ui-widget input,
.ui-widget select,
.ui-widget textarea,
.ui-widget button {
font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;
font-size: 1em;
}
.ui-widget-content {
border: 1px solid #dddddd;
background: #eeeeee url("images/ui-bg_highlight-soft_100_eeeeee_1x100.png") 50% top repeat-x;
color: #333333;
}
.ui-widget-content a {
color: #333333;
}
.ui-widget-header {
border: 1px solid #e78f08;
background: #f6a828 url("images/ui-bg_gloss-wave_35_f6a828_500x100.png") 50% 50% repeat-x;
color: #ffffff;
font-weight: bold;
}
.ui-widget-header a {
color: #ffffff;
}
/* Interaction states
----------------------------------*/
.ui-state-default,
.ui-widget-content .ui-state-default,
.ui-widget-header .ui-state-default {
border: 1px solid #cccccc;
background: #f6f6f6 url("images/ui-bg_glass_100_f6f6f6_1x400.png") 50% 50% repeat-x;
font-weight: bold;
color: #1c94c4;
}
.ui-state-default a,
.ui-state-default a:link,
.ui-state-default a:visited {
color: #1c94c4;
text-decoration: none;
}
.ui-state-hover,
.ui-widget-content .ui-state-hover,
.ui-widget-header .ui-state-hover,
.ui-state-focus,
.ui-widget-content .ui-state-focus,
.ui-widget-header .ui-state-focus {
border: 1px solid #fbcb09;
background: #fdf5ce url("images/ui-bg_glass_100_fdf5ce_1x400.png") 50% 50% repeat-x;
font-weight: bold;
color: #c77405;
}
.ui-state-hover a,
.ui-state-hover a:hover,
.ui-state-hover a:link,
.ui-state-hover a:visited,
.ui-state-focus a,
.ui-state-focus a:hover,
.ui-state-focus a:link,
.ui-state-focus a:visited {
color: #c77405;
text-decoration: none;
}
.ui-state-active,
.ui-widget-content .ui-state-active,
.ui-widget-header .ui-state-active {
border: 1px solid #fbd850;
background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;
font-weight: bold;
color: #eb8f00;
}
.ui-state-active a,
.ui-state-active a:link,
.ui-state-active a:visited {
color: #eb8f00;
text-decoration: none;
}
/* Interaction Cues
----------------------------------*/
.ui-state-highlight,
.ui-widget-content .ui-state-highlight,
.ui-widget-header .ui-state-highlight {
border: 1px solid #fed22f;
background: #ffe45c url("images/ui-bg_highlight-soft_75_ffe45c_1x100.png") 50% top repeat-x;
color: #363636;
}
.ui-state-highlight a,
.ui-widget-content .ui-state-highlight a,
.ui-widget-header .ui-state-highlight a {
color: #363636;
}
.ui-state-error,
.ui-widget-content .ui-state-error,
.ui-widget-header .ui-state-error {
border: 1px solid #cd0a0a;
background: #b81900 url("images/ui-bg_diagonals-thick_18_b81900_40x40.png") 50% 50% repeat;
color: #ffffff;
}
.ui-state-error a,
.ui-widget-content .ui-state-error a,
.ui-widget-header .ui-state-error a {
color: #ffffff;
}
.ui-state-error-text,
.ui-widget-content .ui-state-error-text,
.ui-widget-header .ui-state-error-text {
color: #ffffff;
}
.ui-priority-primary,
.ui-widget-content .ui-priority-primary,
.ui-widget-header .ui-priority-primary {
font-weight: bold;
}
.ui-priority-secondary,
.ui-widget-content .ui-priority-secondary,
.ui-widget-header .ui-priority-secondary {
opacity: .7;
filter:Alpha(Opacity=70); /* support: IE8 */
font-weight: normal;
}
.ui-state-disabled,
.ui-widget-content .ui-state-disabled,
.ui-widget-header .ui-state-disabled {
opacity: .35;
filter:Alpha(Opacity=35); /* support: IE8 */
background-image: none;
}
.ui-state-disabled .ui-icon {
filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
}
/* Icons
----------------------------------*/
/* states and images */
.ui-icon {
width: 16px;
height: 16px;
}
.ui-icon,
.ui-widget-content .ui-icon {
background-image: url("images/ui-icons_222222_256x240.png");
}
.ui-widget-header .ui-icon {
background-image: url("images/ui-icons_ffffff_256x240.png");
}
.ui-state-default .ui-icon {
background-image: url("images/ui-icons_ef8c08_256x240.png");
}
.ui-state-hover .ui-icon,
.ui-state-focus .ui-icon {
background-image: url("images/ui-icons_ef8c08_256x240.png");
}
.ui-state-active .ui-icon {
background-image: url("images/ui-icons_ef8c08_256x240.png");
}
.ui-state-highlight .ui-icon {
background-image: url("images/ui-icons_228ef1_256x240.png");
}
.ui-state-error .ui-icon,
.ui-state-error-text .ui-icon {
background-image: url("images/ui-icons_ffd27a_256x240.png");
}
/* positioning */
.ui-icon-blank { background-position: 16px 16px; }
.ui-icon-carat-1-n { background-position: 0 0; }
.ui-icon-carat-1-ne { background-position: -16px 0; }
.ui-icon-carat-1-e { background-position: -32px 0; }
.ui-icon-carat-1-se { background-position: -48px 0; }
.ui-icon-carat-1-s { background-position: -64px 0; }
.ui-icon-carat-1-sw { background-position: -80px 0; }
.ui-icon-carat-1-w { background-position: -96px 0; }
.ui-icon-carat-1-nw { background-position: -112px 0; }
.ui-icon-carat-2-n-s { background-position: -128px 0; }
.ui-icon-carat-2-e-w { background-position: -144px 0; }
.ui-icon-triangle-1-n { background-position: 0 -16px; }
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
.ui-icon-triangle-1-e { background-position: -32px -16px; }
.ui-icon-triangle-1-se { background-position: -48px -16px; }
.ui-icon-triangle-1-s { background-position: -64px -16px; }
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
.ui-icon-triangle-1-w { background-position: -96px -16px; }
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
.ui-icon-arrow-1-n { background-position: 0 -32px; }
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
.ui-icon-arrow-1-e { background-position: -32px -32px; }
.ui-icon-arrow-1-se { background-position: -48px -32px; }
.ui-icon-arrow-1-s { background-position: -64px -32px; }
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
.ui-icon-arrow-1-w { background-position: -96px -32px; }
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
.ui-icon-arrow-4 { background-position: 0 -80px; }
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
.ui-icon-extlink { background-position: -32px -80px; }
.ui-icon-newwin { background-position: -48px -80px; }
.ui-icon-refresh { background-position: -64px -80px; }
.ui-icon-shuffle { background-position: -80px -80px; }
.ui-icon-transfer-e-w { background-position: -96px -80px; }
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
.ui-icon-folder-collapsed { background-position: 0 -96px; }
.ui-icon-folder-open { background-position: -16px -96px; }
.ui-icon-document { background-position: -32px -96px; }
.ui-icon-document-b { background-position: -48px -96px; }
.ui-icon-note { background-position: -64px -96px; }
.ui-icon-mail-closed { background-position: -80px -96px; }
.ui-icon-mail-open { background-position: -96px -96px; }
.ui-icon-suitcase { background-position: -112px -96px; }
.ui-icon-comment { background-position: -128px -96px; }
.ui-icon-person { background-position: -144px -96px; }
.ui-icon-print { background-position: -160px -96px; }
.ui-icon-trash { background-position: -176px -96px; }
.ui-icon-locked { background-position: -192px -96px; }
.ui-icon-unlocked { background-position: -208px -96px; }
.ui-icon-bookmark { background-position: -224px -96px; }
.ui-icon-tag { background-position: -240px -96px; }
.ui-icon-home { background-position: 0 -112px; }
.ui-icon-flag { background-position: -16px -112px; }
.ui-icon-calendar { background-position: -32px -112px; }
.ui-icon-cart { background-position: -48px -112px; }
.ui-icon-pencil { background-position: -64px -112px; }
.ui-icon-clock { background-position: -80px -112px; }
.ui-icon-disk { background-position: -96px -112px; }
.ui-icon-calculator { background-position: -112px -112px; }
.ui-icon-zoomin { background-position: -128px -112px; }
.ui-icon-zoomout { background-position: -144px -112px; }
.ui-icon-search { background-position: -160px -112px; }
.ui-icon-wrench { background-position: -176px -112px; }
.ui-icon-gear { background-position: -192px -112px; }
.ui-icon-heart { background-position: -208px -112px; }
.ui-icon-star { background-position: -224px -112px; }
.ui-icon-link { background-position: -240px -112px; }
.ui-icon-cancel { background-position: 0 -128px; }
.ui-icon-plus { background-position: -16px -128px; }
.ui-icon-plusthick { background-position: -32px -128px; }
.ui-icon-minus { background-position: -48px -128px; }
.ui-icon-minusthick { background-position: -64px -128px; }
.ui-icon-close { background-position: -80px -128px; }
.ui-icon-closethick { background-position: -96px -128px; }
.ui-icon-key { background-position: -112px -128px; }
.ui-icon-lightbulb { background-position: -128px -128px; }
.ui-icon-scissors { background-position: -144px -128px; }
.ui-icon-clipboard { background-position: -160px -128px; }
.ui-icon-copy { background-position: -176px -128px; }
.ui-icon-contact { background-position: -192px -128px; }
.ui-icon-image { background-position: -208px -128px; }
.ui-icon-video { background-position: -224px -128px; }
.ui-icon-script { background-position: -240px -128px; }
.ui-icon-alert { background-position: 0 -144px; }
.ui-icon-info { background-position: -16px -144px; }
.ui-icon-notice { background-position: -32px -144px; }
.ui-icon-help { background-position: -48px -144px; }
.ui-icon-check { background-position: -64px -144px; }
.ui-icon-bullet { background-position: -80px -144px; }
.ui-icon-radio-on { background-position: -96px -144px; }
.ui-icon-radio-off { background-position: -112px -144px; }
.ui-icon-pin-w { background-position: -128px -144px; }
.ui-icon-pin-s { background-position: -144px -144px; }
.ui-icon-play { background-position: 0 -160px; }
.ui-icon-pause { background-position: -16px -160px; }
.ui-icon-seek-next { background-position: -32px -160px; }
.ui-icon-seek-prev { background-position: -48px -160px; }
.ui-icon-seek-end { background-position: -64px -160px; }
.ui-icon-seek-start { background-position: -80px -160px; }
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
.ui-icon-seek-first { background-position: -80px -160px; }
.ui-icon-stop { background-position: -96px -160px; }
.ui-icon-eject { background-position: -112px -160px; }
.ui-icon-volume-off { background-position: -128px -160px; }
.ui-icon-volume-on { background-position: -144px -160px; }
.ui-icon-power { background-position: 0 -176px; }
.ui-icon-signal-diag { background-position: -16px -176px; }
.ui-icon-signal { background-position: -32px -176px; }
.ui-icon-battery-0 { background-position: -48px -176px; }
.ui-icon-battery-1 { background-position: -64px -176px; }
.ui-icon-battery-2 { background-position: -80px -176px; }
.ui-icon-battery-3 { background-position: -96px -176px; }
.ui-icon-circle-plus { background-position: 0 -192px; }
.ui-icon-circle-minus { background-position: -16px -192px; }
.ui-icon-circle-close { background-position: -32px -192px; }
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
.ui-icon-circle-zoomin { background-position: -176px -192px; }
.ui-icon-circle-zoomout { background-position: -192px -192px; }
.ui-icon-circle-check { background-position: -208px -192px; }
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
.ui-icon-circlesmall-close { background-position: -32px -208px; }
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
.ui-icon-squaresmall-close { background-position: -80px -208px; }
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
/* Misc visuals
----------------------------------*/
/* Corner radius */
.ui-corner-all,
.ui-corner-top,
.ui-corner-left,
.ui-corner-tl {
border-top-left-radius: 4px;
}
.ui-corner-all,
.ui-corner-top,
.ui-corner-right,
.ui-corner-tr {
border-top-right-radius: 4px;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-left,
.ui-corner-bl {
border-bottom-left-radius: 4px;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-right,
.ui-corner-br {
border-bottom-right-radius: 4px;
}
/* Overlays */
.ui-widget-overlay {
background: #666666 url("images/ui-bg_diagonals-thick_20_666666_40x40.png") 50% 50% repeat;
opacity: .5;
filter: Alpha(Opacity=50); /* support: IE8 */
}
.ui-widget-shadow {
margin: -5px 0 0 -5px;
padding: 5px;
background: #000000 url("images/ui-bg_flat_10_000000_40x100.png") 50% 50% repeat-x;
opacity: .2;
filter: Alpha(Opacity=20); /* support: IE8 */
border-radius: 5px;
}

View File

@ -1,25 +1,8 @@
@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");
html, body {
height: 100%;
}
img {
max-width: 100%;
height: auto;
}
.footer {
padding-top: 30px;
padding-bottom: 30px;
float: bottom;
bottom: 0;
}
label {
font-size: 16px;
}
#language-selector { #language-selector {
width: 130px; width: 130px;
@ -47,7 +30,6 @@ label {
font-size: 15px; font-size: 15px;
} }
/* index css */
.jumbotron { .jumbotron {
text-align: center; text-align: center;
background-color: transparent; background-color: transparent;

View File

@ -0,0 +1,45 @@
/* surrounding tag container */
.tag-editor {
list-style-type: none; padding: 0 5px 0 0; margin: 0; overflow: hidden; border: 1px solid #eee; cursor: text;
font: normal 14px sans-serif; color: #555; background: #fff;
}
/* core styles usually need no change */
.tag-editor li { display: block; float: left; overflow: hidden; margin: 3px 0; line-height: 1.5; }
.tag-editor div { float: left; padding: 0 4px; }
.tag-editor .placeholder { padding: 0 8px; color: #bbb; }
.tag-editor .tag-editor-spacer { padding: 0; width: 8px; overflow: hidden; color: transparent; background: none; }
.tag-editor input {
vertical-align: inherit; border: 0; outline: none; padding: 0; margin: 0; cursor: text;
font-family: inherit; font-weight: inherit; font-size: inherit; font-style: inherit;
box-shadow: none; background: none;
}
/* tag style */
.tag-editor .tag-editor-tag {
padding-left: 5px; color: #46799b; background: #e0eaf1; white-space: nowrap;
overflow: hidden; cursor: pointer; border-radius: 2px 0 0 2px;
}
/* delete icon */
.tag-editor .tag-editor-delete { background: #e0eaf1; cursor: pointer; padding-right: 5px; border-radius: 0 2px 2px 0; }
.tag-editor .tag-editor-delete i {
display: inline-block; width: 7px; height: 7px; vertical-align: middle; background: url(/static/img/delete.png) 0 0 no-repeat;
position: relative; top: -1px;
}
.tag-editor .tag-editor-delete:hover i { background-position: 0 -14px; }
.tag-editor .tag-editor-tag.active+.tag-editor-delete,
.tag-editor .tag-editor-tag.active+.tag-editor-delete i { background: none; cursor: text; }
.tag-editor .tag-editor-tag.active { background: none !important; }
/* jQuery UI autocomplete - code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css */
.ui-autocomplete { position: absolute; top: 0; left: 0; cursor: default; font-size: 14px; }
.ui-front { z-index: 9999; }
.ui-menu { list-style: none; padding: 1px; margin: 0; display: block; outline: none; }
.ui-menu .ui-menu-item a { text-decoration: none; display: block; padding: 2px .4em; line-height: 1.4; min-height: 0; /* support: IE7 */ }
.ui-widget-content { border: 1px solid #bbb; background: #fff; color: #555; }
.ui-widget-content a { color: #46799b; }
.ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus,
.ui-widget-header .ui-state-focus { background: #e0eaf1; }
.ui-helper-hidden-accessible { display: none; }

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?
<svg id="svg2" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" y="0px" xml:space="preserve" height="7" viewBox="0 0 6.8000018 7.0000004" width="6.8" version="1.1" enable-background="new 0 0 16 16" x="0px">
<path id="path4" fill="#d40000" d="m3.9834,3.5,2.6753-2.6753c0.18847-0.18847,0.18847-0.49485,0-0.68332-0.18846-0.18847-0.49485-0.18847-0.68332,0l-2.6754,2.6751-2.6753-2.6752c-0.18847-0.18847-0.49485-0.18847-0.68332,0-0.18847,0.18846-0.18847,0.49485,0,0.68332l2.6751,2.6753-2.6751,2.6752c-0.18847,0.18847-0.18847,0.49485,0,0.68332s0.49485,0.18847,0.68332,0l2.6753-2.6752,2.6752,2.6753c0.18847,0.18847,0.49485,0.18847,0.68332,0s0.18847-0.49485,0-0.68332l-2.6752-2.6753z"/>
</svg>

View File

@ -0,0 +1,47 @@
define("admin", ["jquery", "avalon"], function ($, avalon) {
avalon.ready(function () {
function li_active(selector) {
$(selector).attr("class", "list-group-item active");
}
function li_inactive(selector) {
$(".list-group-item").attr("class", "list-group-item");
}
function show_template(url) {
$("#loading-gif").show();
vm.template_url = url;
}
var hash = window.location.hash.substring(1);
if (!hash) {
hash = "index/index";
}
var vm = avalon.define({
$id: "admin",
template_url: "template/" + hash + ".html",
hide_loading: function () {
$("#loading-gif").hide();
}
});
avalon.scan();
li_active("#li-" + hash.replace("/", "-"));
window.onhashchange = function () {
var hash = window.location.hash.substring(1);
if (hash) {
li_inactive(".list-group-item");
li_active("#li-" + hash.replace("/", "-"));
show_template("template/" + hash + ".html");
}
};
});
});

View File

@ -0,0 +1,171 @@
require(["jquery", "avalon", "csrf", "bs_alert", "editor", "validation"], function ($, avalon, csrfHeader, bs_alert, editor) {
avalon.vmodels.announcement = null;
// avalon:定义模式 announcement
avalon.ready(function () {
var announcementEditor = editor("#editor"); //创建新建公告的内容编辑器
var editAnnouncementEditor = editor("#editAnnouncementEditor");
var vm = avalon.define({
$id: "announcement",
//通用变量
announcement: [], // 公告列表数据项
previous_page: 0, // 之前的页数
next_page: 0, // 之后的页数
page: 1, // 当前页数
isEditing: 0, // 正在编辑的公告的ID 为零说明未在编辑
page_count: 1, // 总页数
visableOnly: false, //仅显示可见公告
// 编辑
announcementVisible: 0,
getState: function (el) { //获取公告当前状态,显示
if (el.visible)
return "可见";
else
return "隐藏";
},
getNext: function () {
if (!vm.next_page)
return;
getPageData(vm.page + 1);
},
getPrevious: function () {
if (!vm.previous_page)
return;
getPageData(vm.page - 1);
},
getBtnClass: function (btn) { //上一页/下一页按钮启用禁用逻辑
if (btn) {
return vm.next_page ? "btn btn-primary" : "btn btn-primary disabled";
}
else {
return vm.previous_page ? "btn btn-primary" : "btn btn-primary disabled";
}
},
enEdit: function (el) { //点击编辑按钮的事件,显示/隐藏编辑区
$("#newTitle").val(el.title);
editAnnouncementEditor.setValue(el.content);
vm.announcementVisible = el.visible;
if (vm.isEditing == el.id)
vm.isEditing = 0;
else
vm.isEditing = el.id;
editAnnouncementEditor.focus();
},
disEdit: function () { //收起编辑框
vm.isEditing = 0;
},
submitChange: function () { // 处理编辑公告提交事件,顺便验证字段为空
var title = $("#newTitle").val(), content = editAnnouncementEditor.getValue();
if (title != "") {
if (content != "") {
$.ajax({ //发送修改公告请求
beforeSend: csrfHeader,
url: "/api/admin/announcement/",
dataType: "json",
method: "put",
data: {
id: vm.isEditing,
title: title,
content: content,
visible: vm.announcementVisible
},
success: function (data) {
if (!data.code) {
bs_alert("修改成功");
vm.isEditing = 0;
getPageData(1);
}
else {
bs_alert(data.data);
}
}
});
}
else
bs_alert("公告内容不得为空");
}
else
bs_alert("公告标题不能为空");
}
});
vm.$watch("visableOnly", function () {
getPageData(1);
});
avalon.scan();
getPageData(1); //公告列表初始化
//Ajax get数据
function getPageData(page) {
var visible = '';
if (vm.visableOnly == true)
visible = "&visible=true";
$.ajax({
beforeSend: csrfHeader,
url: "/api/announcements/?paging=true&page=" + page + "&page_size=10" + visible,
dataType: "json",
method: "get",
success: function (data) {
if (!data.code) {
vm.announcement = data.data.results;
vm.page_count = data.data.total_page;
vm.previous_page = data.data.previous_page;
vm.next_page = data.data.next_page;
vm.page = page;
}
else {
bs_alert(data.data);
}
}
});
}
//新建公告表单验证与数据提交
$("#announcement-form")
.formValidation({
framework: "bootstrap",
fields: {
title: {
validators: {
notEmpty: {
message: "请填写公告标题"
}
}
}
}
}
).on('success.form.fv', function (e) {
e.preventDefault();
var title = $("#title").val();
var content = announcementEditor.getValue();
if (content == "") {
bs_alert("请填写公告内容");
return;
}
$.ajax({
beforeSend: csrfHeader,
url: "/api/admin/announcement/",
data: {title: title, content: content},
dataType: "json",
method: "post",
success: function (data) {
if (!data.code) {
bs_alert("提交成功!");
$("#title").val("");
announcementEditor.setValue("");
getPageData(1);
} else {
bs_alert(data.data);
}
}
})
});
});
});

View File

@ -1,7 +1,7 @@
require(["jquery", "avalon", "editor", "uploader", "datetimepicker", require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
"validation" "validation","tagEditor"],
],
function ($, avalon, editor, uploader) { function ($, avalon, editor, uploader) {
avalon.vmodels.add_contest = null;
$("#add-contest-form") $("#add-contest-form")
.formValidation({ .formValidation({
framework: "bootstrap", framework: "bootstrap",
@ -18,6 +18,13 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
} }
} }
}, },
description: {
validators: {
notEmpty: {
message: "请输入描述"
}
}
},
start_time: { start_time: {
validators: { validators: {
notEmpty: { notEmpty: {
@ -92,9 +99,25 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
}) })
.on("success.form.fv", function (e) { .on("success.form.fv", function (e) {
e.preventDefault(); e.preventDefault();
alert("1111"); var data = {
title: vm.title, description: vm.description, start_time: vm.startTime, end_time: vm.endTime,
password: vm.password, model: vm.model, open_rank: vm.openRank, problems: []
};
for (var i = 0; i < vm.problems.length; i++) {
var problem = {
title: vm.problems[i].title, description: vm.problems[i].description,
cpu: vm.problems[i].cpu, memory: vm.problems[i].memory, samples: []
};
for (var j = 0; j < vm.problems[i].samples.length; j++) {
problem.samples.push({
input: vm.problems[i].samples[j].input,
output: vm.problems[i].samples[j].output
})
}
data.problems.push(problem);
}
console.log(data);
}); });
function make_id() { function make_id() {
var text = ""; var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@ -102,21 +125,59 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
text += possible.charAt(Math.floor(Math.random() * possible.length)); text += possible.charAt(Math.floor(Math.random() * possible.length));
return text; return text;
} }
var editor1 = editor("#editor"); var editor1 = editor("#editor");
var vm = avalon.define({ var vm = avalon.define({
$id: "add_contest", $id: "add_contest",
title: "",
description: "",
startTime: "",
endTime: "",
password: "",
model: "",
openRank: false,
problems: [], problems: [],
add_problem: function () { add_problem: function () {
var problem = {};
var problem_id = make_id(); var problem_id = make_id();
problem["id"] = problem_id; var problem = {
problem["samples"] = []; id: problem_id,
problem["webuploader"] = {}; title: "",
problem["toggle_string"] = "折叠"; cpu: "",
memory: "",
description: "",
samples: [],
visible: true,
test_case_id: "",
testCaseList: [],
hint: "",
isVisible: false,
difficulty: 0,
tags: [],
tag: ""
};
vm.problems.push(problem); vm.problems.push(problem);
uploader("#problem-" + problem_id + "-uploader"); var id = vm.problems.length - 1;
console.log(vm.problems); editor("#problem-" + problem_id + "-description");
var hinteditor = editor("#problem-" + problem_id +"-hint");
$("#problem-" + problem_id +"-tags").tagEditor();
uploader("#problem-" + problem_id + "-uploader", "/api/admin/test_case_upload/", function (file, respond) {
console.log(respond);
if (respond.code)
bs_alert(respond.data);
else {
vm.problems[id].test_case_id = respond.data.test_case_id;
vm.problems[id].uploadSuccess = true;
vm.problems[id].testCaseList = [];
for (var i = 0; i < respond.data.file_list.input.length; i++) {
vm.problems[id].push({
input: respond.data.file_list.input[i],
output: respond.data.file_list.output[i]
});
}
}
});
$("#add-contest-form").formValidation('addField', $('[name="problem_name[]"]')); $("#add-contest-form").formValidation('addField', $('[name="problem_name[]"]'));
$("#add-contest-form").formValidation('addField', $('[name="cpu[]"]')); $("#add-contest-form").formValidation('addField', $('[name="cpu[]"]'));
$("#add-contest-form").formValidation('addField', $('[name="memory[]"]')); $("#add-contest-form").formValidation('addField', $('[name="memory[]"]'));
@ -126,31 +187,21 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
vm.problems.remove(problem); vm.problems.remove(problem);
} }
}, },
toggle_problem: function (problem) { toggle: function (item) {
$("#" + "problem-" + problem.id + "-body").toggle(); item.visible = !item.visible;
if (problem["toggle_string"] == "展开") {
problem["toggle_string"] = "折叠";
}
else {
problem["toggle_string"] = "展开";
}
}, },
add_sample: function (problem) { add_sample: function (problem) {
problem["samples"].push({"id": make_id(), "toggle_string": "折叠"}); problem.samples.push({id: make_id(), visible: true, input: "", output: ""});
}, },
del_sample: function (problem, sample) { del_sample: function (problem, sample) {
if (confirm("你确定要删除么?")) { if (confirm("你确定要删除么?")) {
problem["samples"].remove(sample); problem.samples.remove(sample);
} }
}, },
toggle_sample: function (problem, sample) { getBtnContent: function (item) {
$("#" + "problem-" + problem.id + "-sampleio-" + sample.id + "-body").toggle(); if (item.visible)
if (sample["toggle_string"] == "展开") { return "折叠";
sample["toggle_string"] = "折叠"; return "展开";
}
else {
sample["toggle_string"] = "展开";
}
} }
}); });
avalon.scan(); avalon.scan();
@ -167,7 +218,6 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
weekStart: 1, weekStart: 1,
language: "zh-CN" language: "zh-CN"
}); });
$("#contest_start_time").datetimepicker() $("#contest_start_time").datetimepicker()
.on("hide", function (ev) { .on("hide", function (ev) {
$("#add-contest-form") $("#add-contest-form")

View File

@ -0,0 +1,80 @@
require(["jquery", "chart"], function ($, Chart) {
var data2 = {
labels: ["January", "February", "March", "April", "May", "June", "July",
"January", "February", "March", "April", "January", "February", "March", "April"],
datasets: [
{
label: "2222222",
fillColor: "rgba(255,255,255,0.2)",
strokeColor: "rgba(151,187,205,1)",
pointColor: "rgba(151,187,205,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(151,187,205,1)",
data: [3, 7, 8, 9, 1, 4, 10, 10, 9, 8, 7, 10, 10, 10, 10]
}
]
};
new Chart($("#waiting-queue-chart").get(0).getContext("2d")).Line(data2);
var data = {
labels: ["January", "February", "March", "April", "May", "June", "July",
"January", "February", "March", "April", "January", "February", "March", "April"],
datasets: [
{
label: "11111111",
fillColor: "rgba(255,255,255,0.2)",
strokeColor: "rgba(250,68,68,1)",
pointColor: "rgba(220,220,220,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(220,220,220,1)",
data: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
},
{
label: "2222222",
fillColor: "rgba(255,255,255,0.2)",
strokeColor: "rgba(151,187,205,1)",
pointColor: "rgba(151,187,205,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(151,187,205,1)",
data: [3, 7, 8, 9, 1, 4, 10, 10, 9, 8, 7, 10, 10, 10, 10]
}
]
};
Chart.defaults.global.responsive = true;
new Chart($("#judge-instance-chart").get(0).getContext("2d")).Line(data);
var data1 = {
labels: ["January", "February", "March", "April", "May", "June", "July",
"January", "February", "March", "April", "January", "February", "March", "April"],
datasets: [
{
label: "2222222",
fillColor: "rgba(255,255,255,0.2)",
strokeColor: "rgba(151,187,205,1)",
pointColor: "rgba(151,187,205,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(151,187,205,1)",
data: [3, 7, 8, 9, 1, 4, 10, 10, 9, 8, 7, 10, 10, 10, 10]
},
{
label: "2222222",
fillColor: "rgba(255,255,255,0.2)",
strokeColor: "rgba(252,214,48,1)",
pointColor: "rgba(252,214,48,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(151,187,205,1)",
data: [30, 70, 58, 49, 19, 44, 100, 100, 89, 88, 77, 50, 80, 66, 100]
}
]
};
Chart.defaults.global.responsive = true;
new Chart($("#c1").get(0).getContext("2d")).Line(data1);
});

View File

@ -0,0 +1,209 @@
require(["jquery", "avalon", "editor", "uploader", "bs_alert", "csrf", "tagEditor", "validation", "jqueryUI"],
function ($, avalon, editor, uploader, bs_alert, csrfHeader) {
avalon.vmodels.add_problem = null;
$("#add-problem-form")
.formValidation({
framework: "bootstrap",
fields: {
title: {
validators: {
notEmpty: {
message: "请填写题目名称"
},
stringLength: {
min: 1,
max: 30,
message: "名称不能超过30个字"
}
}
},
cpu: {
validators: {
notEmpty: {
message: "请输入时间限制"
},
integer: {
message: "请输入一个合法的数字"
},
between: {
inclusive: true,
min: 1,
max: 5000,
message: "只能在1-5000之间"
}
}
},
memory: {
validators: {
notEmpty: {
message: "请输入内存限制"
},
integer: {
message: "请输入一个合法的数字"
}
}
},
difficulty: {
validators: {
notEmpty: {
message: "请输入难度"
},
integer: {
message: "难度用一个整数表示"
}
}
},
source: {
validators: {
notEmpty: {
message: "请输入题目来源"
}
}
}
}
})
.on("success.form.fv", function (e) {
e.preventDefault();
if (vm.test_case_id == '')
{
bs_alert("你还没有上传测试数据!");
return;
}
if (vm.description == '')
{
bs_alert("题目描述不能为空!");
return;
}
if (vm.hint == '')
{
bs_alert("提示不能为空!");
return;
}
var ajaxData = {
title: vm.title,
description: vm.description,
time_limit: vm.cpu,
memory_limit: vm.memory,
samples: [],
test_case_id: vm.test_case_id,
hint: vm.hint,
source: vm.source,
tags: [],
difficulty: vm.difficulty
};
if (vm.samples.length == 0)
{
bs_alert("请至少添加一组样例!");
return;
}
var tags = $("#tags").tagEditor("getTags")[0].tags;
if (tags.length == 0)
{
bs_alert("请至少添加一个标签,这将有利于用户发现你的题目!");
return;
}
for (key in vm.samples.length) {
ajaxData.samples.push({input: vm.samples[key].input, output: vm.samples[key].output});
}
for (key in tags) {
ajaxData.tags.push(tags[key].tag);
}
console.log(ajaxData);
$.ajax({
beforeSend: csrfHeader,
url: "/api/admin/problem/",
dataType: "json",
data:ajaxData,
method: "post",
success: function (data) {
if (!data.code) {
bs_alert("successful!");
console.log(data);
}
else {
bs_alert(data.data);
}
}
})
});
var problemDiscription = editor("#problemDescription");
var testCaseUploader = uploader("#testCaseFile", "/api/admin/test_case_upload/", function (file, respond) {
if (respond.code)
bs_alert(respond.data);
else {
vm.test_case_id = respond.data.test_case_id;
vm.uploadSuccess = true;
vm.testCaseList = [];
for (var i = 0; i < respond.data.file_list.input.length; i++) {
vm.testCaseList.push({
input: respond.data.file_list.input[i],
output: respond.data.file_list.output[i]
});
}
}
});
var hinteditor = editor("#hint");
var tagList = [], completeList = [];
var vm = avalon.define({
$id: "add_problem",
title: "",
description: "",
cpu: 1000,
memory: 256,
samples: [],
hint: "",
visible: false,
difficulty: 0,
tags: [],
tag: "",
test_case_id: "",
testCaseList: [],
uploadSuccess: false,
source: "",
add_sample: function () {
vm.samples.push({input: "", output: "", "visible": true});
},
del_sample: function (sample) {
if (confirm("你确定要删除么?")) {
vm.samples.remove(sample);
}
},
toggle_sample: function (sample) {
sample.visible = !sample.visible;
},
getBtnContent: function (item) {
if (item.visible)
return "折叠";
return "展开";
}
});
$.ajax({
beforeSend: csrfHeader,
url: "/api/admin/tag/",
dataType: "json",
method: "get",
success: function (data) {
if (!data.code) {
tagList = data.data;
completeList = [];
for (key in tagList) {
completeList.push(tagList[key].name);
}
$("#tags").tagEditor({
autocomplete: {
delay: 0, // show suggestions immediately
position: {collision: 'flip'}, // automatic menu position up/down
source: completeList
}
});
}
else {
bs_alert(data.data);
}
}
});
avalon.scan();
});

View File

@ -0,0 +1,165 @@
require(["jquery", "avalon", "csrf", "bs_alert", "validation"], function ($, avalon, csrfHeader, bs_alert) {
avalon.vmodels.user_list = null;
// avalon:定义模式 user_list
avalon.ready(function () {
var vm = avalon.define({
$id: "user_list",
//通用变量
user_list: [], // 用户列表数据项
previous_page: 0, // 之前的页数
next_page: 0, // 之后的页数
page: 1, // 当前页数
isEditing: 0, // 正在编辑的公告的ID 为零说明未在编辑
page_count: 1, // 总页数
user_type: ["一般用户", "管理员", "超级管理员"],
key_word: "",
showAdminOnly: false,
//编辑区域同步变量
username: "",
real_name: "",
email: "",
admin_type: 0,
id: 0,
last_login: "",
create_time: "",
getNext: function () {
if (!vm.next_page)
return;
getPageData(vm.page + 1);
},
getPrevious: function () {
if (!vm.previous_page)
return;
getPageData(vm.page - 1);
},
getBtnClass: function (btn) { //上一页/下一页按钮启用禁用逻辑
if (btn) {
return vm.next_page ? "btn btn-primary" : "btn btn-primary disabled";
}
else {
return vm.previous_page ? "btn btn-primary" : "btn btn-primary disabled";
}
},
enEdit: function (el) { //点击编辑按钮的事件,显示/隐藏编辑区
vm.username = el.username;
vm.real_name = el.real_name;
vm.admin_type = el.admin_type;
vm.email = el.email;
vm.id = el.id;
if (vm.isEditing == el.id)
vm.isEditing = 0;
else
vm.isEditing = el.id;
},
getPage: function (page_index) {
getPageData(page_index);
}
});
vm.$watch("showAdminOnly", function () {
getPageData(1);
});
avalon.scan();
getPageData(1); //用户列表初始化
//Ajax get数据
function getPageData(page) {
var url = "/api/admin/users/?paging=true&page=" + page + "&page_size=10";
if (vm.showAdminOnly == true)
url += "&admin_type=1";
if (vm.key_word != "")
url += "&keyword=" + vm.key_word;
$.ajax({
beforeSend: csrfHeader,
url: url,
dataType: "json",
method: "get",
success: function (data) {
if (!data.code) {
vm.user_list = data.data.results;
vm.page_count = data.data.total_page;
vm.previous_page = data.data.previous_page;
vm.next_page = data.data.next_page;
vm.page = page;
}
else {
bs_alert(data.data);
}
}
});
}
$("#edit_user-form")
.formValidation({
framework: "bootstrap",
fields: {
username: {
validators: {
notEmpty: {
message: "请填写用户名"
},
stringLength: {
min: 3,
max: 30,
message: '用户名长度必须在3到30位之间'
}
}
},
real_name: {
validators: {
notEmpty: {
message: "请填写真实姓名"
}
}
},
email: {
validators: {
notEmpty: {
message: "请填写电子邮箱邮箱地址"
},
emailAddress: {
message: "请填写有效的邮箱地址"
}
}
},
password: {
validators: {
stringLength: {
min: 6,
max: 30,
message: '密码长度必须在6到30位之间'
}
}
}
}
}
).on('success.form.fv', function (e) {
e.preventDefault();
var data = {
username: vm.username,
real_name: vm.real_name,
email: vm.email,
id: vm.id,
admin_type: vm.admin_type
};
if ($("#password").val() !== "")
data.password = $("#password").val();
$.ajax({
beforeSend: csrfHeader,
url: "/api/admin/user/",
data: data,
dataType: "json",
method: "put",
success: function (data) {
if (!data.code) {
bs_alert("提交成功!");
getPageData(1);
$("#password").val("");
} else {
bs_alert(data.data);
}
}
})
});
});
})

View File

@ -0,0 +1,69 @@
require(["jquery", "bs_alert", "csrf", "validation"], function ($, bs_alert, csrfHeader) {
$("#change_password-form").formValidation({
framework: "bootstrap",
fields: {
username: {
validators: {
notEmpty: {
message: "请填写用户名"
}
}
},
password: {
validators: {
notEmpty: {
message: "请填写旧密码"
}
}
},
new_password: {
validators: {
notEmpty: {
message: "请填写新密码"
},
stringLength: {
min: 6,
max: 30,
message: '密码长度必须在6到30位之间'
}
},
onSuccess: function (e, data) {
data.fv.revalidateField('confirm_password');
}
},
confirm_password: {
validators: {
notEmpty: {
message: "请填写确认密码"
},
confirm: {
original: $("#new_password"),
message: "两次输入的密码必须一致"
}
}
}
}
}
).on('success.form.fv', function (e) {
e.preventDefault();
var username = $("#username").val();
var new_password = $("#new_password ").val();
var password = $("#password").val();
$.ajax({
beforeSend: csrfHeader,
url: "/api/change_password/",
data: {username: username, new_password: new_password, old_password: password},
dataType: "json",
method: "post",
success: function (data) {
if (!data.code) {
window.location.href = "/login/";
}
else {
bs_alert(data.data);
}
}
})
});
});

View File

@ -1,6 +1,6 @@
require(["jquery", "bs_alert", "validation"], function($, bs_alert){ require(["jquery", "bs_alert", "csrf", "validation"], function ($, bs_alert, csrfHeader) {
$("#login-form") $("#login-form")
.formValidation({ .formValidation({
framework: "bootstrap", framework: "bootstrap",
fields: { fields: {
username: { username: {
@ -19,20 +19,21 @@ require(["jquery", "bs_alert", "validation"], function($, bs_alert){
} }
} }
} }
).on('success.form.fv', function(e) { ).on('success.form.fv', function (e) {
e.preventDefault(); e.preventDefault();
var username = $("#username").val(); var username = $("#username").val();
var password = $("#password").val(); var password = $("#password").val();
$.ajax({ $.ajax({
beforeSend: csrfHeader,
url: "/api/login/", url: "/api/login/",
data: {username: username, password: password}, data: {username: username, password: password},
dataType: "json", dataType: "json",
method: "post", method: "post",
success: function (data) { success: function (data) {
if(!data.code){ if (!data.code) {
window.location.href="/"; window.location.href = "/";
} }
else{ else {
bs_alert(data.data); bs_alert(data.data);
} }
} }

View File

@ -0,0 +1,95 @@
require(["jquery", "bs_alert", "csrf", "validation"], function ($, bs_alert, csrfHeader) {
$("#register-form")
.formValidation({
framework: "bootstrap",
fields: {
username: {
validators: {
notEmpty: {
message: "请填写用户名"
},
stringLength: {
min: 3,
max: 30,
message: '用户名长度必须在3到30位之间'
},
remote: {
message: "用户名已存在",
url: "/api/username_check/",
field: 'username'
}
}
},
password: {
validators: {
notEmpty: {
message: "请填写密码"
},
stringLength: {
min: 6,
max: 30,
message: '密码长度必须在6到30位之间'
}
},
onSuccess: function (e, data) {
data.fv.revalidateField('confirm_password');
}
},
real_name: {
validators: {
notEmpty: {
message: "请填写真实姓名"
}
}
},
confirm_password: {
validators: {
notEmpty: {
message: "请填写确认密码"
},
confirm: {
original: $("#password"),
message: "两次输入的密码必须一致"
}
}
},
email: {
validators: {
notEmpty: {
message: "请填写电子邮箱邮箱地址"
},
emailAddress: {
message: "请填写有效的邮箱地址"
},
remote: {
message: "您已经注册过了",
url: "/api/email_check/",
field: 'email'
}
}
}
}
}
).on('success.form.fv', function (e) {
e.preventDefault();
var username = $("#username").val();
var real_name = $("#real_name").val();
var password = $("#password").val();
var email = $("#email").val();
$.ajax({
beforeSend: csrfHeader,
url: "/api/register/",
data: {username: username, real_name: real_name, password: password, email: email},
dataType: "json",
method: "post",
success: function (data) {
if (!data.code) {
window.location.href = "/login/";
}
else {
bs_alert(data.data);
}
}
})
});
});

View File

@ -14,7 +14,11 @@ var require = {
bs_alert: "utils/bs_alert", bs_alert: "utils/bs_alert",
submit_code: "app/oj/problem/submit_code", submit_code: "app/oj/problem/submit_code",
contest: "app/admin/contest/contest", contest: "app/admin/contest/contest",
csrf: "utils/csrf",
admin: "app/admin/admin",
chart: "lib/chart/Chart",
tagEditor: "lib/tagEditor/jquery.tag-editor.min",
jqueryUI: "lib/jqueryUI/jquery-ui",
//formValidation 不要在代码中单独使用而是使用和修改utils/validation //formValidation 不要在代码中单独使用而是使用和修改utils/validation
base: "lib/formValidation/base", base: "lib/formValidation/base",
helper: "lib/formValidation/helper", helper: "lib/formValidation/helper",
@ -25,7 +29,9 @@ var require = {
"validator/date": "lib/formValidation/validator/date", "validator/date": "lib/formValidation/validator/date",
"validator/integer": "lib/formValidation/validator/integer", "validator/integer": "lib/formValidation/validator/integer",
"validator/between": "lib/formValidation/validator/between", "validator/between": "lib/formValidation/validator/between",
"validator/confirm":"lib/formValidation/validator/confirm",
"validator/remote":"lib/formValidation/validator/remote",
"validator/emailAddress":"lib/formValidation/validator/emailAddress",
//富文本编辑器 不要直接使用而是使用上面的editor //富文本编辑器 不要直接使用而是使用上面的editor
simditor: "lib/simditor/simditor", simditor: "lib/simditor/simditor",
"simple-module": "lib/simditor/module", "simple-module": "lib/simditor/module",

3477
static/src/js/lib/chart/Chart.js vendored Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
/**
* confirm validator
*/
(function(root, factory) {
"use strict";
// AMD module is defined
if (typeof define === "function" && define.amd) {
define("validator/confirm", ["jquery", "base"], factory);
} else {
// planted over the root!
factory(root.jQuery, root.FormValidation);
}
}(this, function ($, FormValidation) {
FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
'en_US': {
confirm: {
'default': 'Please input the same value'
}
}
});
FormValidation.Validator.confirm = {
validate: function(validator, $field, options) {
if (options.original.val() == $field.val() || $field.val()== '')
return true;
return false;
}
};
}));

View File

@ -1,146 +1,48 @@
/** /**
* remote validator * remote validator
*
* @link http://formvalidation.io/validators/remote/
* @author https://twitter.com/nghuuphuoc
* @copyright (c) 2013 - 2015 Nguyen Huu Phuoc
* @license http://formvalidation.io/license/
*/ */
(function(root, factory) { (function(root, factory) {
"use strict"; "use strict";
// AMD module is defined // AMD module is defined
if (typeof define === "function" && define.amd) { if (typeof define === "function" && define.amd) {
define("validator/remote", ["jquery", "base"], factory); define("validator/remote", ["jquery", "base", "csrf"], factory);
} else { } else {
// planted over the root! // planted over the root!
factory(root.jQuery, root.FormValidation); factory(root.jQuery, root.FormValidation);
} }
}(this, function ($, FormValidation, csrfHeader) {
}(this, function ($, FormValidation) {
FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, { FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
'en_US': { 'en_US': {
remote: { remote: {
'default': 'Please enter a valid value' 'default': ''
} }
} }
}); });
FormValidation.Validator.remote = { FormValidation.Validator.remote = {
html5Attributes: {
message: 'message',
name: 'name',
type: 'type',
url: 'url',
data: 'data',
delay: 'delay'
},
/**
* Destroy the timer when destroying the bootstrapValidator (using validator.destroy() method)
*/
destroy: function(validator, $field, options) {
var ns = validator.getNamespace(),
timer = $field.data(ns + '.remote.timer');
if (timer) {
clearTimeout(timer);
$field.removeData(ns + '.remote.timer');
}
},
/**
* Request a remote server to check the input value
*
* @param {FormValidation.Base} validator Plugin instance
* @param {jQuery} $field Field element
* @param {Object} options Can consist of the following keys:
* - url {String|Function}
* - type {String} [optional] Can be GET or POST (default)
* - data {Object|Function} [optional]: By default, it will take the value
* {
* <fieldName>: <fieldValue>
* }
* - delay
* - name {String} [optional]: Override the field name for the request.
* - message: The invalid message
* - headers: Additional headers
* @returns {Deferred}
*/
validate: function(validator, $field, options) { validate: function(validator, $field, options) {
var ns = validator.getNamespace(), var dfd = new $.Deferred(), ajaxData = {};
value = validator.getFieldValue($field, 'remote'), ajaxData[options.field] = $field.val();
dfd = new $.Deferred(); if ($field.val() === '')
if (value === '') { return true;
dfd.resolve($field, 'remote', { valid: true }); var url = options.url;
return dfd; var xhr = $.ajax({
} beforeSend: csrfHeader,
url: url,
var name = $field.attr('data-' + ns + '-field'), dataType: 'json',
data = options.data || {}, data: ajaxData,
url = options.url, method: "post"
type = options.type || 'GET', });
headers = options.headers || {}; xhr.success(function(response) {
if (response.code == 1)
// Support dynamic data dfd.resolve($field, 'remote',{valid:true, message:options.msg});
if ('function' === typeof data) { dfd.resolve($field, 'remote',{valid:!response.data, message:options.msg});
data = data.call(this, validator); })
} .error(function(response) {
dfd.resolve($field, 'remote', {valid: false});
// Parse string data from HTML5 attribute });
if ('string' === typeof data) { return dfd;
data = JSON.parse(data);
}
// Support dynamic url
if ('function' === typeof url) {
url = url.call(this, validator);
}
data[options.name || name] = value;
function runCallback() {
var xhr = $.ajax({
type: type,
headers: headers,
url: url,
dataType: 'json',
data: data
});
xhr
.success(function(response) {
response.valid = response.valid === true || response.valid === 'true';
dfd.resolve($field, 'remote', response);
})
.error(function(response) {
dfd.resolve($field, 'remote', {
valid: false
});
});
dfd.fail(function() {
xhr.abort();
});
return dfd;
}
if (options.delay) {
// Since the form might have multiple fields with the same name
// I have to attach the timer to the field element
if ($field.data(ns + '.remote.timer')) {
clearTimeout($field.data(ns + '.remote.timer'));
}
$field.data(ns + '.remote.timer', setTimeout(runCallback, options.delay));
return dfd;
} else {
return runCallback();
}
} }
}; };
return FormValidation.Validator.remote;
})); }));

View File

@ -0,0 +1,146 @@
/**
* remote validator
*
* @link http://formvalidation.io/validators/remote/
* @author https://twitter.com/nghuuphuoc
* @copyright (c) 2013 - 2015 Nguyen Huu Phuoc
* @license http://formvalidation.io/license/
*/
(function(root, factory) {
"use strict";
// AMD module is defined
if (typeof define === "function" && define.amd) {
define("validator/remote", ["jquery", "base"], factory);
} else {
// planted over the root!
factory(root.jQuery, root.FormValidation);
}
}(this, function ($, FormValidation) {
FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
'en_US': {
remote: {
'default': 'Please enter a valid value'
}
}
});
FormValidation.Validator.remote = {
html5Attributes: {
message: 'message',
name: 'name',
type: 'type',
url: 'url',
data: 'data',
delay: 'delay'
},
/**
* Destroy the timer when destroying the bootstrapValidator (using validator.destroy() method)
*/
destroy: function(validator, $field, options) {
var ns = validator.getNamespace(),
timer = $field.data(ns + '.remote.timer');
if (timer) {
clearTimeout(timer);
$field.removeData(ns + '.remote.timer');
}
},
/**
* Request a remote server to check the input value
*
* @param {FormValidation.Base} validator Plugin instance
* @param {jQuery} $field Field element
* @param {Object} options Can consist of the following keys:
* - url {String|Function}
* - type {String} [optional] Can be GET or POST (default)
* - data {Object|Function} [optional]: By default, it will take the value
* {
* <fieldName>: <fieldValue>
* }
* - delay
* - name {String} [optional]: Override the field name for the request.
* - message: The invalid message
* - headers: Additional headers
* @returns {Deferred}
*/
validate: function(validator, $field, options) {
var ns = validator.getNamespace(),
value = validator.getFieldValue($field, 'remote'),
dfd = new $.Deferred();
if (value === '') {
dfd.resolve($field, 'remote', { valid: true });
return dfd;
}
var name = $field.attr('data-' + ns + '-field'),
data = options.data || {},
url = options.url,
type = options.type || 'GET',
headers = options.headers || {};
// Support dynamic data
if ('function' === typeof data) {
data = data.call(this, validator);
}
// Parse string data from HTML5 attribute
if ('string' === typeof data) {
data = JSON.parse(data);
}
// Support dynamic url
if ('function' === typeof url) {
url = url.call(this, validator);
}
data[options.name || name] = value;
function runCallback() {
var xhr = $.ajax({
type: type,
headers: headers,
url: url,
dataType: 'json',
data: data
});
xhr
.success(function(response) {
response.valid = response.valid === true || response.valid === 'true';
dfd.resolve($field, 'remote', response);
})
.error(function(response) {
dfd.resolve($field, 'remote', {
valid: false
});
});
dfd.fail(function() {
xhr.abort();
});
return dfd;
}
if (options.delay) {
// Since the form might have multiple fields with the same name
// I have to attach the timer to the field element
if ($field.data(ns + '.remote.timer')) {
clearTimeout($field.data(ns + '.remote.timer'));
}
$field.data(ns + '.remote.timer', setTimeout(runCallback, options.delay));
return dfd;
} else {
return runCallback();
}
}
};
return FormValidation.Validator.remote;
}));

2610
static/src/js/lib/jqueryUI/jquery-ui.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,23 @@
define("csrf",function(){
function get_cookie(cookie_name) {
var name = cookie_name + "=";
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) return c.substring(name.length, c.length);
}
return "";
}
function csrfHeader(){
// jquery的请求
if(arguments.length == 2) {
arguments[0].setRequestHeader("X-CSRFToken", get_cookie("csrftoken"));
}
// 百度webuploader 的请求
else if(arguments.length == 3){
arguments[2]["X-CSRFToken"] = get_cookie("csrftoken");
}
}
return csrfHeader;
});

View File

@ -1,20 +1,23 @@
define("uploader", ["webuploader"], function(webuploader){ define("uploader", ["webuploader", "csrf"], function(webuploader,csrf){
function uploader(selector) { function uploader(selector, server, onSuccess) {
return webuploader.create({ var Webuploader= webuploader.create({
auto: true,
// swf文件路径 // swf文件路径
swf: "/js/Uploader.swf", swf: "/static/img/Uploader.swf",
// 文件接收服务端。 // 文件接收服务端。
server: "http://webuploader.duapp.com/server/fileupload.php", server: server,
// 选择文件的按钮。可选。 // 选择文件的按钮。可选。
// 内部根据当前运行是创建可能是input元素也可能是flash. // 内部根据当前运行是创建可能是input元素也可能是flash.
pick: selector, pick: selector,
// 不压缩image, 默认如果是jpeg文件上传前会压缩一把再上传 // 不压缩image, 默认如果是jpeg文件上传前会压缩一把再上传
resize: false resize: false,
uploadBeforeSend : csrf
}); });
Webuploader.on("uploadBeforeSend",csrf);
Webuploader.on("uploadSuccess", onSuccess);
return Webuploader;
} }
return uploader; return uploader;
}); });

View File

@ -7,5 +7,9 @@ define("validation",
'validator/stringLength', 'validator/stringLength',
'validator/date', 'validator/date',
'validator/integer', 'validator/integer',
'validator/between'], function () { 'validator/between',
'validator/confirm',
'validator/remote',
'validator/emailAddress'],
function () {
}); });

150
template/admin/admin.html Normal file
View File

@ -0,0 +1,150 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="renderer" content="webkit">
<title>在线评测系统 - 后台管理</title>
<!-- custom css begin -->
{% block css_block %}{% endblock %}
<!-- custom css end -->
<!-- global css begin -->
<link href="/static/css/admin.css" rel="stylesheet">
<!-- global css end -->
</head>
<body>
<!-- nav begin -->
<nav class="navbar navbar-masthead navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
aria-expanded="false" aria-controls="navbar">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">qduoj admin</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="#">主页</a></li>
<li><a href="#about">题目</a></li>
<li><a href="#contact">提交</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">
李扬
<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">我的提交</a></li>
<li><a href="#">我的资料</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">退出</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<!-- nav end -->
<!--browser happy begin -->
<!--[if lt IE 9]>
<div class="alert alert-danger text-center" role="alert">
当前网页 <strong>不支持</strong> 你正在使用的浏览器. 为了正常的访问, 请 <a href="http://browsehappy.com/">升级你的浏览器</a>.
</div>
<![endif]-->
<!-- browser happy end -->
<div class="container main" ms-controller="admin">
<div class="row">
<!-- admin left begin-->
<div class="col-md-2">
<ul class="list-group">
<li class="list-group-header">首页</li>
<li class="list-group-item" id="li-index-index">
<a href="#index/index">主页</a>
</li>
<li class="list-group-item" id="li-monitor-monitor">
<a href="#monitor/monitor">监控</a>
</li>
<li class="list-group-item" id="li-statistics-statistics">
<a href="#statistics/statistics">统计</a>
</li>
<li class="list-group-item" id="li-announcement-announcement">
<a href="#announcement/announcement">公告</a>
</li>
<li class="list-group-header">题目管理</li>
<li class="list-group-item" id="li-problem-problem_list">
<a href="#problem/problem_list">题目列表</a>
</li>
<li class="list-group-item" id="li-problem-add_problem">
<a href="#problem/add_problem">创建题目</a>
</li>
<li class="list-group-header">比赛管理</li>
<li class="list-group-item" id="li-contest-contest_list">
<a href="#contest/contest_list">比赛列表</a>
</li>
<li class="list-group-item" id="li-contest-add_contest">
<a href="#contest/add_contest">创建比赛</a>
</li>
<li class="list-group-header">用户管理</li>
<li class="list-group-item" id="li-user-user_list">
<a href="#user/user_list">用户列表</a>
</li>
<li class="list-group-item" id="li-user-user_group">
<a href="#user/user_group">用户分组</a>
</li>
</ul>
</div>
<!-- admin left end -->
<img src="/static/img/loading.gif" id="loading-gif">
<!-- custom body begin -->
<div ms-include-src="template_url" data-include-rendered="hide_loading" data-include-replace="true"></div>
<!-- custom body end -->
</div>
</div>
<div class="modal fade" id="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title">提示</h4>
</div>
<div class="modal-body">
<p id="modal-text"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<script src="/static/js/config.js"></script>
<script src="/static/js/require.js"></script>
<script>
require(["bootstrap", "admin"]);
</script>
<!-- footer begin -->
<div class="footer">
<p class="text-muted text-center">Copyright © 2015 青岛大学信息工程学院 创新实验室</p>
</div>
<!-- footer end -->
</body>
</html>

View File

@ -0,0 +1,65 @@
<div ms-controller="announcement" class="col-md-9">
<h1>Announcement</h1>
<table class="table table-striped">
<tr>
<th>编号</th>
<th>标题</th>
<th>创建时间</th>
<th>更新时间</th>
<th>创建者</th>
<th>状态</th>
<th>操作</th>
</tr>
<tr ms-repeat="announcement">
<td>{{el.id}}</td>
<td>{{el.title}}</td>
<td>{{el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{el.last_update_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{el.created_by.username}}</td>
<td>{{getState(el)}}</td>
<td>
<button class="btn-sm btn-info" ms-click="enEdit(el)">编辑</button>
</td>
</tr>
</table>
<div class="form-group">
<label>仅显示可见 <input ms-duplex-checked="visableOnly" type="checkbox"/></label>
</div>
<div class="text-right">
页数:{{page}}/{{page_count}}&nbsp;&nbsp;
<botton ms-attr-class="getBtnClass(0)" ms-click="getPrevious">上一页</botton>
<botton ms-attr-class="getBtnClass(1)" ms-click="getNext">下一页</botton>
</div>
<div ms-visible="isEditing">
<h3>编辑公告</h3>
<div class="form-group"><label for="title">标题</label>
<input name="title" type="text" class="form-control" id="newTitle" placeholder="公告标题" value=""></div>
<div class="form-group">
<label>内容</label>
<textarea id="editAnnouncementEditor"></textarea>
</div>
<div class="form-group">
<label>可见 <input ms-duplex-checked="announcementVisible" type="checkbox"/></label>
</div>
<div class="form-group">
<button ms-click="submitChange()" class="btn btn-primary">提交</button>
&nbsp;&nbsp;
<button ms-click="disEdit()" class="btn btn-danger">取消</button>
</div>
</div>
<h3>添加公告</h3>
<form id="announcement-form">
<div class="form-group"><label for="title">标题</label>
<input name="title" type="text" class="form-control" id="title" placeholder="公告标题"></div>
<div class="form-group">
<label>内容</label>
<textarea id="editor" placeholder="公告内容"></textarea>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
</div>
<script src="/static/js/app/admin/announcement/announcement.js"></script>

View File

@ -1,185 +1,218 @@
{% extends "admin_base.html" %} <div ms-controller="add_contest">
{% block body %} <form id="add-contest-form">
{% verbatim %} <div class="col-md-9">
<div ms-controller="add_contest"> <div class="col-md-12">
<label>比赛名称</label>
</div>
<div class="col-md-12">
<div class="form-group">
<input type="text" name="name" class="form-control" ms-duplex="title">
</div>
</div>
<div class="col-md-12">
<label>说明</label>
</div>
<div class="col-md-12">
<div class="form-group">
<textarea id="editor" placeholder="这里输入内容" autofocus ms-duplex="description"></textarea>
<small ms-visible="description==''" style="color:red">请填写比赛描述</small>
</div>
</div>
<div class="col-md-6">
<label>开始时间</label>
</div>
<div class="col-md-6">
<label>结束时间</label>
</div>
<div class="col-md-6">
<div class="form-group">
<input type="text" class="form-control" name="start_time" id="contest_start_time"
ms-duplex="startTime">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<input type="text" class="form-control" name="end_time" id="contest_end_time" ms-duplex="endTime">
</div>
</div>
<div class="col-md-6">
<form id="add-contest-form"> <label>
<div class="col-md-9"> 密码保护
<div class="col-md-12"> </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">
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="password">
</div> </div>
<div class="col-md-12"> </div>
<div class="form-group"> <div class="col-md-3">
<input type="text" name="name" class="form-control"> <div class="form-group">
</div> <label><input type="radio" name="mode" ms-duplex-checked="model">
</div> <small>OI</small>
<div class="col-md-12"> </label>
<label>说明</label> <label><input type="radio" name="mode">
</div> <small>ACM</small>
<div class="col-md-12">
<div class="form-group">
<textarea id="editor" placeholder="这里输入内容" autofocus></textarea>
</div>
</div>
<div class="col-md-6">
<label>开始时间</label>
</div>
<div class="col-md-6">
<label>结束时间</label>
</div>
<div class="col-md-6">
<div class="form-group">
<input type="text" class="form-control" name="start_time" id="contest_start_time">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<input type="text" class="form-control" name="end_time" id="contest_end_time">
</div>
</div>
<div class="col-md-6">
<label>
密码保护
</label> </label>
</div> </div>
<div class="col-md-3"> </div>
<label> <div class="col-md-3">
模式 <div class="form-group">
<label class="text"><input type="checkbox" ms-duplex-checked="openRank">
<small>开放排名</small>
</label> </label>
</div> </div>
<div class="col-md-3"> </div>
<label>
结束前是否开放排名
</label>
</div>
<div class="col-md-6">
<div class="form-group">
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛">
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<input type="radio" name="mode">OI
<input type="radio" name="mode">ACM
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<input type="checkbox" value="open_rank">开放排名
</div>
</div>
<div class="col-md-12"> <div class="col-md-12">
<label>添加题目</label> <label>添加题目</label>
<a href="javascript:void(0)" class="btn btn-primary btn-sm" ms-click="add_problem()">添加</a> <a href="javascript:void(0)" class="btn btn-primary btn-sm" ms-click="add_problem()">添加</a>
</div> </div>
<div class="col-md-12"> <div class="col-md-12">
<div class="problem" ms-repeat-problem="problems"> <div class="problem" ms-repeat-problem="problems">
<div class="panel panel-default problem-panel" ms-attr-id="problem-{{ problem.id }}"> <div class="panel panel-default problem-panel" ms-attr-id="problem-{{ problem.id }}">
<div class="panel-heading"> <div class="panel-heading">
<span class="panel-title">题目{{$index + 1}} </span> <span class="panel-title">题目{{$index + 1}} </span>
<a href="javascript:void(0)" class="btn btn-primary btn-sm" <a href="javascript:void(0)" class="btn btn-primary btn-sm" ms-click="toggle(problem)">
ms-click="toggle_problem(problem)"> {{getBtnContent(problem)}}
{{ problem.toggle_string }} </a>
</a> <a href="javascript:void(0)" class="btn btn-danger btn-sm" ms-click="del_problem(problem)">
<a href="javascript:void(0)" class="btn btn-danger btn-sm" 删除
ms-click="del_problem(problem)"> </a>
删除 </div>
</a> <div class="panel-body" ms-visible="problem.visible">
<div class="col-md-12">
<label>题目</label>
</div> </div>
<div class="panel-body" ms-attr-id="problem-{{ problem.id }}-body">
<div class="col-md-12">
<label>题目</label>
</div>
<div class="col-md-12"> <div class="col-md-12">
<div class="form-group"> <div class="form-group">
<input type="text" name="problem_name[]" class="form-control"> <input type="text" name="problem_name[]" class="form-control"
</div> ms-duplex="problem.title">
</div> </div>
<div class="col-md-6"> </div>
<label>cpu</label> <label>题目描述</label>
</div> <textarea ms-attr-id="problem-{{ problem.id }}-description" placeholder="这里输入内容"
<div class="col-md-6"> ms-duplex="problem.description"></textarea>
<label>内存</label> <small ms-visible="problem.description==''" style="color:red">请填写题目描述</small>
</div> <div class="form-group">
<div class="col-md-6"> <label>提示</label>
<div class="form-group"> <textarea ms-attr-id="problem-{{ problem.id }}-hint" placeholder="这里输入内容" ms-duplex="problem.hint"></textarea>
<input type="text" name="cpu[]" class="form-control"> </div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<input type="text" name="memory[]" class="form-control">
</div>
</div>
<div class="col-md-12">
<label>样例</label> <div class="col-md-3">
<a href="javascript:void(0)" class="btn btn-primary btn-sm" <div class="form-group"><label>cpu</label>
ms-click="add_sample(problem)"> <input type="number" name="cpu[]" class="form-control" ms-duplex="problem.cpu">
添加 </div>
</a> </div>
<div class="col-md-3">
<div class="form-group"><label>内存</label>
<input type="number" name="memory[]" class="form-control" ms-duplex="problem.memory">
</div>
</div>
<div class="col-md-3">
<div class="form-group"><label>难度</label>
<input type="number" name="difficulty[]" class="form-control" ms-duplex="problem.difficulty">
</div>
</div>
<div class="col-md-3 form-group">
<label>前台是否可见</label><br>
<label><input type="checkbox" ms-duplex-checked="problem.isVisible">
<small> 可见</small>
</label>
</div>
<div id="tag" class="col-md-12">
<label>标签</label><br>
<input type="text" ms-attr-id="problem-{{ problem.id }}-tags" >
</div>
<div class="col-md-12">
<div class="sample"> <label>样例</label>
<div class="panel panel-default sample-panel" <a href="javascript:void(0)" class="btn btn-primary btn-sm"
ms-repeat-sample="problem.samples"> ms-click="add_sample(problem)">
<div class="panel-heading"> 添加
<span class="panel-title">样例{{$index + 1}}</span> </a>
<a href="javascript:void(0)" class="btn btn-primary btn-sm" <div class="sample">
ms-click="toggle_sample(problem, sample)"> <div class="panel panel-default sample-panel"
{{ sample.toggle_string }} ms-repeat-sample="problem.samples">
</a> <div class="panel-heading">
<a href="javascript:void(0)" class="btn btn-danger btn-sm" <span class="panel-title">样例{{$index + 1}}</span>
ms-click="del_sample(problem, sample)">
删除
</a>
</div> <a href="javascript:void(0)" class="btn btn-primary btn-sm"
<div class="panel-body" ms-click="toggle(sample)">
ms-attr-id="problem-{{ problem.id }}-sampleio-{{ sample.id }}-body"> {{getBtnContent(sample)}}
<div class="col-md-12"> </a>
<a href="javascript:void(0)" class="btn btn-danger btn-sm"
ms-click="del_sample(problem, sample)">
删除
</a>
</div>
<div class="panel-body row" ms-visible="sample.visible">
<div class="col-md-6">
<div class="form-group">
<label>样例输入</label> <label>样例输入</label>
<textarea class="form-control" rows="5"
ms-duplex="sample.input"></textarea>
</div> </div>
<div class="col-md-12"> </div>
<div class="form-group"> <div class="col-md-6">
<textarea class="form-control" rows="5"></textarea> <div class="form-group">
</div>
</div>
<div class="col-md-12">
<label>样例输出</label> <label>样例输出</label>
</div> <textarea class="form-control" rows="5"
<div class="col-md-12"> ms-duplex="sample.output"></textarea>
<div class="form-group">
<textarea class="form-control" rows="5"></textarea>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-12"> </div>
<label>测试数据</label> <div class="col-md-12">
</div> <label>测试数据</label><br>
<div class="col-md-12"> <small class="text-info">请将所有测试用例打包在一个文件中上传所有文件要在压缩包的根目录且输入输出文件名要以从1开始连续数字标识要对应例如<br>
<div class="form-group"> 1.in 1.out 2.in 2.out
<div ms-attr-id="problem-{{ problem.id }}-uploader">选择文件</div> </small>
<table class="table table-striped" ms-visible="uploadSuccess">
</div> <tr>
<td>编号</td>
<td>输入文件名</td>
<td>输出文件名</td>
</tr>
<tr ms-repeat="testCaseList">
<td>{{$index}}</td>
<td>{{el.input}}</td>
<td>{{el.output}}</td>
</tr>
</table>
</div>
<div class="col-md-12">
<div class="form-group">
<div ms-attr-id="problem-{{ problem.id }}-uploader">选择文件</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</form> <div class="col-md-12">
</div> <input type="submit" class="btn btn-success btn-lg" value="发布比赛">
{% endverbatim %} </div>
{% endblock %} </div>
{% block js_block %}
<script src="/static/js/app/admin/contest/contest.js"></script> </form>
{% endblock %} </div>
<script src="/static/js/app/admin/contest/contest.js"></script>
<link href="/static/css/tagEditor/jquery.tag-editor.css" rel="stylesheet">

View File

@ -1,4 +0,0 @@
{% extends "admin_base.html" %}
{% block body %}
Hello world
{% endblock %}

View File

@ -0,0 +1 @@
<h1>Hello world</h1>

View File

@ -0,0 +1,29 @@
<div class="col-md-9">
<h1>服务器监控</h1>
<div>
<div>
<h3>等待判题队列长度</h3>
<div id="waiting-queue">
<canvas class="line-chart" id="waiting-queue-chart"></canvas>
</div>
</div>
<div>
<h3>【10.1.24.23 - judge1 】</h3>
<div>
<canvas class="line-chart" id="judge-instance-chart"></canvas>
<div class="chart-description">判题实例数量变化</div>
</div>
<div>
<canvas id="c1" class="line-chart"></canvas>
<div class="chart-description">cpu 和 内存</div>
</div>
</div>
</div>
<script src="/static/js/app/admin/monitor/monitor.js"></script>
</div>

View File

@ -0,0 +1,103 @@
<div ms-controller="add_problem" class="col-md-9">
<form id="add-problem-form">
<div class="col-md-12">
<div class="form-group col-md-6">
<label>题目标题</label>
<input type="text" name="title" class="form-control" ms-duplex="title">
</div>
<div class="form-group col-md-6">
<label>来源</label>
<input type="text" name="source" class="form-control" ms-duplex="source">
</div>
<div class="form-group">
<label>题目描述</label>
<textarea id="problemDescription" placeholder="这里输入内容" autofocus ms-duplex="description"></textarea>
<small ms-visible="description==''" style="color:red">请填写题目描述</small>
</div>
<div class="form-group">
<label>提示</label>
<textarea id="hint" placeholder="这里输入内容" ms-duplex="hint"></textarea>
</div>
<div class="col-md-3">
<div class="form-group"><label>时间限制(ms)</label>
<input type="number" name="cpu" class="form-control" ms-duplex="cpu">
</div>
</div>
<div class="col-md-3">
<div class="form-group"><label>内存限制(MB)</label>
<input type="number" name="memory" class="form-control" ms-duplex="memory">
</div>
</div>
<div class="col-md-3">
<div class="form-group"><label>难度</label>
<input type="number" name="difficulty" class="form-control" ms-duplex="difficulty">
</div>
</div>
<div class="col-md-3 form-group">
<label>前台是否可见</label><br>
<label><input type="checkbox" ms-duplex-checked="visible">
<small> 可见</small>
</label>
</div>
<div id="tag" class="col-md-12">
<label>标签</label><br>
<input type="text" id="tags">
</div>
<div class="col-md-12"><br>
<label>样例</label>
<a href="javascript:void(0)" class="btn btn-primary btn-sm" ms-click="add_sample()">添加</a>
<div class="sample">
<div class="panel panel-default sample-panel" ms-repeat-sample="samples">
<div class="panel-heading">
<span class="panel-title">样例{{$index + 1}}</span>
<a href="javascript:void(0)" class="btn btn-primary btn-sm"
ms-click="toggle_sample(sample)">
{{getBtnContent(sample)}}
</a>
<a href="javascript:void(0)" class="btn btn-danger btn-sm"
ms-click="del_sample(sample)">
删除
</a>
</div>
<div class="panel-body row" ms-visible="sample.visible">
<div class="col-md-6">
<div class="form-group">
<label>样例输入</label>
<textarea class="form-control" rows="5" ms-duplex="sample.input"></textarea>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>样例输出</label>
<textarea class="form-control" rows="5" ms-duplex="sample.output"></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-12"><br>
<label>测试数据</label><br>
<small class="text-info">请将所有测试用例打包在一个文件中上传所有文件要在压缩包的根目录且输入输出文件名要以从1开始连续数字标识要对应例如<br>
1.in 1.out 2.in 2.out</small>
<table class="table table-striped" ms-visible="uploadSuccess">
<tr><td>编号</td><td>输入文件名</td><td>输出文件名</td></tr>
<tr ms-repeat="testCaseList"><td>{{$index}}</td><td>{{el.input}}</td><td>{{el.output}}</td></tr>
</table>
</div>
<div class="col-md-12">
<div class="form-group">
<div id="testCaseFile">选择文件</div>
</div>
</div>
</div>
<div class="col-md-12">
<input type="submit" class="btn btn-success btn-lg" value="发布题目" id="submitBtn">
</div>
</form>
</div>
<script src="/static/js/app/admin/problem/add_problem.js"></script>
<link href="/static/css/tagEditor/jquery.tag-editor.css" rel="stylesheet">

View File

@ -0,0 +1,79 @@
<div ms-controller="user_list" class="col-md-9">
<h1>User</h1>
<div class="text-right">
<form class="form-inline" onsubmit="return false;">
<div class="form-group-sm">
<label>搜索</label>
<input name="keyWord" class="form-control" placeholder="请输入关键词" ms-duplex="key_word">
<input type="submit" value="搜索" class="btn btn-primary" ms-click="getPage(1)">
</div>
</form>
<br>
</div>
<table class="table table-striped">
<tr>
<th>ID</th>
<th>用户名</th>
<th>注册时间</th>
<th>最近登陆</th>
<th>真实姓名</th>
<th>电子邮箱</th>
<th>用户类型</th>
<th>修改</th>
</tr>
<tr ms-repeat="user_list">
<td>{{el.id}}</td>
<td>{{el.username}}</td>
<td>{{el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{el.last_login|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{el.real_name}}</td>
<td>{{el.email}}</td>
<td>{{user_type[el.admin_type]}}</td>
<td>
<button class="btn-sm btn-info" ms-click="enEdit(el)">编辑</button>
</td>
</tr>
</table>
<div class="form-group">
<label>仅显示管理员 <input ms-duplex-checked="showAdminOnly" type="checkbox"/></label>
</div>
<div class="text-right">
页数:{{page}}/{{page_count}}&nbsp;&nbsp;
<botton ms-attr-class="getBtnClass(0)" ms-click="getPrevious">上一页</botton>
<botton ms-attr-class="getBtnClass(1)" ms-click="getNext">下一页</botton>
</div>
<div ms-visible="isEditing">
<h3>修改用户信息</h3>
<form id="edit_user-form">
<div class="row">
<div class="form-group col-md-4"><label>ID</label>
<input name="id" type="number" class="form-control" readonly ms-duplex="id">
</div>
<div class="form-group col-md-4"><label>用户名</label>
<input name="username" type="text" class="form-control" ms-duplex="username">
</div>
<div class="form-group col-md-4"><label>真实姓名</label>
<input name="real_name" type="text" class="form-control" ms-duplex="real_name">
</div>
</div>
<div class="row">
<div class="form-group col-md-4"><label>新密码(留空则保留原密码)</label>
<input name="password" type="password" class="form-control" id="password" ms-duplex="password"
placeholder="此项留空则保留原密码">
</div>
<div class="form-group col-md-4"><label>电子邮箱</label>
<input name="email" type="email" class="form-control" ms-duplex="email">
</div>
<div class="form-group col-md-4"><label>用户类型</label>
<select name="admin_type" class="form-control" ms-duplex="admin_type">
<option ms-repeat="user_type" ms-attr-value="$index">{{el}}</option>
</select>
</div>
</div>
<div class="form-group">
<button type="sbumit" class="btn btn-primary">提交</button>
</div>
</form>
</div>
</div>
<script src="/static/js/app/admin/user/user_list.js"></script>

View File

@ -65,14 +65,14 @@
<![endif]--> <![endif]-->
<!-- browser happy end --> <!-- browser happy end -->
<div class="container"> <div class="container" ms-controller="admin">
<div class="row"> <div class="row">
<!-- admin left begin--> <!-- admin left begin-->
<div class="col-md-2"> <div class="col-md-2">
<ul class="list-group"> <ul class="list-group">
<li class="list-group-header">List header</li> <li class="list-group-header">List header</li>
<li class="list-group-item active"><a href="#">Home</a></li> <li class="list-group-item" id="li-index"><a href="#index">主页</a></li>
<li class="list-group-item"><a href="#">Library</a></li> <li class="list-group-item" id="li-announcement"><a href="#announcement">公告</a></li>
<li class="list-group-item"><a href="#">Applications</a></li> <li class="list-group-item"><a href="#">Applications</a></li>
<li class="list-group-header">Another list header</li> <li class="list-group-header">Another list header</li>
<li class="list-group-item"><a href="#">Help</a></li> <li class="list-group-item"><a href="#">Help</a></li>
@ -108,7 +108,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>
require(["bootstrap"]); require(["bootstrap", "admin"]);
</script> </script>
{% block js_block %}{% endblock %} {% block js_block %}{% endblock %}
<!-- footer begin --> <!-- footer begin -->

View File

@ -1,10 +1,34 @@
<!DOCTYPE html> {% extends "oj_base.html" %}
<html> {% block body %}
<head lang="en"> <div class="container main">
<meta charset="UTF-8"> <div class="col-md-6 col-md-offset-3">
<title></title> <h2 class="text-center">修改密码</h2>
</head>
<body>
</body> <form id="change_password-form">
</html> <div class="form-group">
<label for="username">用户名</label>
<input type="text" class="form-control input-lg" id="username" name="username" placeholder="用户名"
autofocus>
</div>
<div class="form-group">
<label for="password">旧密码</label>
<input type="password" class="form-control input-lg" id="password" name="password" placeholder="密码">
</div>
<div class="form-group">
<label for="new_password">新密码</label>
<input type="password" class="form-control input-lg" id="new_password" name="new_password" placeholder="新密码">
</div>
<div class="form-group">
<label for="confirm_password">确认密码</label>
<input type="password" class="form-control input-lg" id="confirm_password" name="confirm_password" placeholder="确认密码">
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% block js_block %}
<script src="/static/js/app/oj/account/change_password.js"></script>
{% endblock %}

View File

@ -1,6 +1,6 @@
{% extends "oj_base.html" %} {% extends "oj_base.html" %}
{% block body %} {% block body %}
<div class="container"> <div class="container main">
<div class="col-md-6 col-md-offset-3"> <div class="col-md-6 col-md-offset-3">
<h2 class="text-center">用户登录</h2> <h2 class="text-center">用户登录</h2>

View File

@ -1,10 +1,38 @@
<!DOCTYPE html> {% extends "oj_base.html" %}
<html> {% block body %}
<head lang="en"> <div class="container main">
<meta charset="UTF-8"> <div class="col-md-6 col-md-offset-3">
<title></title> <h2 class="text-center">用户注册</h2>
</head>
<body>
</body> <form id="register-form">
</html> <div class="form-group">
<label for="username">用户名</label>
<input type="text" class="form-control input-lg" id="username" name="username" placeholder="用户名"
autofocus>
</div>
<div class="form-group">
<label for="real_name">真实姓名</label>
<input type="text" class="form-control input-lg" id="real_name" name="real_name" placeholder="真实姓名">
</div>
<div class="form-group">
<label for="email">邮箱地址</label>
<input type="email" class="form-control input-lg" id="email" name="email" placeholder="邮箱地址">
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control input-lg" id="password" name="password" placeholder="密码">
</div>
<div class="form-group">
<label for="confirm_password">确认密码</label>
<input type="password" class="form-control input-lg" id="confirm_password" name="confirm_password" placeholder="确认密码">
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% block js_block %}
<script src="/static/js/app/oj/account/register.js"></script>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends "oj_base.html" %}
{% block body %}
<div class="container main">
<h1 class="text-center">{{ announcement.title }}</h1>
<p class="text-muted text-center">
作者:{{ announcement.created_by }}
&nbsp;&nbsp;&nbsp;
创建时间:{{ announcement.create_time }}
{% ifequal announcement.create_time announcement.last_update_time %}
{% else %}
&nbsp;&nbsp;&nbsp;
最后更新:{{ announcement.last_update_time }}
{% endifequal %}
</p>
<div>
<p>{{ announcement.content|safe }}</p>
</div>
</div>
{% endblock %}

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