mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2024-09-21 08:23:20 +00:00
Merge branch 'sxw-dev' of https://git.coding.net/virusdefender/qduoj into sxw-dev
This commit is contained in:
commit
be8821bf49
4
.gitignore
vendored
4
.gitignore
vendored
@ -55,4 +55,6 @@ db.db
|
||||
db.sqlite3
|
||||
.DS_Store
|
||||
log/
|
||||
release/
|
||||
release/
|
||||
tmp/
|
||||
test_case/
|
32
account/decorators.py
Normal file
32
account/decorators.py
Normal 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
|
@ -2,7 +2,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.db.models.deletion
|
||||
import account.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@ -18,10 +18,17 @@ class Migration(migrations.Migration):
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)),
|
||||
('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={
|
||||
'db_table': 'user',
|
||||
},
|
||||
managers=[
|
||||
('objects', account.models.UserManager()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AdminGroup',
|
||||
@ -29,9 +36,4 @@ class Migration(migrations.Migration):
|
||||
('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),
|
||||
),
|
||||
]
|
||||
|
@ -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),
|
||||
),
|
||||
]
|
@ -14,12 +14,22 @@ class UserManager(models.Manager):
|
||||
return self.get(**{self.model.USERNAME_FIELD: username})
|
||||
|
||||
|
||||
REGULAR_USER = 0
|
||||
ADMIN = 1
|
||||
SUPER_ADMIN = 2
|
||||
|
||||
|
||||
class User(AbstractBaseUser):
|
||||
# 用户名
|
||||
username = models.CharField(max_length=30, unique=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'
|
||||
REQUIRED_FIELDS = []
|
||||
|
@ -1,6 +1,8 @@
|
||||
# coding=utf-8
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import User
|
||||
|
||||
|
||||
class UserLoginSerializer(serializers.Serializer):
|
||||
username = serializers.CharField(max_length=30)
|
||||
@ -11,10 +13,15 @@ class UsernameCheckSerializer(serializers.Serializer):
|
||||
username = serializers.CharField(max_length=30)
|
||||
|
||||
|
||||
class EmailCheckSerializer(serializers.Serializer):
|
||||
email = serializers.EmailField(max_length=254)
|
||||
|
||||
|
||||
class UserRegisterSerializer(serializers.Serializer):
|
||||
username = serializers.CharField(max_length=30)
|
||||
real_name = serializers.CharField(max_length=30)
|
||||
password = serializers.CharField(max_length=30, min_length=6)
|
||||
email = serializers.EmailField(max_length=254)
|
||||
|
||||
|
||||
class UserChangePasswordSerializer(serializers.Serializer):
|
||||
@ -22,3 +29,18 @@ class UserChangePasswordSerializer(serializers.Serializer):
|
||||
old_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
18
account/test_urls.py
Normal 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()),
|
||||
]
|
273
account/tests.py
273
account/tests.py
@ -1,10 +1,17 @@
|
||||
# coding=utf-8
|
||||
import json
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
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.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):
|
||||
@ -57,6 +64,25 @@ class UsernameCheckTest(APITestCase):
|
||||
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):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
@ -68,22 +94,35 @@ class UserRegisterAPITest(APITestCase):
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
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)
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_same_username(self):
|
||||
User.objects.create(username="aa", real_name="ww")
|
||||
data = {"username": "aa", "real_name": "ww", "password": "zzzzzzz"}
|
||||
User.objects.create(username="aa")
|
||||
data = {"username": "aa", "real_name": "ww", "password": "zzzzzzz", "email": "6060@qq.com"}
|
||||
response = self.client.post(self.url, data=data)
|
||||
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):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
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):
|
||||
data = {"username": "test", "old_password": "aaaccc", "new_password": "aaaddd"}
|
||||
@ -98,4 +137,226 @@ class UserChangePasswordAPITest(APITestCase):
|
||||
def test_username_does_not_exist(self):
|
||||
data = {"username": "test1", "old_password": "aaabbb", "new_password": "aaaddd"}
|
||||
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")
|
||||
|
@ -1,13 +1,15 @@
|
||||
# coding=utf-8
|
||||
from django.contrib import auth
|
||||
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
|
||||
from utils.shortcuts import serializer_invalid_response, error_response, success_response, paginate
|
||||
|
||||
from .models import User
|
||||
from .serializers import UserLoginSerializer, UsernameCheckSerializer, UserRegisterSerializer, \
|
||||
UserChangePasswordSerializer
|
||||
UserChangePasswordSerializer, EmailCheckSerializer, UserSerializer, EditUserSerializer
|
||||
|
||||
|
||||
class UserLoginAPIView(APIView):
|
||||
@ -17,7 +19,7 @@ class UserLoginAPIView(APIView):
|
||||
---
|
||||
request_serializer: UserLoginSerializer
|
||||
"""
|
||||
serializer = UserLoginSerializer(data=request.DATA)
|
||||
serializer = UserLoginSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
data = serializer.data
|
||||
user = auth.authenticate(username=data["username"], password=data["password"])
|
||||
@ -38,14 +40,20 @@ class UserRegisterAPIView(APIView):
|
||||
---
|
||||
request_serializer: UserRegisterSerializer
|
||||
"""
|
||||
serializer = UserRegisterSerializer(data=request.DATA)
|
||||
serializer = UserRegisterSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
data = serializer.data
|
||||
try:
|
||||
User.objects.get(username=data["username"])
|
||||
return error_response(u"用户名已存在")
|
||||
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.save()
|
||||
return success_response(u"注册成功!")
|
||||
@ -60,7 +68,7 @@ class UserChangePasswordAPIView(APIView):
|
||||
---
|
||||
request_serializer: UserChangePasswordSerializer
|
||||
"""
|
||||
serializer = UserChangePasswordSerializer(data=request.DATA)
|
||||
serializer = UserChangePasswordSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
data = serializer.data
|
||||
user = auth.authenticate(username=data["username"], password=data["old_password"])
|
||||
@ -81,7 +89,7 @@ class UsernameCheckAPIView(APIView):
|
||||
---
|
||||
request_serializer: UsernameCheckSerializer
|
||||
"""
|
||||
serializer = UsernameCheckSerializer(data=request.DATA)
|
||||
serializer = UsernameCheckSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
try:
|
||||
User.objects.get(username=serializer.data["username"])
|
||||
@ -89,4 +97,71 @@ class UsernameCheckAPIView(APIView):
|
||||
except User.DoesNotExist:
|
||||
return success_response(False)
|
||||
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
16
admin/middleware.py
Normal 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
10
admin/test_urls.py
Normal 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"),
|
||||
]
|
@ -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)
|
||||
|
@ -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
0
announcement/__init__.py
Normal file
3
announcement/admin.py
Normal file
3
announcement/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
30
announcement/migrations/0001_initial.py
Normal file
30
announcement/migrations/0001_initial.py
Normal 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',
|
||||
},
|
||||
),
|
||||
]
|
0
announcement/migrations/__init__.py
Normal file
0
announcement/migrations/__init__.py
Normal file
22
announcement/models.py
Normal file
22
announcement/models.py
Normal 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"
|
30
announcement/serializers.py
Normal file
30
announcement/serializers.py
Normal 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
89
announcement/tests.py
Normal 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
72
announcement/views.py
Normal 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)
|
@ -8,12 +8,44 @@ from problem.models import AbstractProblem
|
||||
class Contest(models.Model):
|
||||
title = models.CharField(max_length=40)
|
||||
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)
|
||||
# 开始时间
|
||||
start_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)
|
||||
|
||||
class Meta:
|
||||
db_table = "contest"
|
||||
|
||||
|
||||
class ContestProblem(AbstractProblem):
|
||||
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
0
group/__init__.py
Normal file
3
group/admin.py
Normal file
3
group/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
61
group/migrations/0001_initial.py
Normal file
61
group/migrations/0001_initial.py
Normal 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'),
|
||||
),
|
||||
]
|
0
group/migrations/__init__.py
Normal file
0
group/migrations/__init__.py
Normal file
40
group/models.py
Normal file
40
group/models.py
Normal 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
42
group/serializers.py
Normal 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
3
group/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
175
group/views.py
Normal file
175
group/views.py
Normal 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
0
install/__init__.py
Normal file
3
install/admin.py
Normal file
3
install/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
0
install/migrations/__init__.py
Normal file
0
install/migrations/__init__.py
Normal file
3
install/models.py
Normal file
3
install/models.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
3
install/tests.py
Normal file
3
install/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
12
install/views.py
Normal file
12
install/views.py
Normal 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")
|
@ -0,0 +1 @@
|
||||
# coding=utf-8
|
1
judge/controller/README.md
Normal file
1
judge/controller/README.md
Normal file
@ -0,0 +1 @@
|
||||
celery -A judge.controller worker -l DEBUG
|
1
judge/controller/__init__.py
Normal file
1
judge/controller/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# coding=utf-8
|
5
judge/controller/celery.py
Normal file
5
judge/controller/celery.py
Normal 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"])
|
8
judge/controller/tasks.py
Normal file
8
judge/controller/tasks.py
Normal 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
0
judge/judger/__init__.py
Normal file
@ -39,7 +39,6 @@ ALLOWED_HOSTS = []
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
@ -47,6 +46,11 @@ INSTALLED_APPS = (
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
'account',
|
||||
'announcement',
|
||||
'utils',
|
||||
'group',
|
||||
'problem',
|
||||
'admin',
|
||||
|
||||
'rest_framework',
|
||||
'rest_framework_swagger',
|
||||
@ -61,6 +65,7 @@ MIDDLEWARE_CLASSES = (
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'admin.middleware.AdminRequiredMiddleware'
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'oj.urls'
|
||||
|
28
oj/urls.py
28
oj/urls.py
@ -3,19 +3,43 @@ from django.conf.urls import include, url
|
||||
from django.contrib import admin
|
||||
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 = [
|
||||
url(r'^install/$', "install.views.install"),
|
||||
url("^$", TemplateView.as_view(template_name="oj/index.html"), name="index_page"),
|
||||
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'^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/register/$', UserRegisterAPIView.as_view(), name="user_register_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/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'^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'^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"),
|
||||
]
|
||||
|
55
problem/migrations/0001_initial.py
Normal file
55
problem/migrations/0001_initial.py
Normal 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),
|
||||
),
|
||||
]
|
18
problem/migrations/0002_remove_problemtag_description.py
Normal file
18
problem/migrations/0002_remove_problemtag_description.py
Normal file
@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('problem', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='problemtag',
|
||||
name='description',
|
||||
),
|
||||
]
|
@ -5,7 +5,11 @@ from account.models import User
|
||||
|
||||
|
||||
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):
|
||||
@ -14,21 +18,19 @@ class AbstractProblem(models.Model):
|
||||
# 问题描述 HTML 格式
|
||||
description = models.TextField()
|
||||
# 样例输入 可能会存储 json 格式的数据
|
||||
sample_input = models.TextField(blank=True)
|
||||
# 样例输出 同上
|
||||
sample_output = models.TextField(blank=True)
|
||||
sample = models.TextField(blank=True)
|
||||
# 测试用例id 这个id 可以用来拼接得到测试用例的文件存储位置
|
||||
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)
|
||||
# 这个题是谁创建的
|
||||
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()
|
||||
# 内存限制 单位是MB
|
||||
@ -39,14 +41,13 @@ class AbstractProblem(models.Model):
|
||||
total_submit_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:
|
||||
abstract = True
|
||||
|
||||
|
||||
class Problem(AbstractProblem):
|
||||
pass
|
||||
# 难度 0 - n
|
||||
difficulty = models.IntegerField()
|
||||
# 标签
|
||||
tags = models.ManyToManyField(ProblemTag, null=True)
|
||||
|
71
problem/serizalizers.py
Normal file
71
problem/serizalizers.py
Normal 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)
|
109
problem/tests.py
109
problem/tests.py
@ -1,6 +1,115 @@
|
||||
# coding=utf-8
|
||||
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):
|
||||
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)
|
||||
|
204
problem/views.py
204
problem/views.py
@ -1,7 +1,211 @@
|
||||
# coding=utf-8
|
||||
import zipfile
|
||||
import re
|
||||
import os
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
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):
|
||||
# todo
|
||||
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"测试用例压缩文件格式错误,请保证测试用例文件在根目录下直接压缩")
|
@ -5,4 +5,5 @@ django-redis-sessions
|
||||
djangorestframework
|
||||
django-rest-swagger
|
||||
celery
|
||||
gunicorn
|
||||
gunicorn
|
||||
coverage
|
@ -1,25 +1,34 @@
|
||||
@import url("global.css");
|
||||
@import url("bootstrap/bootstrap.min.css");
|
||||
@import url("bootstrap/todc-bootstrap.min.css");
|
||||
@import url("codeMirror/codemirror.css");
|
||||
@import url("simditor/simditor.css");
|
||||
@import url("webuploader/webuploader.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 {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
.line-chart{
|
||||
min-width:100%;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
float: bottom;
|
||||
bottom: 0;
|
||||
.pie-chart{
|
||||
min-width:100%;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 16px;
|
||||
.chart-description{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pie-chart-container{
|
||||
width: 50%;
|
||||
float: left;
|
||||
}
|
31
static/src/css/global.css
Normal file
31
static/src/css/global.css
Normal 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
544
static/src/css/jqueryUI/jquery-ui.css
vendored
Normal 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;
|
||||
}
|
7
static/src/css/jqueryUI/jquery-ui.min.css
vendored
Normal file
7
static/src/css/jqueryUI/jquery-ui.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
152
static/src/css/jqueryUI/jquery-ui.structure.css
vendored
Normal file
152
static/src/css/jqueryUI/jquery-ui.structure.css
vendored
Normal 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;
|
||||
}
|
410
static/src/css/jqueryUI/jquery-ui.theme.css
vendored
Normal file
410
static/src/css/jqueryUI/jquery-ui.theme.css
vendored
Normal 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;
|
||||
}
|
@ -1,25 +1,8 @@
|
||||
@import url("global.css");
|
||||
@import url("bootstrap/bootstrap.min.css");
|
||||
@import url("bootstrap/todc-bootstrap.min.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 {
|
||||
width: 130px;
|
||||
@ -47,7 +30,6 @@ label {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* index css */
|
||||
.jumbotron {
|
||||
text-align: center;
|
||||
background-color: transparent;
|
||||
|
45
static/src/css/tagEditor/jquery.tag-editor.css
Normal file
45
static/src/css/tagEditor/jquery.tag-editor.css
Normal 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
BIN
static/src/img/delete.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 250 B |
4
static/src/img/delete.svg
Normal file
4
static/src/img/delete.svg
Normal 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>
|
47
static/src/js/app/admin/admin.js
Normal file
47
static/src/js/app/admin/admin.js
Normal 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");
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
});
|
171
static/src/js/app/admin/announcement/announcement.js
Normal file
171
static/src/js/app/admin/announcement/announcement.js
Normal 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);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
@ -1,7 +1,7 @@
|
||||
require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
|
||||
"validation"
|
||||
],
|
||||
"validation","tagEditor"],
|
||||
function ($, avalon, editor, uploader) {
|
||||
avalon.vmodels.add_contest = null;
|
||||
$("#add-contest-form")
|
||||
.formValidation({
|
||||
framework: "bootstrap",
|
||||
@ -18,6 +18,13 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
|
||||
}
|
||||
}
|
||||
},
|
||||
description: {
|
||||
validators: {
|
||||
notEmpty: {
|
||||
message: "请输入描述"
|
||||
}
|
||||
}
|
||||
},
|
||||
start_time: {
|
||||
validators: {
|
||||
notEmpty: {
|
||||
@ -92,9 +99,25 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
|
||||
})
|
||||
.on("success.form.fv", function (e) {
|
||||
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() {
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
@ -102,21 +125,59 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
var editor1 = editor("#editor");
|
||||
|
||||
var vm = avalon.define({
|
||||
$id: "add_contest",
|
||||
title: "",
|
||||
description: "",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
password: "",
|
||||
model: "",
|
||||
openRank: false,
|
||||
problems: [],
|
||||
add_problem: function () {
|
||||
var problem = {};
|
||||
var problem_id = make_id();
|
||||
problem["id"] = problem_id;
|
||||
problem["samples"] = [];
|
||||
problem["webuploader"] = {};
|
||||
problem["toggle_string"] = "折叠";
|
||||
var problem = {
|
||||
id: problem_id,
|
||||
title: "",
|
||||
cpu: "",
|
||||
memory: "",
|
||||
description: "",
|
||||
samples: [],
|
||||
visible: true,
|
||||
test_case_id: "",
|
||||
testCaseList: [],
|
||||
hint: "",
|
||||
isVisible: false,
|
||||
difficulty: 0,
|
||||
tags: [],
|
||||
tag: ""
|
||||
};
|
||||
vm.problems.push(problem);
|
||||
uploader("#problem-" + problem_id + "-uploader");
|
||||
console.log(vm.problems);
|
||||
var id = vm.problems.length - 1;
|
||||
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="cpu[]"]'));
|
||||
$("#add-contest-form").formValidation('addField', $('[name="memory[]"]'));
|
||||
@ -126,31 +187,21 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
|
||||
vm.problems.remove(problem);
|
||||
}
|
||||
},
|
||||
toggle_problem: function (problem) {
|
||||
$("#" + "problem-" + problem.id + "-body").toggle();
|
||||
if (problem["toggle_string"] == "展开") {
|
||||
problem["toggle_string"] = "折叠";
|
||||
}
|
||||
else {
|
||||
problem["toggle_string"] = "展开";
|
||||
}
|
||||
toggle: function (item) {
|
||||
item.visible = !item.visible;
|
||||
},
|
||||
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) {
|
||||
if (confirm("你确定要删除么?")) {
|
||||
problem["samples"].remove(sample);
|
||||
problem.samples.remove(sample);
|
||||
}
|
||||
},
|
||||
toggle_sample: function (problem, sample) {
|
||||
$("#" + "problem-" + problem.id + "-sampleio-" + sample.id + "-body").toggle();
|
||||
if (sample["toggle_string"] == "展开") {
|
||||
sample["toggle_string"] = "折叠";
|
||||
}
|
||||
else {
|
||||
sample["toggle_string"] = "展开";
|
||||
}
|
||||
getBtnContent: function (item) {
|
||||
if (item.visible)
|
||||
return "折叠";
|
||||
return "展开";
|
||||
}
|
||||
});
|
||||
avalon.scan();
|
||||
@ -167,7 +218,6 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
|
||||
weekStart: 1,
|
||||
language: "zh-CN"
|
||||
});
|
||||
|
||||
$("#contest_start_time").datetimepicker()
|
||||
.on("hide", function (ev) {
|
||||
$("#add-contest-form")
|
||||
|
80
static/src/js/app/admin/monitor/monitor.js
Normal file
80
static/src/js/app/admin/monitor/monitor.js
Normal 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);
|
||||
|
||||
|
||||
|
||||
});
|
209
static/src/js/app/admin/problem/add_problem.js
Normal file
209
static/src/js/app/admin/problem/add_problem.js
Normal 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();
|
||||
});
|
165
static/src/js/app/admin/user/user_list.js
Normal file
165
static/src/js/app/admin/user/user_list.js
Normal 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);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
})
|
69
static/src/js/app/oj/account/change_password.js
Normal file
69
static/src/js/app/oj/account/change_password.js
Normal 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);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
require(["jquery", "bs_alert", "validation"], function($, bs_alert){
|
||||
require(["jquery", "bs_alert", "csrf", "validation"], function ($, bs_alert, csrfHeader) {
|
||||
$("#login-form")
|
||||
.formValidation({
|
||||
.formValidation({
|
||||
framework: "bootstrap",
|
||||
fields: {
|
||||
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();
|
||||
var username = $("#username").val();
|
||||
var password = $("#password").val();
|
||||
$.ajax({
|
||||
beforeSend: csrfHeader,
|
||||
url: "/api/login/",
|
||||
data: {username: username, password: password},
|
||||
dataType: "json",
|
||||
method: "post",
|
||||
success: function (data) {
|
||||
if(!data.code){
|
||||
window.location.href="/";
|
||||
if (!data.code) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
else{
|
||||
else {
|
||||
bs_alert(data.data);
|
||||
}
|
||||
}
|
||||
|
95
static/src/js/app/oj/account/register.js
Normal file
95
static/src/js/app/oj/account/register.js
Normal 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);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
@ -14,7 +14,11 @@ var require = {
|
||||
bs_alert: "utils/bs_alert",
|
||||
submit_code: "app/oj/problem/submit_code",
|
||||
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
|
||||
base: "lib/formValidation/base",
|
||||
helper: "lib/formValidation/helper",
|
||||
@ -25,7 +29,9 @@ var require = {
|
||||
"validator/date": "lib/formValidation/validator/date",
|
||||
"validator/integer": "lib/formValidation/validator/integer",
|
||||
"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
|
||||
simditor: "lib/simditor/simditor",
|
||||
"simple-module": "lib/simditor/module",
|
||||
|
3477
static/src/js/lib/chart/Chart.js
vendored
Executable file
3477
static/src/js/lib/chart/Chart.js
vendored
Executable file
File diff suppressed because it is too large
Load Diff
32
static/src/js/lib/formValidation/validator/confirm.js
Normal file
32
static/src/js/lib/formValidation/validator/confirm.js
Normal 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;
|
||||
}
|
||||
};
|
||||
}));
|
@ -1,146 +1,48 @@
|
||||
/**
|
||||
* 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);
|
||||
define("validator/remote", ["jquery", "base", "csrf"], factory);
|
||||
} else {
|
||||
// planted over the root!
|
||||
factory(root.jQuery, root.FormValidation);
|
||||
}
|
||||
|
||||
}(this, function ($, FormValidation) {
|
||||
}(this, function ($, FormValidation, csrfHeader) {
|
||||
FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
|
||||
'en_US': {
|
||||
remote: {
|
||||
'default': 'Please enter a valid value'
|
||||
'default': ''
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
var dfd = new $.Deferred(), ajaxData = {};
|
||||
ajaxData[options.field] = $field.val();
|
||||
if ($field.val() === '')
|
||||
return true;
|
||||
var url = options.url;
|
||||
var xhr = $.ajax({
|
||||
beforeSend: csrfHeader,
|
||||
url: url,
|
||||
dataType: 'json',
|
||||
data: ajaxData,
|
||||
method: "post"
|
||||
});
|
||||
xhr.success(function(response) {
|
||||
if (response.code == 1)
|
||||
dfd.resolve($field, 'remote',{valid:true, message:options.msg});
|
||||
dfd.resolve($field, 'remote',{valid:!response.data, message:options.msg});
|
||||
})
|
||||
.error(function(response) {
|
||||
dfd.resolve($field, 'remote', {valid: false});
|
||||
});
|
||||
return dfd;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return FormValidation.Validator.remote;
|
||||
}));
|
||||
|
146
static/src/js/lib/formValidation/validator/remotex.js
Normal file
146
static/src/js/lib/formValidation/validator/remotex.js
Normal 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
2610
static/src/js/lib/jqueryUI/jquery-ui.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5
static/src/js/lib/tagEditor/jquery.tag-editor.min.js
vendored
Normal file
5
static/src/js/lib/tagEditor/jquery.tag-editor.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
23
static/src/js/utils/csrf.js
Normal file
23
static/src/js/utils/csrf.js
Normal 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;
|
||||
});
|
@ -1,20 +1,23 @@
|
||||
define("uploader", ["webuploader"], function(webuploader){
|
||||
function uploader(selector) {
|
||||
return webuploader.create({
|
||||
|
||||
define("uploader", ["webuploader", "csrf"], function(webuploader,csrf){
|
||||
function uploader(selector, server, onSuccess) {
|
||||
var Webuploader= webuploader.create({
|
||||
auto: true,
|
||||
// swf文件路径
|
||||
swf: "/js/Uploader.swf",
|
||||
|
||||
swf: "/static/img/Uploader.swf",
|
||||
// 文件接收服务端。
|
||||
server: "http://webuploader.duapp.com/server/fileupload.php",
|
||||
|
||||
server: server,
|
||||
// 选择文件的按钮。可选。
|
||||
// 内部根据当前运行是创建,可能是input元素,也可能是flash.
|
||||
pick: selector,
|
||||
|
||||
// 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传!
|
||||
resize: false
|
||||
resize: false,
|
||||
uploadBeforeSend : csrf
|
||||
});
|
||||
Webuploader.on("uploadBeforeSend",csrf);
|
||||
Webuploader.on("uploadSuccess", onSuccess);
|
||||
|
||||
return Webuploader;
|
||||
}
|
||||
|
||||
return uploader;
|
||||
});
|
@ -7,5 +7,9 @@ define("validation",
|
||||
'validator/stringLength',
|
||||
'validator/date',
|
||||
'validator/integer',
|
||||
'validator/between'], function () {
|
||||
'validator/between',
|
||||
'validator/confirm',
|
||||
'validator/remote',
|
||||
'validator/emailAddress'],
|
||||
function () {
|
||||
});
|
150
template/admin/admin.html
Normal file
150
template/admin/admin.html
Normal 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">×</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>
|
65
template/admin/announcement/announcement.html
Normal file
65
template/admin/announcement/announcement.html
Normal 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}}
|
||||
<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>
|
||||
|
||||
<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>
|
@ -1,185 +1,218 @@
|
||||
{% extends "admin_base.html" %}
|
||||
{% block body %}
|
||||
{% verbatim %}
|
||||
<div ms-controller="add_contest">
|
||||
<div ms-controller="add_contest">
|
||||
<form id="add-contest-form">
|
||||
<div class="col-md-9">
|
||||
<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>
|
||||
|
||||
|
||||
<form id="add-contest-form">
|
||||
<div class="col-md-9">
|
||||
<div class="col-md-12">
|
||||
<label>比赛题目</label>
|
||||
<div class="col-md-6">
|
||||
<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 class="col-md-12">
|
||||
<div class="form-group">
|
||||
<input type="text" name="name" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label>说明</label>
|
||||
</div>
|
||||
<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>
|
||||
密码保护
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label><input type="radio" name="mode" ms-duplex-checked="model">
|
||||
<small>OI</small>
|
||||
</label>
|
||||
<label><input type="radio" name="mode">
|
||||
<small>ACM</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label>
|
||||
模式
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="text"><input type="checkbox" ms-duplex-checked="openRank">
|
||||
<small>开放排名</small>
|
||||
</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="留空就是公开赛">
|
||||
</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>
|
||||
|
||||
<div class="col-md-12">
|
||||
<label>添加题目</label>
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-sm" ms-click="add_problem()">添加</a>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="problem" ms-repeat-problem="problems">
|
||||
<div class="panel panel-default problem-panel" ms-attr-id="problem-{{ problem.id }}">
|
||||
<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_problem(problem)">
|
||||
{{ problem.toggle_string }}
|
||||
</a>
|
||||
<a href="javascript:void(0)" class="btn btn-danger btn-sm"
|
||||
ms-click="del_problem(problem)">
|
||||
删除
|
||||
</a>
|
||||
<div class="col-md-12">
|
||||
<label>添加题目</label>
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-sm" ms-click="add_problem()">添加</a>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="problem" ms-repeat-problem="problems">
|
||||
<div class="panel panel-default problem-panel" ms-attr-id="problem-{{ problem.id }}">
|
||||
<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(problem)">
|
||||
{{getBtnContent(problem)}}
|
||||
</a>
|
||||
<a href="javascript:void(0)" class="btn btn-danger btn-sm" ms-click="del_problem(problem)">
|
||||
删除
|
||||
</a>
|
||||
</div>
|
||||
<div class="panel-body" ms-visible="problem.visible">
|
||||
<div class="col-md-12">
|
||||
<label>题目</label>
|
||||
</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="form-group">
|
||||
<input type="text" name="problem_name[]" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<input type="text" name="problem_name[]" class="form-control"
|
||||
ms-duplex="problem.title">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label>cpu</label>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label>内存</label>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<input type="text" name="cpu[]" class="form-control">
|
||||
</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">
|
||||
</div>
|
||||
<label>题目描述</label>
|
||||
<textarea ms-attr-id="problem-{{ problem.id }}-description" placeholder="这里输入内容"
|
||||
ms-duplex="problem.description"></textarea>
|
||||
<small ms-visible="problem.description==''" style="color:red">请填写题目描述</small>
|
||||
<div class="form-group">
|
||||
<label>提示</label>
|
||||
<textarea ms-attr-id="problem-{{ problem.id }}-hint" placeholder="这里输入内容" ms-duplex="problem.hint"></textarea>
|
||||
</div>
|
||||
|
||||
<label>样例</label>
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-sm"
|
||||
ms-click="add_sample(problem)">
|
||||
添加
|
||||
</a>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group"><label>cpu</label>
|
||||
<input type="number" name="cpu[]" class="form-control" ms-duplex="problem.cpu">
|
||||
</div>
|
||||
</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">
|
||||
<div class="panel panel-default sample-panel"
|
||||
ms-repeat-sample="problem.samples">
|
||||
<div class="panel-heading">
|
||||
<span class="panel-title">样例{{$index + 1}}</span>
|
||||
<label>样例</label>
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-sm"
|
||||
ms-click="add_sample(problem)">
|
||||
添加
|
||||
</a>
|
||||
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-sm"
|
||||
ms-click="toggle_sample(problem, sample)">
|
||||
{{ sample.toggle_string }}
|
||||
</a>
|
||||
<a href="javascript:void(0)" class="btn btn-danger btn-sm"
|
||||
ms-click="del_sample(problem, sample)">
|
||||
删除
|
||||
</a>
|
||||
<div class="sample">
|
||||
<div class="panel panel-default sample-panel"
|
||||
ms-repeat-sample="problem.samples">
|
||||
<div class="panel-heading">
|
||||
<span class="panel-title">样例{{$index + 1}}</span>
|
||||
|
||||
</div>
|
||||
<div class="panel-body"
|
||||
ms-attr-id="problem-{{ problem.id }}-sampleio-{{ sample.id }}-body">
|
||||
<div class="col-md-12">
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-sm"
|
||||
ms-click="toggle(sample)">
|
||||
{{getBtnContent(sample)}}
|
||||
</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>
|
||||
|
||||
<textarea class="form-control" rows="5"
|
||||
ms-duplex="sample.input"></textarea>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" rows="5"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>样例输出</label>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" rows="5"></textarea>
|
||||
</div>
|
||||
<textarea class="form-control" rows="5"
|
||||
ms-duplex="sample.output"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label>测试数据</label>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<div ms-attr-id="problem-{{ problem.id }}-uploader">选择文件</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<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 ms-attr-id="problem-{{ problem.id }}-uploader">选择文件</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endverbatim %}
|
||||
{% endblock %}
|
||||
{% block js_block %}
|
||||
<script src="/static/js/app/admin/contest/contest.js"></script>
|
||||
{% endblock %}
|
||||
<div class="col-md-12">
|
||||
<input type="submit" class="btn btn-success btn-lg" value="发布比赛">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/app/admin/contest/contest.js"></script>
|
||||
<link href="/static/css/tagEditor/jquery.tag-editor.css" rel="stylesheet">
|
@ -1,4 +0,0 @@
|
||||
{% extends "admin_base.html" %}
|
||||
{% block body %}
|
||||
Hello world
|
||||
{% endblock %}
|
1
template/admin/index/index.html
Normal file
1
template/admin/index/index.html
Normal file
@ -0,0 +1 @@
|
||||
<h1>Hello world</h1>
|
29
template/admin/monitor/monitor.html
Normal file
29
template/admin/monitor/monitor.html
Normal 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>
|
103
template/admin/problem/add_problem.html
Normal file
103
template/admin/problem/add_problem.html
Normal 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">
|
79
template/admin/user/user_list.html
Normal file
79
template/admin/user/user_list.html
Normal 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}}
|
||||
<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>
|
@ -65,14 +65,14 @@
|
||||
<![endif]-->
|
||||
<!-- browser happy end -->
|
||||
|
||||
<div class="container">
|
||||
<div class="container" ms-controller="admin">
|
||||
<div class="row">
|
||||
<!-- admin left begin-->
|
||||
<div class="col-md-2">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-header">List header</li>
|
||||
<li class="list-group-item active"><a href="#">Home</a></li>
|
||||
<li class="list-group-item"><a href="#">Library</a></li>
|
||||
<li class="list-group-item" id="li-index"><a href="#index">主页</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-header">Another list header</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/require.js"></script>
|
||||
<script>
|
||||
require(["bootstrap"]);
|
||||
require(["bootstrap", "admin"]);
|
||||
</script>
|
||||
{% block js_block %}{% endblock %}
|
||||
<!-- footer begin -->
|
||||
|
@ -1,10 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
{% extends "oj_base.html" %}
|
||||
{% block body %}
|
||||
<div class="container main">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
<h2 class="text-center">修改密码</h2>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<form id="change_password-form">
|
||||
<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 %}
|
@ -1,6 +1,6 @@
|
||||
{% extends "oj_base.html" %}
|
||||
{% block body %}
|
||||
<div class="container">
|
||||
<div class="container main">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
<h2 class="text-center">用户登录</h2>
|
||||
|
||||
|
@ -1,10 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
{% extends "oj_base.html" %}
|
||||
{% block body %}
|
||||
<div class="container main">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
<h2 class="text-center">用户注册</h2>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<form id="register-form">
|
||||
<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 %}
|
23
template/oj/announcement/announcement.html
Normal file
23
template/oj/announcement/announcement.html
Normal 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 }}
|
||||
|
||||
创建时间:{{ announcement.create_time }}
|
||||
{% ifequal announcement.create_time announcement.last_update_time %}
|
||||
{% else %}
|
||||
|
||||
最后更新:{{ 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
Loading…
Reference in New Issue
Block a user