mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2024-09-21 16:33:22 +00:00
Merge branch 'sxw-dev' of https://git.coding.net/virusdefender/qduoj into sxw-dev
This commit is contained in:
commit
be8821bf49
2
.gitignore
vendored
2
.gitignore
vendored
@ -56,3 +56,5 @@ db.sqlite3
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
log/
|
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 __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
import django.db.models.deletion
|
import account.models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@ -18,10 +18,17 @@ class Migration(migrations.Migration):
|
|||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)),
|
('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)),
|
||||||
('username', models.CharField(unique=True, max_length=30)),
|
('username', models.CharField(unique=True, max_length=30)),
|
||||||
|
('real_name', models.CharField(max_length=30, null=True, blank=True)),
|
||||||
|
('email', models.EmailField(max_length=254, null=True, blank=True)),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('admin_type', models.IntegerField(default=0)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'db_table': 'user',
|
'db_table': 'user',
|
||||||
},
|
},
|
||||||
|
managers=[
|
||||||
|
('objects', account.models.UserManager()),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='AdminGroup',
|
name='AdminGroup',
|
||||||
@ -29,9 +36,4 @@ class Migration(migrations.Migration):
|
|||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='admin_group',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, to='account.AdminGroup', null=True),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
@ -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})
|
return self.get(**{self.model.USERNAME_FIELD: username})
|
||||||
|
|
||||||
|
|
||||||
|
REGULAR_USER = 0
|
||||||
|
ADMIN = 1
|
||||||
|
SUPER_ADMIN = 2
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractBaseUser):
|
class User(AbstractBaseUser):
|
||||||
# 用户名
|
# 用户名
|
||||||
username = models.CharField(max_length=30, unique=True)
|
username = models.CharField(max_length=30, unique=True)
|
||||||
# 真实姓名
|
# 真实姓名
|
||||||
real_name = models.CharField(max_length=30, blank=True, null=True)
|
real_name = models.CharField(max_length=30, blank=True, null=True)
|
||||||
admin_group = models.ForeignKey(AdminGroup, null=True, on_delete=models.SET_NULL)
|
# 用户邮箱
|
||||||
|
email = models.EmailField(max_length=254, blank=True, null=True)
|
||||||
|
# 用户注册时间
|
||||||
|
create_time = models.DateTimeField(auto_now_add=True)
|
||||||
|
# 0代表不是管理员 1是普通管理员 2是超级管理员
|
||||||
|
admin_type = models.IntegerField(default=0)
|
||||||
|
|
||||||
USERNAME_FIELD = 'username'
|
USERNAME_FIELD = 'username'
|
||||||
REQUIRED_FIELDS = []
|
REQUIRED_FIELDS = []
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from .models import User
|
||||||
|
|
||||||
|
|
||||||
class UserLoginSerializer(serializers.Serializer):
|
class UserLoginSerializer(serializers.Serializer):
|
||||||
username = serializers.CharField(max_length=30)
|
username = serializers.CharField(max_length=30)
|
||||||
@ -11,10 +13,15 @@ class UsernameCheckSerializer(serializers.Serializer):
|
|||||||
username = serializers.CharField(max_length=30)
|
username = serializers.CharField(max_length=30)
|
||||||
|
|
||||||
|
|
||||||
|
class EmailCheckSerializer(serializers.Serializer):
|
||||||
|
email = serializers.EmailField(max_length=254)
|
||||||
|
|
||||||
|
|
||||||
class UserRegisterSerializer(serializers.Serializer):
|
class UserRegisterSerializer(serializers.Serializer):
|
||||||
username = serializers.CharField(max_length=30)
|
username = serializers.CharField(max_length=30)
|
||||||
real_name = serializers.CharField(max_length=30)
|
real_name = serializers.CharField(max_length=30)
|
||||||
password = serializers.CharField(max_length=30, min_length=6)
|
password = serializers.CharField(max_length=30, min_length=6)
|
||||||
|
email = serializers.EmailField(max_length=254)
|
||||||
|
|
||||||
|
|
||||||
class UserChangePasswordSerializer(serializers.Serializer):
|
class UserChangePasswordSerializer(serializers.Serializer):
|
||||||
@ -22,3 +29,18 @@ class UserChangePasswordSerializer(serializers.Serializer):
|
|||||||
old_password = serializers.CharField(max_length=30, min_length=6)
|
old_password = serializers.CharField(max_length=30, min_length=6)
|
||||||
new_password = serializers.CharField(max_length=30, min_length=6)
|
new_password = serializers.CharField(max_length=30, min_length=6)
|
||||||
|
|
||||||
|
|
||||||
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
exclude = ["password"]
|
||||||
|
|
||||||
|
|
||||||
|
class EditUserSerializer(serializers.Serializer):
|
||||||
|
id = serializers.IntegerField()
|
||||||
|
username = serializers.CharField(max_length=30)
|
||||||
|
real_name = serializers.CharField(max_length=30)
|
||||||
|
password = serializers.CharField(max_length=30, min_length=6, required=False, default=None)
|
||||||
|
email = serializers.EmailField(max_length=254)
|
||||||
|
admin_type = serializers.IntegerField(default=0)
|
||||||
|
18
account/test_urls.py
Normal file
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()),
|
||||||
|
]
|
271
account/tests.py
271
account/tests.py
@ -1,10 +1,17 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
import json
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.contrib import auth
|
||||||
|
|
||||||
from rest_framework.test import APITestCase, APIClient
|
from rest_framework.test import APITestCase, APIClient
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from .models import User
|
from .models import User, SUPER_ADMIN
|
||||||
|
from .decorators import login_required, admin_required
|
||||||
|
|
||||||
|
|
||||||
class UserLoginTest(TestCase):
|
class UserLoginTest(TestCase):
|
||||||
@ -57,6 +64,25 @@ class UsernameCheckTest(APITestCase):
|
|||||||
self.assertEqual(response.data, {"code": 0, "data": False})
|
self.assertEqual(response.data, {"code": 0, "data": False})
|
||||||
|
|
||||||
|
|
||||||
|
class EmailCheckTest(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = APIClient()
|
||||||
|
self.url = reverse("email_check_api")
|
||||||
|
User.objects.create(email="11@qq.com")
|
||||||
|
|
||||||
|
def test_invalid_data(self):
|
||||||
|
response = self.client.post(self.url, data={"email000": "11@qq.com"})
|
||||||
|
self.assertEqual(response.data["code"], 1)
|
||||||
|
|
||||||
|
def test_email_exists(self):
|
||||||
|
response = self.client.post(self.url, data={"email": "11@qq.com"})
|
||||||
|
self.assertEqual(response.data, {"code": 0, "data": True})
|
||||||
|
|
||||||
|
def test_email_does_not_exist(self):
|
||||||
|
response = self.client.post(self.url, data={"email": "33@qq.com"})
|
||||||
|
self.assertEqual(response.data, {"code": 0, "data": False})
|
||||||
|
|
||||||
|
|
||||||
class UserRegisterAPITest(APITestCase):
|
class UserRegisterAPITest(APITestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
@ -68,22 +94,35 @@ class UserRegisterAPITest(APITestCase):
|
|||||||
self.assertEqual(response.data["code"], 1)
|
self.assertEqual(response.data["code"], 1)
|
||||||
|
|
||||||
def test_short_password(self):
|
def test_short_password(self):
|
||||||
data = {"username": "test", "real_name": "TT", "password": "qq"}
|
data = {"username": "test", "real_name": "TT", "password": "qq", "email": "6060@qq.com"}
|
||||||
response = self.client.post(self.url, data=data)
|
response = self.client.post(self.url, data=data)
|
||||||
self.assertEqual(response.data["code"], 1)
|
self.assertEqual(response.data["code"], 1)
|
||||||
|
|
||||||
def test_same_username(self):
|
def test_same_username(self):
|
||||||
User.objects.create(username="aa", real_name="ww")
|
User.objects.create(username="aa")
|
||||||
data = {"username": "aa", "real_name": "ww", "password": "zzzzzzz"}
|
data = {"username": "aa", "real_name": "ww", "password": "zzzzzzz", "email": "6060@qq.com"}
|
||||||
response = self.client.post(self.url, data=data)
|
response = self.client.post(self.url, data=data)
|
||||||
self.assertEqual(response.data, {"code": 1, "data": u"用户名已存在"})
|
self.assertEqual(response.data, {"code": 1, "data": u"用户名已存在"})
|
||||||
|
|
||||||
|
def test_same_email(self):
|
||||||
|
User.objects.create(username="bb", email="8080@qq.com")
|
||||||
|
data = {"username": "aa", "real_name": "ww", "password": "zzzzzzz", "email": "8080@qq.com"}
|
||||||
|
response = self.client.post(self.url, data=data)
|
||||||
|
self.assertEqual(response.data, {"code": 1, "data": u"该邮箱已被注册,请换其他邮箱进行注册"})
|
||||||
|
|
||||||
|
def test_success_email(self):
|
||||||
|
data = {"username": "cc", "real_name": "dd", "password": "xxxxxx", "email": "9090@qq.com"}
|
||||||
|
response = self.client.post(self.url, data=data)
|
||||||
|
self.assertEqual(response.data, {"code": 0, "data": u"注册成功!"})
|
||||||
|
|
||||||
|
|
||||||
class UserChangePasswordAPITest(APITestCase):
|
class UserChangePasswordAPITest(APITestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
self.url = reverse("user_change_password_api")
|
self.url = reverse("user_change_password_api")
|
||||||
User.objects.create(username="test", password="aaabbb")
|
user = User.objects.create(username="test")
|
||||||
|
user.set_password("aaabbb")
|
||||||
|
user.save()
|
||||||
|
|
||||||
def test_error_old_password(self):
|
def test_error_old_password(self):
|
||||||
data = {"username": "test", "old_password": "aaaccc", "new_password": "aaaddd"}
|
data = {"username": "test", "old_password": "aaaccc", "new_password": "aaaddd"}
|
||||||
@ -99,3 +138,225 @@ class UserChangePasswordAPITest(APITestCase):
|
|||||||
data = {"username": "test1", "old_password": "aaabbb", "new_password": "aaaddd"}
|
data = {"username": "test1", "old_password": "aaabbb", "new_password": "aaaddd"}
|
||||||
response = self.client.post(self.url, data=data)
|
response = self.client.post(self.url, data=data)
|
||||||
self.assertEqual(response.data["code"], 1)
|
self.assertEqual(response.data["code"], 1)
|
||||||
|
|
||||||
|
def test_success_change_password(self):
|
||||||
|
data = {"username": "test", "old_password": "aaabbb", "new_password": "aaaccc"}
|
||||||
|
response = self.client.post(self.url, data=data)
|
||||||
|
self.assertEqual(response.data, {"code": 0, "data": u"用户密码修改成功!"})
|
||||||
|
|
||||||
|
|
||||||
|
class UserAPITest(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = APIClient()
|
||||||
|
self.url = reverse("user_list_api")
|
||||||
|
user = User.objects.create(username="testx", real_name="xx", admin_type=SUPER_ADMIN)
|
||||||
|
user.set_password("testxx")
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
def test_success_get_data(self):
|
||||||
|
self.client.login(username="testx", password="testxx")
|
||||||
|
self.assertEqual(self.client.get(self.url).data["code"], 0)
|
||||||
|
|
||||||
|
def test_error_admin_type(self):
|
||||||
|
self.client.login(username="testx", password="testxx")
|
||||||
|
response = self.client.get(self.url + "?admin_type=error")
|
||||||
|
self.assertEqual(response.data, {"code": 1, "data": u"参数错误"})
|
||||||
|
|
||||||
|
def test_query_by_keyword(self):
|
||||||
|
self.client.login(username="testx", password="testxx")
|
||||||
|
user1 = User.objects.create(username="test1", real_name="aa")
|
||||||
|
user1.set_password("testaa")
|
||||||
|
user1.save()
|
||||||
|
|
||||||
|
user2 = User.objects.create(username="test2", real_name="bb")
|
||||||
|
user2.set_password("testbb")
|
||||||
|
user2.save()
|
||||||
|
|
||||||
|
user3 = User.objects.create(username="test3", real_name="cc")
|
||||||
|
user3.set_password("testcc")
|
||||||
|
user3.save()
|
||||||
|
|
||||||
|
response = self.client.get(self.url + "?keyword=test1")
|
||||||
|
self.assertEqual(response.data["code"], 0)
|
||||||
|
|
||||||
|
|
||||||
|
class UserAdminAPITest(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = APIClient()
|
||||||
|
self.url = reverse("user_admin_api")
|
||||||
|
user = User.objects.create(username="test", admin_type=SUPER_ADMIN)
|
||||||
|
user.set_password("testaa")
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
def test_put_invalid_data(self):
|
||||||
|
self.client.login(username="test", password="testaa")
|
||||||
|
data = {"username": "test", "password": "testaa", "email": "60@qq.com", "admin_type": "2"}
|
||||||
|
response = self.client.put(self.url, data=data)
|
||||||
|
self.assertEqual(response.data["code"], 1)
|
||||||
|
|
||||||
|
def test_user_does_not_exist(self):
|
||||||
|
self.client.login(username="test", password="testaa")
|
||||||
|
data = {"id": 2, "username": "test0", "real_name": "test00",
|
||||||
|
"password": "testaa","email": "60@qq.com", "admin_type": "2"}
|
||||||
|
response = self.client.put(self.url, data=data)
|
||||||
|
self.assertEqual(response.data, {"code": 1, "data": u"该用户不存在!"})
|
||||||
|
|
||||||
|
def test_success_user_edit_not_password(self):
|
||||||
|
self.client.login(username="test", password="testaa")
|
||||||
|
data = {"id": 1, "username": "test0", "real_name": "test00",
|
||||||
|
"email": "60@qq.com", "admin_type": "2"}
|
||||||
|
response = self.client.put(self.url, data=data)
|
||||||
|
self.assertEqual(response.data["code"], 0)
|
||||||
|
|
||||||
|
def test_success_user_edit_change_password(self):
|
||||||
|
self.client.login(username="test", password="testaa")
|
||||||
|
data = {"id": 1, "username": "test0", "real_name": "test00", "password": "111111",
|
||||||
|
"email": "60@qq.com", "admin_type": "2"}
|
||||||
|
response = self.client.put(self.url, data=data)
|
||||||
|
self.assertEqual(response.data["code"], 0)
|
||||||
|
self.assertIsNotNone(auth.authenticate(username="test0", password="111111"))
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def login_required_FBV_test_without_args(request):
|
||||||
|
return HttpResponse("function based view test1")
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def login_required_FBC_test_with_args(request, problem_id):
|
||||||
|
return HttpResponse(problem_id)
|
||||||
|
|
||||||
|
|
||||||
|
class LoginRequiredCBVTestWithoutArgs(APIView):
|
||||||
|
@login_required
|
||||||
|
def get(self, request):
|
||||||
|
return HttpResponse("class based view login required test1")
|
||||||
|
|
||||||
|
|
||||||
|
class LoginRequiredCBVTestWithArgs(APIView):
|
||||||
|
@login_required
|
||||||
|
def get(self, request, problem_id):
|
||||||
|
return HttpResponse(problem_id)
|
||||||
|
|
||||||
|
|
||||||
|
class LoginRequiredDecoratorTest(TestCase):
|
||||||
|
urls = 'account.test_urls'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
user = User.objects.create(username="test")
|
||||||
|
user.set_password("test")
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
def test_fbv_without_args(self):
|
||||||
|
# 没登陆
|
||||||
|
response = self.client.get("/login_required_test/fbv/1/")
|
||||||
|
self.assertTemplateUsed(response, "utils/error.html")
|
||||||
|
|
||||||
|
# 登陆后
|
||||||
|
self.client.login(username="test", password="test")
|
||||||
|
response = self.client.get("/login_required_test/fbv/1/")
|
||||||
|
self.assertEqual(response.content, "function based view test1")
|
||||||
|
|
||||||
|
def test_fbv_with_args(self):
|
||||||
|
# 没登陆
|
||||||
|
response = self.client.get("/login_required_test/fbv/1024/")
|
||||||
|
self.assertTemplateUsed(response, "utils/error.html")
|
||||||
|
|
||||||
|
# 登陆后
|
||||||
|
self.client.login(username="test", password="test")
|
||||||
|
response = self.client.get("/login_required_test/fbv/1024/")
|
||||||
|
self.assertEqual(response.content, "1024")
|
||||||
|
|
||||||
|
def test_cbv_without_args(self):
|
||||||
|
# 没登陆
|
||||||
|
response = self.client.get("/login_required_test/cbv/1/")
|
||||||
|
self.assertTemplateUsed(response, "utils/error.html")
|
||||||
|
|
||||||
|
# 登陆后
|
||||||
|
self.client.login(username="test", password="test")
|
||||||
|
response = self.client.get("/login_required_test/cbv/1/")
|
||||||
|
self.assertEqual(response.content, "class based view login required test1")
|
||||||
|
|
||||||
|
def test_cbv_with_args(self):
|
||||||
|
# 没登陆
|
||||||
|
response = self.client.get("/login_required_test/cbv/1024/", HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||||
|
self.assertEqual(json.loads(response.content), {"code": 1, "data": u"请先登录"})
|
||||||
|
|
||||||
|
# 登陆后
|
||||||
|
self.client.login(username="test", password="test")
|
||||||
|
response = self.client.get("/login_required_test/cbv/1024/")
|
||||||
|
self.assertEqual(response.content, "1024")
|
||||||
|
|
||||||
|
|
||||||
|
@admin_required
|
||||||
|
def admin_required_FBV_test_without_args(request):
|
||||||
|
return HttpResponse("function based view test1")
|
||||||
|
|
||||||
|
|
||||||
|
@admin_required
|
||||||
|
def admin_required_FBC_test_with_args(request, problem_id):
|
||||||
|
return HttpResponse(problem_id)
|
||||||
|
|
||||||
|
|
||||||
|
class AdminRequiredCBVTestWithoutArgs(APIView):
|
||||||
|
@admin_required
|
||||||
|
def get(self, request):
|
||||||
|
return HttpResponse("class based view login required test1")
|
||||||
|
|
||||||
|
|
||||||
|
class AdminRequiredCBVTestWithArgs(APIView):
|
||||||
|
@admin_required
|
||||||
|
def get(self, request, problem_id):
|
||||||
|
return HttpResponse(problem_id)
|
||||||
|
|
||||||
|
|
||||||
|
class AdminRequiredDecoratorTest(TestCase):
|
||||||
|
urls = 'account.test_urls'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
user = User.objects.create(username="test")
|
||||||
|
user.admin_type = 1
|
||||||
|
user.set_password("test")
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
def test_fbv_without_args(self):
|
||||||
|
# 没登陆
|
||||||
|
response = self.client.get("/admin_required_test/fbv/1/")
|
||||||
|
self.assertTemplateUsed(response, "utils/error.html")
|
||||||
|
|
||||||
|
# 登陆后
|
||||||
|
self.client.login(username="test", password="test")
|
||||||
|
response = self.client.get("/admin_required_test/fbv/1/")
|
||||||
|
self.assertEqual(response.content, "function based view test1")
|
||||||
|
|
||||||
|
def test_fbv_with_args(self):
|
||||||
|
# 没登陆
|
||||||
|
response = self.client.get("/admin_required_test/fbv/1024/")
|
||||||
|
self.assertTemplateUsed(response, "utils/error.html")
|
||||||
|
|
||||||
|
# 登陆后
|
||||||
|
self.client.login(username="test", password="test")
|
||||||
|
response = self.client.get("/admin_required_test/fbv/1024/")
|
||||||
|
self.assertEqual(response.content, "1024")
|
||||||
|
|
||||||
|
def test_cbv_without_args(self):
|
||||||
|
# 没登陆
|
||||||
|
response = self.client.get("/admin_required_test/cbv/1/")
|
||||||
|
self.assertTemplateUsed(response, "utils/error.html")
|
||||||
|
|
||||||
|
# 登陆后
|
||||||
|
self.client.login(username="test", password="test")
|
||||||
|
response = self.client.get("/admin_required_test/cbv/1/")
|
||||||
|
self.assertEqual(response.content, "class based view login required test1")
|
||||||
|
|
||||||
|
def test_cbv_with_args(self):
|
||||||
|
# 没登陆
|
||||||
|
response = self.client.get("/admin_required_test/cbv/1024/", HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||||
|
self.assertEqual(json.loads(response.content), {"code": 1, "data": u"需要管理员权限"})
|
||||||
|
|
||||||
|
# 登陆后
|
||||||
|
self.client.login(username="test", password="test")
|
||||||
|
response = self.client.get("/admin_required_test/cbv/1024/")
|
||||||
|
self.assertEqual(response.content, "1024")
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from utils.shortcuts import serializer_invalid_response, error_response, success_response
|
from utils.shortcuts import serializer_invalid_response, error_response, success_response, paginate
|
||||||
|
|
||||||
from .models import User
|
from .models import User
|
||||||
from .serializers import UserLoginSerializer, UsernameCheckSerializer, UserRegisterSerializer, \
|
from .serializers import UserLoginSerializer, UsernameCheckSerializer, UserRegisterSerializer, \
|
||||||
UserChangePasswordSerializer
|
UserChangePasswordSerializer, EmailCheckSerializer, UserSerializer, EditUserSerializer
|
||||||
|
|
||||||
|
|
||||||
class UserLoginAPIView(APIView):
|
class UserLoginAPIView(APIView):
|
||||||
@ -17,7 +19,7 @@ class UserLoginAPIView(APIView):
|
|||||||
---
|
---
|
||||||
request_serializer: UserLoginSerializer
|
request_serializer: UserLoginSerializer
|
||||||
"""
|
"""
|
||||||
serializer = UserLoginSerializer(data=request.DATA)
|
serializer = UserLoginSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
user = auth.authenticate(username=data["username"], password=data["password"])
|
user = auth.authenticate(username=data["username"], password=data["password"])
|
||||||
@ -38,14 +40,20 @@ class UserRegisterAPIView(APIView):
|
|||||||
---
|
---
|
||||||
request_serializer: UserRegisterSerializer
|
request_serializer: UserRegisterSerializer
|
||||||
"""
|
"""
|
||||||
serializer = UserRegisterSerializer(data=request.DATA)
|
serializer = UserRegisterSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
try:
|
try:
|
||||||
User.objects.get(username=data["username"])
|
User.objects.get(username=data["username"])
|
||||||
return error_response(u"用户名已存在")
|
return error_response(u"用户名已存在")
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
user = User.objects.create(username=data["username"], real_name=data["real_name"])
|
pass
|
||||||
|
try:
|
||||||
|
User.objects.get(email=data["email"])
|
||||||
|
return error_response(u"该邮箱已被注册,请换其他邮箱进行注册")
|
||||||
|
except User.DoesNotExist:
|
||||||
|
user = User.objects.create(username=data["username"], real_name=data["real_name"],
|
||||||
|
email=data["email"])
|
||||||
user.set_password(data["password"])
|
user.set_password(data["password"])
|
||||||
user.save()
|
user.save()
|
||||||
return success_response(u"注册成功!")
|
return success_response(u"注册成功!")
|
||||||
@ -60,7 +68,7 @@ class UserChangePasswordAPIView(APIView):
|
|||||||
---
|
---
|
||||||
request_serializer: UserChangePasswordSerializer
|
request_serializer: UserChangePasswordSerializer
|
||||||
"""
|
"""
|
||||||
serializer = UserChangePasswordSerializer(data=request.DATA)
|
serializer = UserChangePasswordSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
user = auth.authenticate(username=data["username"], password=data["old_password"])
|
user = auth.authenticate(username=data["username"], password=data["old_password"])
|
||||||
@ -81,7 +89,7 @@ class UsernameCheckAPIView(APIView):
|
|||||||
---
|
---
|
||||||
request_serializer: UsernameCheckSerializer
|
request_serializer: UsernameCheckSerializer
|
||||||
"""
|
"""
|
||||||
serializer = UsernameCheckSerializer(data=request.DATA)
|
serializer = UsernameCheckSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
try:
|
try:
|
||||||
User.objects.get(username=serializer.data["username"])
|
User.objects.get(username=serializer.data["username"])
|
||||||
@ -90,3 +98,70 @@ class UsernameCheckAPIView(APIView):
|
|||||||
return success_response(False)
|
return success_response(False)
|
||||||
else:
|
else:
|
||||||
return serializer_invalid_response(serializer)
|
return serializer_invalid_response(serializer)
|
||||||
|
|
||||||
|
|
||||||
|
class EmailCheckAPIView(APIView):
|
||||||
|
def post(self, request):
|
||||||
|
"""
|
||||||
|
检测邮箱是否存在,存在返回True,不存在返回False
|
||||||
|
---
|
||||||
|
request_serializer: EmailCheckSerializer
|
||||||
|
"""
|
||||||
|
serializer = EmailCheckSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
try:
|
||||||
|
User.objects.get(email=serializer.data["email"])
|
||||||
|
return success_response(True)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return success_response(False)
|
||||||
|
else:
|
||||||
|
return serializer_invalid_response(serializer)
|
||||||
|
|
||||||
|
|
||||||
|
class UserAPIView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
"""
|
||||||
|
用户分页json api接口
|
||||||
|
---
|
||||||
|
response_serializer: UserSerializer
|
||||||
|
"""
|
||||||
|
user = User.objects.all().order_by("-create_time")
|
||||||
|
admin_type = request.GET.get("admin_type", None)
|
||||||
|
if admin_type:
|
||||||
|
try:
|
||||||
|
user = user.filter(admin_type__gte=int(admin_type))
|
||||||
|
except ValueError:
|
||||||
|
return error_response(u"参数错误")
|
||||||
|
keyword = request.GET.get("keyword", None)
|
||||||
|
if keyword:
|
||||||
|
user = user.filter(Q(username__contains=keyword) |
|
||||||
|
Q(real_name__contains=keyword) |
|
||||||
|
Q(email__contains=keyword))
|
||||||
|
return paginate(request, user, UserSerializer)
|
||||||
|
|
||||||
|
|
||||||
|
class UserAdminAPIView(APIView):
|
||||||
|
def put(self, request):
|
||||||
|
"""
|
||||||
|
用户编辑json api接口
|
||||||
|
---
|
||||||
|
request_serializer: EditUserSerializer
|
||||||
|
response_serializer: UserSerializer
|
||||||
|
"""
|
||||||
|
serializer = EditUserSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
data = serializer.data
|
||||||
|
try:
|
||||||
|
user = User.objects.get(id=data["id"])
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return error_response(u"该用户不存在!")
|
||||||
|
user.username = data["username"]
|
||||||
|
user.real_name = data["real_name"]
|
||||||
|
user.email = data["email"]
|
||||||
|
user.admin_type = data["admin_type"]
|
||||||
|
if data["password"]:
|
||||||
|
user.set_password(data["password"])
|
||||||
|
user.save()
|
||||||
|
return success_response(UserSerializer(user).data)
|
||||||
|
else:
|
||||||
|
return serializer_invalid_response(serializer)
|
||||||
|
16
admin/middleware.py
Normal file
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):
|
class Contest(models.Model):
|
||||||
title = models.CharField(max_length=40)
|
title = models.CharField(max_length=40)
|
||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
is_public = models.BooleanField()
|
# 比赛模式 现在有 acm 模式,按照 ac 题目数量得分模式,
|
||||||
|
# 按照 ac 的题目的总分得分模式和按照通过的测试用例总分得分模式等
|
||||||
|
mode = models.IntegerField()
|
||||||
|
# 是否显示排名结果
|
||||||
|
show_rank = models.BooleanField()
|
||||||
|
# 如果这一项不为空,那这就不是公开赛,需要密码才能进入
|
||||||
password = models.CharField(max_length=30, blank=True, null=True)
|
password = models.CharField(max_length=30, blank=True, null=True)
|
||||||
|
# 开始时间
|
||||||
start_time = models.DateTimeField()
|
start_time = models.DateTimeField()
|
||||||
|
# 结束时间
|
||||||
end_time = models.DateTimeField()
|
end_time = models.DateTimeField()
|
||||||
|
# 创建时间
|
||||||
|
create_time = models.DateTimeField(auto_now_add=True)
|
||||||
|
# 最后修改时间
|
||||||
|
last_updated_time = models.DateTimeField(auto_now=True)
|
||||||
created_by = models.ForeignKey(User)
|
created_by = models.ForeignKey(User)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "contest"
|
||||||
|
|
||||||
|
|
||||||
class ContestProblem(AbstractProblem):
|
class ContestProblem(AbstractProblem):
|
||||||
contest = models.ForeignKey(Contest)
|
contest = models.ForeignKey(Contest)
|
||||||
|
# 比如A B 或者1 2 或者 a b 将按照这个排序
|
||||||
|
sort_index = models.CharField(max_length=30)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "contest_problem"
|
||||||
|
|
||||||
|
|
||||||
|
class ContestProblemTestCase(models.Model):
|
||||||
|
"""
|
||||||
|
如果比赛是按照通过的测试用例总分计算的话,就需要这个model 记录每个测试用例的分数
|
||||||
|
"""
|
||||||
|
# 测试用例的id 这个还在测试用例的配置文件里面有对应
|
||||||
|
id = models.CharField(max_length=40, primary_key=True, db_index=True)
|
||||||
|
problem = models.ForeignKey(ContestProblem)
|
||||||
|
score = models.IntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "contest_problem_test_case"
|
||||||
|
0
group/__init__.py
Normal file
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
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
@ -47,6 +46,11 @@ INSTALLED_APPS = (
|
|||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
|
||||||
'account',
|
'account',
|
||||||
|
'announcement',
|
||||||
|
'utils',
|
||||||
|
'group',
|
||||||
|
'problem',
|
||||||
|
'admin',
|
||||||
|
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'rest_framework_swagger',
|
'rest_framework_swagger',
|
||||||
@ -61,6 +65,7 @@ MIDDLEWARE_CLASSES = (
|
|||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'admin.middleware.AdminRequiredMiddleware'
|
||||||
)
|
)
|
||||||
|
|
||||||
ROOT_URLCONF = 'oj.urls'
|
ROOT_URLCONF = 'oj.urls'
|
||||||
|
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.contrib import admin
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from account.views import UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView, UserChangePasswordAPIView
|
from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView,
|
||||||
|
UserChangePasswordAPIView, EmailCheckAPIView,
|
||||||
|
UserAPIView, UserAdminAPIView)
|
||||||
|
from announcement.views import AnnouncementAPIView, AnnouncementAdminAPIView
|
||||||
|
from group.views import GroupAdminAPIView
|
||||||
|
from admin.views import AdminTemplateView
|
||||||
|
|
||||||
|
from problem.views import ProblemAdminAPIView
|
||||||
|
from problem.views import TestCaseUploadAPIView, ProblemTagAdminAPIView
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
url(r'^install/$', "install.views.install"),
|
||||||
url("^$", TemplateView.as_view(template_name="oj/index.html"), name="index_page"),
|
url("^$", TemplateView.as_view(template_name="oj/index.html"), name="index_page"),
|
||||||
url(r'^docs/', include('rest_framework_swagger.urls')),
|
url(r'^docs/', include('rest_framework_swagger.urls')),
|
||||||
url(r'^admin/$', TemplateView.as_view(template_name="admin/index.html"), name="admin_index_page"),
|
url(r'^admin/$', TemplateView.as_view(template_name="admin/admin.html"), name="admin_spa_page"),
|
||||||
url(r'^login/$', TemplateView.as_view(template_name="oj/account/login.html"), name="user_login_page"),
|
url(r'^login/$', TemplateView.as_view(template_name="oj/account/login.html"), name="user_login_page"),
|
||||||
|
url(r'^register/$', TemplateView.as_view(template_name="oj/account/register.html"), name="user_register_page"),
|
||||||
|
url(r'^change_password/$', TemplateView.as_view(template_name="oj/account/change_password.html"), name="user_change_password_page"),
|
||||||
url(r'^api/login/$', UserLoginAPIView.as_view(), name="user_login_api"),
|
url(r'^api/login/$', UserLoginAPIView.as_view(), name="user_login_api"),
|
||||||
url(r'^api/register/$', UserRegisterAPIView.as_view(), name="user_register_api"),
|
url(r'^api/register/$', UserRegisterAPIView.as_view(), name="user_register_api"),
|
||||||
url(r'^api/change_password/$', UserChangePasswordAPIView.as_view(), name="user_change_password_api"),
|
url(r'^api/change_password/$', UserChangePasswordAPIView.as_view(), name="user_change_password_api"),
|
||||||
url(r'^api/username_check/$', UsernameCheckAPIView.as_view(), name="username_check_api"),
|
url(r'^api/username_check/$', UsernameCheckAPIView.as_view(), name="username_check_api"),
|
||||||
|
url(r'^api/email_check/$', EmailCheckAPIView.as_view(), name="email_check_api"),
|
||||||
|
url(r'^api/admin/announcement/$', AnnouncementAdminAPIView.as_view(), name="announcement_admin_api"),
|
||||||
|
url(r'^api/admin/user/$', UserAdminAPIView.as_view(), name="user_admin_api"),
|
||||||
url(r'^problem/(?P<problem_id>\d+)/$', "problem.views.problem_page", name="problem_page"),
|
url(r'^problem/(?P<problem_id>\d+)/$', "problem.views.problem_page", name="problem_page"),
|
||||||
|
url(r'^announcement/(?P<announcement_id>\d+)/$', "announcement.views.announcement_page", name="announcement_page"),
|
||||||
|
|
||||||
|
url(r'^api/announcements/$', AnnouncementAPIView.as_view(), name="announcement_list_api"),
|
||||||
|
url(r'^api/admin/users/$', UserAPIView.as_view(), name="user_list_api"),
|
||||||
|
|
||||||
url(r'^admin/contest/$', TemplateView.as_view(template_name="admin/contest/add_contest.html"), name="add_contest_page"),
|
url(r'^admin/contest/$', TemplateView.as_view(template_name="admin/contest/add_contest.html"), name="add_contest_page"),
|
||||||
url(r'^problems/$', TemplateView.as_view(template_name="oj/problem/problem_list.html"), name="problem_list_page"),
|
url(r'^problems/$', TemplateView.as_view(template_name="oj/problem/problem_list.html"), name="problem_list_page"),
|
||||||
|
url(r'^admin/template/(?P<template_dir>\w+)/(?P<template_name>\w+).html', AdminTemplateView.as_view(), name="admin_template"),
|
||||||
|
url(r'^api/admin/group/$', GroupAdminAPIView.as_view(), name="group_admin_api"),
|
||||||
|
url(r'^api/admin/problem/$', ProblemAdminAPIView.as_view(), name="problem_admin_api"),
|
||||||
|
url(r'^api/admin/test_case_upload/$', TestCaseUploadAPIView.as_view(), name="test_case_upload_api"),
|
||||||
|
url(r'^api/admin/tag/$', ProblemTagAdminAPIView.as_view(), name="problem_tag_admin_api"),
|
||||||
]
|
]
|
||||||
|
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):
|
class ProblemTag(models.Model):
|
||||||
pass
|
name = models.CharField(max_length=30)
|
||||||
|
# description = models.CharField(max_length=50)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "problem_tag"
|
||||||
|
|
||||||
|
|
||||||
class AbstractProblem(models.Model):
|
class AbstractProblem(models.Model):
|
||||||
@ -14,21 +18,19 @@ class AbstractProblem(models.Model):
|
|||||||
# 问题描述 HTML 格式
|
# 问题描述 HTML 格式
|
||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
# 样例输入 可能会存储 json 格式的数据
|
# 样例输入 可能会存储 json 格式的数据
|
||||||
sample_input = models.TextField(blank=True)
|
sample = models.TextField(blank=True)
|
||||||
# 样例输出 同上
|
|
||||||
sample_output = models.TextField(blank=True)
|
|
||||||
# 测试用例id 这个id 可以用来拼接得到测试用例的文件存储位置
|
# 测试用例id 这个id 可以用来拼接得到测试用例的文件存储位置
|
||||||
test_case_id = models.CharField(max_length=40)
|
test_case_id = models.CharField(max_length=40)
|
||||||
# 提示
|
# 提示
|
||||||
hint = models.TextField(blank=True)
|
hint = models.TextField(blank=True, null=True)
|
||||||
# 创建时间
|
# 创建时间
|
||||||
create_time = models.DateTimeField(auth_now_add=True)
|
create_time = models.DateTimeField(auto_now_add=True)
|
||||||
# 最后更新时间
|
# 最后更新时间
|
||||||
last_update_time = models.DateTimeField(auto_now=True)
|
last_update_time = models.DateTimeField(auto_now=True)
|
||||||
# 这个题是谁创建的
|
# 这个题是谁创建的
|
||||||
created_by = models.ForeignKey(User)
|
created_by = models.ForeignKey(User)
|
||||||
# 来源
|
# 来源
|
||||||
source = models.CharField(max_length=30, blank=True)
|
source = models.CharField(max_length=30, blank=True, null=True)
|
||||||
# 时间限制 单位是毫秒
|
# 时间限制 单位是毫秒
|
||||||
time_limit = models.IntegerField()
|
time_limit = models.IntegerField()
|
||||||
# 内存限制 单位是MB
|
# 内存限制 单位是MB
|
||||||
@ -39,14 +41,13 @@ class AbstractProblem(models.Model):
|
|||||||
total_submit_number = models.IntegerField(default=0)
|
total_submit_number = models.IntegerField(default=0)
|
||||||
# 通过数量
|
# 通过数量
|
||||||
total_accepted_number = models.IntegerField(default=0)
|
total_accepted_number = models.IntegerField(default=0)
|
||||||
# 标签
|
|
||||||
tags = models.ManyToManyField(ProblemTag, null=True)
|
|
||||||
# 难度 0 - n
|
|
||||||
difficulty = models.IntegerField()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
class Problem(AbstractProblem):
|
class Problem(AbstractProblem):
|
||||||
pass
|
# 难度 0 - n
|
||||||
|
difficulty = models.IntegerField()
|
||||||
|
# 标签
|
||||||
|
tags = models.ManyToManyField(ProblemTag, null=True)
|
||||||
|
71
problem/serizalizers.py
Normal file
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
|
# coding=utf-8
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from rest_framework.test import APITestCase, APIClient
|
||||||
|
|
||||||
|
from account.models import User, SUPER_ADMIN
|
||||||
|
from problem.models import Problem, ProblemTag
|
||||||
|
|
||||||
|
|
||||||
class ProblemPageTest(TestCase):
|
class ProblemPageTest(TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ProblemAdminTest(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = APIClient()
|
||||||
|
self.url = reverse("problem_admin_api")
|
||||||
|
user = User.objects.create(username="test", admin_type=SUPER_ADMIN)
|
||||||
|
user.set_password("testaa")
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
# 以下是发布题目的测试
|
||||||
|
def test_invalid_format(self):
|
||||||
|
self.client.login(username="test", password="testaa")
|
||||||
|
data = {"title": "test1"}
|
||||||
|
response = self.client.post(self.url, data=data)
|
||||||
|
self.assertEqual(response.data["code"], 1)
|
||||||
|
|
||||||
|
def test_success_problem(self):
|
||||||
|
self.client.login(username="test", password="testaa")
|
||||||
|
ProblemTag.objects.create(name="tag1", description="destag1")
|
||||||
|
data = {"title": "title1",
|
||||||
|
"description": "des1",
|
||||||
|
"test_case_id": "1",
|
||||||
|
"source": "source1",
|
||||||
|
"sample": [{"input": "1 1", "output": "2"}],
|
||||||
|
"time_limit": "100",
|
||||||
|
"memory_limit": "1000",
|
||||||
|
"difficulty": "1",
|
||||||
|
"hint": "hint1",
|
||||||
|
"tags": [1]}
|
||||||
|
response = self.client.post(self.url, data=data)
|
||||||
|
self.assertEqual(response.data["code"], 0)
|
||||||
|
|
||||||
|
# 以下是编辑题目的测试
|
||||||
|
def test_put_invalid_data(self):
|
||||||
|
self.client.login(username="test", password="testaa")
|
||||||
|
data = {"title": "test0"}
|
||||||
|
response = self.client.put(self.url, data=data)
|
||||||
|
self.assertEqual(response.data["code"], 1)
|
||||||
|
|
||||||
|
def test_problem_does_not_exist(self):
|
||||||
|
self.client.login(username="test", password="testaa")
|
||||||
|
ProblemTag.objects.create(name="tag1", description="destag1")
|
||||||
|
tags = ProblemTag.objects.filter(id__in=[1])
|
||||||
|
problem = Problem.objects.create(title="title1",
|
||||||
|
description="des1",
|
||||||
|
test_case_id="1",
|
||||||
|
source="source1",
|
||||||
|
sample=[{"input": "1 1", "output": "2"}],
|
||||||
|
time_limit=100,
|
||||||
|
memory_limit=1000,
|
||||||
|
difficulty=1,
|
||||||
|
hint="hint1",
|
||||||
|
created_by=User.objects.get(username="test"))
|
||||||
|
problem.tags.add(*tags)
|
||||||
|
data = {"id": 2,
|
||||||
|
"title": "title1",
|
||||||
|
"description": "des1",
|
||||||
|
"test_case_id": "1",
|
||||||
|
"source": "source1",
|
||||||
|
"sample": [{"input": "1 1", "output": "2"}],
|
||||||
|
"time_limit": "100",
|
||||||
|
"memory_limit": "1000",
|
||||||
|
"difficulty": "1",
|
||||||
|
"hint": "hint1",
|
||||||
|
"tags": [1]}
|
||||||
|
response = self.client.put(self.url, data=data)
|
||||||
|
self.assertEqual(response.data, {"code": 1, "data": u"该题目不存在!"})
|
||||||
|
|
||||||
|
def test_success_edit_problem(self):
|
||||||
|
self.client.login(username="test", password="testaa")
|
||||||
|
self.client.login(username="test", password="testaa")
|
||||||
|
ProblemTag.objects.create(name="tag1", description="destag1")
|
||||||
|
ProblemTag.objects.create(name="tag2", description="destag2")
|
||||||
|
tags = ProblemTag.objects.filter(id__in=[1])
|
||||||
|
problem0 = Problem.objects.create(title="title1",
|
||||||
|
description="des1",
|
||||||
|
test_case_id="1",
|
||||||
|
source="source1",
|
||||||
|
sample=[{"input": "1 1", "output": "2"}],
|
||||||
|
time_limit=100,
|
||||||
|
memory_limit=1000,
|
||||||
|
difficulty=1,
|
||||||
|
hint="hint1",
|
||||||
|
created_by=User.objects.get(username="test"))
|
||||||
|
problem0.tags.add(*tags)
|
||||||
|
data = {"id": 1,
|
||||||
|
"title": "title1",
|
||||||
|
"description": "des1",
|
||||||
|
"test_case_id": "1",
|
||||||
|
"source": "source1",
|
||||||
|
"sample": [{"input": "1 1", "output": "2"}],
|
||||||
|
"time_limit": "100",
|
||||||
|
"memory_limit": "1000",
|
||||||
|
"difficulty": "1",
|
||||||
|
"hint": "hint1",
|
||||||
|
"visible": True,
|
||||||
|
"tags": [1, 2]}
|
||||||
|
problem = Problem.objects.get(id=data["id"])
|
||||||
|
problem.tags.remove(*problem.tags.all())
|
||||||
|
problem.tags.add(*ProblemTag.objects.filter(id__in=data["tags"]))
|
||||||
|
response = self.client.put(self.url, data=data)
|
||||||
|
self.assertEqual(response.data["code"], 0)
|
||||||
|
204
problem/views.py
204
problem/views.py
@ -1,7 +1,211 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
import zipfile
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
from utils.shortcuts import serializer_invalid_response, error_response, success_response, paginate, rand_str
|
||||||
|
from .serizalizers import (CreateProblemSerializer, EditProblemSerializer, ProblemSerializer,
|
||||||
|
ProblemTagSerializer, CreateProblemTagSerializer)
|
||||||
|
from .models import Problem, ProblemTag
|
||||||
|
|
||||||
|
|
||||||
|
class ProblemTagAdminAPIView(APIView):
|
||||||
|
def post(self, request):
|
||||||
|
"""
|
||||||
|
创建标签的接口
|
||||||
|
---
|
||||||
|
request_serializer: CreateProblemTagSerializer
|
||||||
|
"""
|
||||||
|
serializer = CreateProblemTagSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
try:
|
||||||
|
tag = ProblemTag.objects.get(name=serializer.data["name"])
|
||||||
|
except ProblemTag.DoesNotExist:
|
||||||
|
tag = ProblemTag.objects.create(name=serializer.data["name"])
|
||||||
|
return success_response(ProblemTagSerializer(tag).data)
|
||||||
|
else:
|
||||||
|
return error_response(serializer)
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
keyword = request.GET.get("keyword", None)
|
||||||
|
if not keyword:
|
||||||
|
return error_response(u"参数错误")
|
||||||
|
tags = ProblemTag.objects.filter(name__contains=keyword)
|
||||||
|
return success_response(ProblemTagSerializer(tags, many=True).data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def problem_page(request, problem_id):
|
def problem_page(request, problem_id):
|
||||||
# todo
|
# todo
|
||||||
return render(request, "oj/problem/problem.html")
|
return render(request, "oj/problem/problem.html")
|
||||||
|
|
||||||
|
|
||||||
|
class ProblemAdminAPIView(APIView):
|
||||||
|
def post(self, request):
|
||||||
|
"""
|
||||||
|
题目发布json api接口
|
||||||
|
---
|
||||||
|
request_serializer: CreateProblemSerializer
|
||||||
|
response_serializer: ProblemSerializer
|
||||||
|
"""
|
||||||
|
serializer = CreateProblemSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
data = serializer.data
|
||||||
|
problem = Problem.objects.create(title=data["title"],
|
||||||
|
description=data["description"],
|
||||||
|
test_case_id=data["test_case_id"],
|
||||||
|
source=data["source"],
|
||||||
|
sample=json.dumps(data["sample"]),
|
||||||
|
time_limit=data["time_limit"],
|
||||||
|
memory_limit=data["memory_limit"],
|
||||||
|
difficulty=data["difficulty"],
|
||||||
|
created_by=request.user,
|
||||||
|
hint=data["hint"])
|
||||||
|
|
||||||
|
tags = ProblemTag.objects.filter(id__in=data["tags"])
|
||||||
|
problem.tags.add(*tags)
|
||||||
|
return success_response(ProblemSerializer(problem).data)
|
||||||
|
else:
|
||||||
|
return serializer_invalid_response(serializer)
|
||||||
|
|
||||||
|
def put(self, request):
|
||||||
|
"""
|
||||||
|
题目编辑json api接口
|
||||||
|
---
|
||||||
|
request_serializer: EditProblemSerializer
|
||||||
|
response_serializer: ProblemSerializer
|
||||||
|
"""
|
||||||
|
serializer = EditProblemSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
data = serializer.data
|
||||||
|
try:
|
||||||
|
problem = Problem.objects.get(id=data["id"])
|
||||||
|
except Problem.DoesNotExist:
|
||||||
|
return error_response(u"该题目不存在!")
|
||||||
|
|
||||||
|
problem.title = data["title"]
|
||||||
|
problem.description = data["description"]
|
||||||
|
problem.test_case_id = data["test_case_id"]
|
||||||
|
problem.source = data["source"]
|
||||||
|
problem.time_limit = data["time_limit"]
|
||||||
|
problem.memory_limit = data["memory_limit"]
|
||||||
|
problem.difficulty = data["difficulty"]
|
||||||
|
problem.sample = json.dumps(data["sample"])
|
||||||
|
problem.hint = data["hint"]
|
||||||
|
problem.visible = data["visible"]
|
||||||
|
|
||||||
|
# 删除原有的标签的对应关系
|
||||||
|
problem.tags.remove(*problem.tags.all())
|
||||||
|
# 重新添加所有的标签
|
||||||
|
problem.tags.add(*ProblemTag.objects.filter(id__in=data["tags"]))
|
||||||
|
problem.save()
|
||||||
|
return success_response(ProblemSerializer(problem).data)
|
||||||
|
else:
|
||||||
|
return serializer_invalid_response(serializer)
|
||||||
|
|
||||||
|
|
||||||
|
class ProblemAPIView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
"""
|
||||||
|
题目分页json api接口
|
||||||
|
---
|
||||||
|
response_serializer: ProblemSerializer
|
||||||
|
"""
|
||||||
|
problem = Problem.objects.all().order_by("-last_update_time")
|
||||||
|
visible = request.GET.get("visible", None)
|
||||||
|
if visible:
|
||||||
|
problem = problem.filter(visible=(visible == "true"))
|
||||||
|
keyword = request.GET.get("keyword", None)
|
||||||
|
if keyword:
|
||||||
|
problem = problem.filter(Q(difficulty__contains=keyword))
|
||||||
|
|
||||||
|
return paginate(request, problem, ProblemSerializer)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseUploadAPIView(APIView):
|
||||||
|
def _is_legal_test_case_file_name(self, file_name):
|
||||||
|
# 正整数开头的 .in 或者.out 结尾的
|
||||||
|
regex = r"^[1-9]\d*\.(in|out)$"
|
||||||
|
return re.compile(regex).match(file_name) is not None
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
if "file" not in request.FILES:
|
||||||
|
return error_response(u"文件上传失败")
|
||||||
|
|
||||||
|
f = request.FILES["file"]
|
||||||
|
|
||||||
|
tmp_zip = "tmp/" + rand_str() + ".zip"
|
||||||
|
with open(tmp_zip, "wb") as test_case_zip:
|
||||||
|
for chunk in f:
|
||||||
|
test_case_zip.write(chunk)
|
||||||
|
|
||||||
|
test_case_file = zipfile.ZipFile(tmp_zip, 'r')
|
||||||
|
name_list = test_case_file.namelist()
|
||||||
|
|
||||||
|
l = []
|
||||||
|
|
||||||
|
# 如果文件是直接打包的,那么name_list 就是["1.in", "1.out"]这样的
|
||||||
|
# 如果文件还有一层文件夹test_case,那么name_list就是["test_case/", "test_case/1.in", "test_case/1.out"]
|
||||||
|
# 现在暂时只支持第一种,先判断一下是什么格式的
|
||||||
|
|
||||||
|
# 第一种格式的
|
||||||
|
if "1.in" in name_list and "1.out" in name_list:
|
||||||
|
for file_name in name_list:
|
||||||
|
if self._is_legal_test_case_file_name(file_name):
|
||||||
|
name = file_name.split(".")
|
||||||
|
# 有了.in 判断对应的.out 在不在
|
||||||
|
if name[1] == "in":
|
||||||
|
if (name[0] + ".out") in name_list:
|
||||||
|
l.append(file_name)
|
||||||
|
else:
|
||||||
|
return error_response(u"测试用例文件不完整,缺少" + name[0] + ".out")
|
||||||
|
else:
|
||||||
|
# 有了.out 判断对应的 .in 在不在
|
||||||
|
if (name[0] + ".in") in name_list:
|
||||||
|
l.append(file_name)
|
||||||
|
else:
|
||||||
|
return error_response(u"测试用例文件不完整,缺少" + name[0] + ".in")
|
||||||
|
|
||||||
|
problem_test_dir = rand_str()
|
||||||
|
test_case_dir = "test_case/" + problem_test_dir + "/"
|
||||||
|
|
||||||
|
# 得到了合法的测试用例文件列表 然后去解压缩
|
||||||
|
os.mkdir(test_case_dir)
|
||||||
|
for name in l:
|
||||||
|
f = open(test_case_dir + name, "wb")
|
||||||
|
f.write(test_case_file.read(name))
|
||||||
|
f.close()
|
||||||
|
l.sort()
|
||||||
|
|
||||||
|
file_info = {"test_case_number": len(l) / 2, "test_cases": {}}
|
||||||
|
|
||||||
|
# 计算输出文件的md5
|
||||||
|
for i in range(len(l) / 2):
|
||||||
|
md5 = hashlib.md5()
|
||||||
|
f = open(test_case_dir + str(i + 1) + ".out", "r")
|
||||||
|
while True:
|
||||||
|
data = f.read(2 ** 8)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
md5.update(data)
|
||||||
|
|
||||||
|
file_info["test_cases"][str(i + 1)] = {"input_name": str(i + 1) + ".in",
|
||||||
|
"output_name": str(i + 1) + ".out",
|
||||||
|
"output_md5": md5.hexdigest(),
|
||||||
|
"output_size": os.path.getsize(test_case_dir + str(i + 1) + ".out")}
|
||||||
|
# 写入配置文件
|
||||||
|
open(test_case_dir + "info", "w").write(json.dumps(file_info))
|
||||||
|
|
||||||
|
return success_response({"test_case_id": problem_test_dir,
|
||||||
|
"file_list": {"input": l[0::2],
|
||||||
|
"output": l[1::2]}})
|
||||||
|
else:
|
||||||
|
return error_response(u"测试用例压缩文件格式错误,请保证测试用例文件在根目录下直接压缩")
|
@ -6,3 +6,4 @@ djangorestframework
|
|||||||
django-rest-swagger
|
django-rest-swagger
|
||||||
celery
|
celery
|
||||||
gunicorn
|
gunicorn
|
||||||
|
coverage
|
@ -1,25 +1,34 @@
|
|||||||
|
@import url("global.css");
|
||||||
@import url("bootstrap/bootstrap.min.css");
|
@import url("bootstrap/bootstrap.min.css");
|
||||||
@import url("bootstrap/todc-bootstrap.min.css");
|
@import url("bootstrap/todc-bootstrap.min.css");
|
||||||
@import url("codeMirror/codemirror.css");
|
@import url("codeMirror/codemirror.css");
|
||||||
@import url("simditor/simditor.css");
|
@import url("simditor/simditor.css");
|
||||||
@import url("webuploader/webuploader.css");
|
@import url("webuploader/webuploader.css");
|
||||||
@import url("datetime_picker/bootstrap-datetimepicker.css");
|
@import url("datetime_picker/bootstrap-datetimepicker.css");
|
||||||
html, body {
|
|
||||||
height: 100%;
|
#loading-gif{
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; bottom: 0; right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
.line-chart{
|
||||||
max-width: 100%;
|
min-width:100%;
|
||||||
height: auto;
|
max-height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.pie-chart{
|
||||||
padding-top: 30px;
|
min-width:100%;
|
||||||
padding-bottom: 30px;
|
max-height: 300px;
|
||||||
float: bottom;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
.chart-description{
|
||||||
font-size: 16px;
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-chart-container{
|
||||||
|
width: 50%;
|
||||||
|
float: left;
|
||||||
}
|
}
|
31
static/src/css/global.css
Normal file
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/bootstrap.min.css");
|
||||||
@import url("bootstrap/todc-bootstrap.min.css");
|
@import url("bootstrap/todc-bootstrap.min.css");
|
||||||
@import url("codeMirror/codemirror.css");
|
@import url("codeMirror/codemirror.css");
|
||||||
html, body {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
padding-top: 30px;
|
|
||||||
padding-bottom: 30px;
|
|
||||||
float: bottom;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#language-selector {
|
#language-selector {
|
||||||
width: 130px;
|
width: 130px;
|
||||||
@ -47,7 +30,6 @@ label {
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* index css */
|
|
||||||
.jumbotron {
|
.jumbotron {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
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",
|
require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
|
||||||
"validation"
|
"validation","tagEditor"],
|
||||||
],
|
|
||||||
function ($, avalon, editor, uploader) {
|
function ($, avalon, editor, uploader) {
|
||||||
|
avalon.vmodels.add_contest = null;
|
||||||
$("#add-contest-form")
|
$("#add-contest-form")
|
||||||
.formValidation({
|
.formValidation({
|
||||||
framework: "bootstrap",
|
framework: "bootstrap",
|
||||||
@ -18,6 +18,13 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
description: {
|
||||||
|
validators: {
|
||||||
|
notEmpty: {
|
||||||
|
message: "请输入描述"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
start_time: {
|
start_time: {
|
||||||
validators: {
|
validators: {
|
||||||
notEmpty: {
|
notEmpty: {
|
||||||
@ -92,9 +99,25 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
|
|||||||
})
|
})
|
||||||
.on("success.form.fv", function (e) {
|
.on("success.form.fv", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
alert("1111");
|
var data = {
|
||||||
|
title: vm.title, description: vm.description, start_time: vm.startTime, end_time: vm.endTime,
|
||||||
|
password: vm.password, model: vm.model, open_rank: vm.openRank, problems: []
|
||||||
|
};
|
||||||
|
for (var i = 0; i < vm.problems.length; i++) {
|
||||||
|
var problem = {
|
||||||
|
title: vm.problems[i].title, description: vm.problems[i].description,
|
||||||
|
cpu: vm.problems[i].cpu, memory: vm.problems[i].memory, samples: []
|
||||||
|
};
|
||||||
|
for (var j = 0; j < vm.problems[i].samples.length; j++) {
|
||||||
|
problem.samples.push({
|
||||||
|
input: vm.problems[i].samples[j].input,
|
||||||
|
output: vm.problems[i].samples[j].output
|
||||||
|
})
|
||||||
|
}
|
||||||
|
data.problems.push(problem);
|
||||||
|
}
|
||||||
|
console.log(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
function make_id() {
|
function make_id() {
|
||||||
var text = "";
|
var text = "";
|
||||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
@ -102,21 +125,59 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
|
|||||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var editor1 = editor("#editor");
|
var editor1 = editor("#editor");
|
||||||
|
|
||||||
var vm = avalon.define({
|
var vm = avalon.define({
|
||||||
$id: "add_contest",
|
$id: "add_contest",
|
||||||
|
title: "",
|
||||||
|
description: "",
|
||||||
|
startTime: "",
|
||||||
|
endTime: "",
|
||||||
|
password: "",
|
||||||
|
model: "",
|
||||||
|
openRank: false,
|
||||||
problems: [],
|
problems: [],
|
||||||
add_problem: function () {
|
add_problem: function () {
|
||||||
var problem = {};
|
|
||||||
var problem_id = make_id();
|
var problem_id = make_id();
|
||||||
problem["id"] = problem_id;
|
var problem = {
|
||||||
problem["samples"] = [];
|
id: problem_id,
|
||||||
problem["webuploader"] = {};
|
title: "",
|
||||||
problem["toggle_string"] = "折叠";
|
cpu: "",
|
||||||
|
memory: "",
|
||||||
|
description: "",
|
||||||
|
samples: [],
|
||||||
|
visible: true,
|
||||||
|
test_case_id: "",
|
||||||
|
testCaseList: [],
|
||||||
|
hint: "",
|
||||||
|
isVisible: false,
|
||||||
|
difficulty: 0,
|
||||||
|
tags: [],
|
||||||
|
tag: ""
|
||||||
|
};
|
||||||
vm.problems.push(problem);
|
vm.problems.push(problem);
|
||||||
uploader("#problem-" + problem_id + "-uploader");
|
var id = vm.problems.length - 1;
|
||||||
console.log(vm.problems);
|
editor("#problem-" + problem_id + "-description");
|
||||||
|
var hinteditor = editor("#problem-" + problem_id +"-hint");
|
||||||
|
$("#problem-" + problem_id +"-tags").tagEditor();
|
||||||
|
uploader("#problem-" + problem_id + "-uploader", "/api/admin/test_case_upload/", function (file, respond) {
|
||||||
|
console.log(respond);
|
||||||
|
if (respond.code)
|
||||||
|
bs_alert(respond.data);
|
||||||
|
else {
|
||||||
|
vm.problems[id].test_case_id = respond.data.test_case_id;
|
||||||
|
vm.problems[id].uploadSuccess = true;
|
||||||
|
vm.problems[id].testCaseList = [];
|
||||||
|
for (var i = 0; i < respond.data.file_list.input.length; i++) {
|
||||||
|
vm.problems[id].push({
|
||||||
|
input: respond.data.file_list.input[i],
|
||||||
|
output: respond.data.file_list.output[i]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
$("#add-contest-form").formValidation('addField', $('[name="problem_name[]"]'));
|
$("#add-contest-form").formValidation('addField', $('[name="problem_name[]"]'));
|
||||||
$("#add-contest-form").formValidation('addField', $('[name="cpu[]"]'));
|
$("#add-contest-form").formValidation('addField', $('[name="cpu[]"]'));
|
||||||
$("#add-contest-form").formValidation('addField', $('[name="memory[]"]'));
|
$("#add-contest-form").formValidation('addField', $('[name="memory[]"]'));
|
||||||
@ -126,31 +187,21 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
|
|||||||
vm.problems.remove(problem);
|
vm.problems.remove(problem);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggle_problem: function (problem) {
|
toggle: function (item) {
|
||||||
$("#" + "problem-" + problem.id + "-body").toggle();
|
item.visible = !item.visible;
|
||||||
if (problem["toggle_string"] == "展开") {
|
|
||||||
problem["toggle_string"] = "折叠";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
problem["toggle_string"] = "展开";
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
add_sample: function (problem) {
|
add_sample: function (problem) {
|
||||||
problem["samples"].push({"id": make_id(), "toggle_string": "折叠"});
|
problem.samples.push({id: make_id(), visible: true, input: "", output: ""});
|
||||||
},
|
},
|
||||||
del_sample: function (problem, sample) {
|
del_sample: function (problem, sample) {
|
||||||
if (confirm("你确定要删除么?")) {
|
if (confirm("你确定要删除么?")) {
|
||||||
problem["samples"].remove(sample);
|
problem.samples.remove(sample);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggle_sample: function (problem, sample) {
|
getBtnContent: function (item) {
|
||||||
$("#" + "problem-" + problem.id + "-sampleio-" + sample.id + "-body").toggle();
|
if (item.visible)
|
||||||
if (sample["toggle_string"] == "展开") {
|
return "折叠";
|
||||||
sample["toggle_string"] = "折叠";
|
return "展开";
|
||||||
}
|
|
||||||
else {
|
|
||||||
sample["toggle_string"] = "展开";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
avalon.scan();
|
avalon.scan();
|
||||||
@ -167,7 +218,6 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
|
|||||||
weekStart: 1,
|
weekStart: 1,
|
||||||
language: "zh-CN"
|
language: "zh-CN"
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#contest_start_time").datetimepicker()
|
$("#contest_start_time").datetimepicker()
|
||||||
.on("hide", function (ev) {
|
.on("hide", function (ev) {
|
||||||
$("#add-contest-form")
|
$("#add-contest-form")
|
||||||
|
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,4 +1,4 @@
|
|||||||
require(["jquery", "bs_alert", "validation"], function($, bs_alert){
|
require(["jquery", "bs_alert", "csrf", "validation"], function ($, bs_alert, csrfHeader) {
|
||||||
$("#login-form")
|
$("#login-form")
|
||||||
.formValidation({
|
.formValidation({
|
||||||
framework: "bootstrap",
|
framework: "bootstrap",
|
||||||
@ -19,20 +19,21 @@ require(["jquery", "bs_alert", "validation"], function($, bs_alert){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).on('success.form.fv', function(e) {
|
).on('success.form.fv', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var username = $("#username").val();
|
var username = $("#username").val();
|
||||||
var password = $("#password").val();
|
var password = $("#password").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
beforeSend: csrfHeader,
|
||||||
url: "/api/login/",
|
url: "/api/login/",
|
||||||
data: {username: username, password: password},
|
data: {username: username, password: password},
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
method: "post",
|
method: "post",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
if(!data.code){
|
if (!data.code) {
|
||||||
window.location.href="/";
|
window.location.href = "/";
|
||||||
}
|
}
|
||||||
else{
|
else {
|
||||||
bs_alert(data.data);
|
bs_alert(data.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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",
|
bs_alert: "utils/bs_alert",
|
||||||
submit_code: "app/oj/problem/submit_code",
|
submit_code: "app/oj/problem/submit_code",
|
||||||
contest: "app/admin/contest/contest",
|
contest: "app/admin/contest/contest",
|
||||||
|
csrf: "utils/csrf",
|
||||||
|
admin: "app/admin/admin",
|
||||||
|
chart: "lib/chart/Chart",
|
||||||
|
tagEditor: "lib/tagEditor/jquery.tag-editor.min",
|
||||||
|
jqueryUI: "lib/jqueryUI/jquery-ui",
|
||||||
//formValidation 不要在代码中单独使用,而是使用和修改utils/validation
|
//formValidation 不要在代码中单独使用,而是使用和修改utils/validation
|
||||||
base: "lib/formValidation/base",
|
base: "lib/formValidation/base",
|
||||||
helper: "lib/formValidation/helper",
|
helper: "lib/formValidation/helper",
|
||||||
@ -25,7 +29,9 @@ var require = {
|
|||||||
"validator/date": "lib/formValidation/validator/date",
|
"validator/date": "lib/formValidation/validator/date",
|
||||||
"validator/integer": "lib/formValidation/validator/integer",
|
"validator/integer": "lib/formValidation/validator/integer",
|
||||||
"validator/between": "lib/formValidation/validator/between",
|
"validator/between": "lib/formValidation/validator/between",
|
||||||
|
"validator/confirm":"lib/formValidation/validator/confirm",
|
||||||
|
"validator/remote":"lib/formValidation/validator/remote",
|
||||||
|
"validator/emailAddress":"lib/formValidation/validator/emailAddress",
|
||||||
//富文本编辑器 不要直接使用,而是使用上面的editor
|
//富文本编辑器 不要直接使用,而是使用上面的editor
|
||||||
simditor: "lib/simditor/simditor",
|
simditor: "lib/simditor/simditor",
|
||||||
"simple-module": "lib/simditor/module",
|
"simple-module": "lib/simditor/module",
|
||||||
|
3477
static/src/js/lib/chart/Chart.js
vendored
Executable file
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
|
* remote validator
|
||||||
*
|
|
||||||
* @link http://formvalidation.io/validators/remote/
|
|
||||||
* @author https://twitter.com/nghuuphuoc
|
|
||||||
* @copyright (c) 2013 - 2015 Nguyen Huu Phuoc
|
|
||||||
* @license http://formvalidation.io/license/
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function(root, factory) {
|
(function(root, factory) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// AMD module is defined
|
// AMD module is defined
|
||||||
if (typeof define === "function" && define.amd) {
|
if (typeof define === "function" && define.amd) {
|
||||||
define("validator/remote", ["jquery", "base"], factory);
|
define("validator/remote", ["jquery", "base", "csrf"], factory);
|
||||||
} else {
|
} else {
|
||||||
// planted over the root!
|
// planted over the root!
|
||||||
factory(root.jQuery, root.FormValidation);
|
factory(root.jQuery, root.FormValidation);
|
||||||
}
|
}
|
||||||
|
}(this, function ($, FormValidation, csrfHeader) {
|
||||||
}(this, function ($, FormValidation) {
|
|
||||||
FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
|
FormValidation.I18n = $.extend(true, FormValidation.I18n || {}, {
|
||||||
'en_US': {
|
'en_US': {
|
||||||
remote: {
|
remote: {
|
||||||
'default': 'Please enter a valid value'
|
'default': ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
FormValidation.Validator.remote = {
|
FormValidation.Validator.remote = {
|
||||||
html5Attributes: {
|
|
||||||
message: 'message',
|
|
||||||
name: 'name',
|
|
||||||
type: 'type',
|
|
||||||
url: 'url',
|
|
||||||
data: 'data',
|
|
||||||
delay: 'delay'
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy the timer when destroying the bootstrapValidator (using validator.destroy() method)
|
|
||||||
*/
|
|
||||||
destroy: function(validator, $field, options) {
|
|
||||||
var ns = validator.getNamespace(),
|
|
||||||
timer = $field.data(ns + '.remote.timer');
|
|
||||||
if (timer) {
|
|
||||||
clearTimeout(timer);
|
|
||||||
$field.removeData(ns + '.remote.timer');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request a remote server to check the input value
|
|
||||||
*
|
|
||||||
* @param {FormValidation.Base} validator Plugin instance
|
|
||||||
* @param {jQuery} $field Field element
|
|
||||||
* @param {Object} options Can consist of the following keys:
|
|
||||||
* - url {String|Function}
|
|
||||||
* - type {String} [optional] Can be GET or POST (default)
|
|
||||||
* - data {Object|Function} [optional]: By default, it will take the value
|
|
||||||
* {
|
|
||||||
* <fieldName>: <fieldValue>
|
|
||||||
* }
|
|
||||||
* - delay
|
|
||||||
* - name {String} [optional]: Override the field name for the request.
|
|
||||||
* - message: The invalid message
|
|
||||||
* - headers: Additional headers
|
|
||||||
* @returns {Deferred}
|
|
||||||
*/
|
|
||||||
validate: function(validator, $field, options) {
|
validate: function(validator, $field, options) {
|
||||||
var ns = validator.getNamespace(),
|
var dfd = new $.Deferred(), ajaxData = {};
|
||||||
value = validator.getFieldValue($field, 'remote'),
|
ajaxData[options.field] = $field.val();
|
||||||
dfd = new $.Deferred();
|
if ($field.val() === '')
|
||||||
if (value === '') {
|
return true;
|
||||||
dfd.resolve($field, 'remote', { valid: true });
|
var url = options.url;
|
||||||
return dfd;
|
|
||||||
}
|
|
||||||
|
|
||||||
var 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({
|
var xhr = $.ajax({
|
||||||
type: type,
|
beforeSend: csrfHeader,
|
||||||
headers: headers,
|
|
||||||
url: url,
|
url: url,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data: data
|
data: ajaxData,
|
||||||
|
method: "post"
|
||||||
});
|
});
|
||||||
|
xhr.success(function(response) {
|
||||||
xhr
|
if (response.code == 1)
|
||||||
.success(function(response) {
|
dfd.resolve($field, 'remote',{valid:true, message:options.msg});
|
||||||
response.valid = response.valid === true || response.valid === 'true';
|
dfd.resolve($field, 'remote',{valid:!response.data, message:options.msg});
|
||||||
dfd.resolve($field, 'remote', response);
|
|
||||||
})
|
})
|
||||||
.error(function(response) {
|
.error(function(response) {
|
||||||
dfd.resolve($field, 'remote', {
|
dfd.resolve($field, 'remote', {valid: false});
|
||||||
valid: false
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
dfd.fail(function() {
|
|
||||||
xhr.abort();
|
|
||||||
});
|
|
||||||
|
|
||||||
return dfd;
|
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;
|
|
||||||
}));
|
}));
|
||||||
|
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){
|
define("uploader", ["webuploader", "csrf"], function(webuploader,csrf){
|
||||||
function uploader(selector) {
|
function uploader(selector, server, onSuccess) {
|
||||||
return webuploader.create({
|
var Webuploader= webuploader.create({
|
||||||
|
auto: true,
|
||||||
// swf文件路径
|
// swf文件路径
|
||||||
swf: "/js/Uploader.swf",
|
swf: "/static/img/Uploader.swf",
|
||||||
|
|
||||||
// 文件接收服务端。
|
// 文件接收服务端。
|
||||||
server: "http://webuploader.duapp.com/server/fileupload.php",
|
server: server,
|
||||||
|
|
||||||
// 选择文件的按钮。可选。
|
// 选择文件的按钮。可选。
|
||||||
// 内部根据当前运行是创建,可能是input元素,也可能是flash.
|
// 内部根据当前运行是创建,可能是input元素,也可能是flash.
|
||||||
pick: selector,
|
pick: selector,
|
||||||
|
|
||||||
// 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传!
|
// 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传!
|
||||||
resize: false
|
resize: false,
|
||||||
|
uploadBeforeSend : csrf
|
||||||
});
|
});
|
||||||
|
Webuploader.on("uploadBeforeSend",csrf);
|
||||||
|
Webuploader.on("uploadSuccess", onSuccess);
|
||||||
|
|
||||||
|
return Webuploader;
|
||||||
}
|
}
|
||||||
|
|
||||||
return uploader;
|
return uploader;
|
||||||
});
|
});
|
@ -7,5 +7,9 @@ define("validation",
|
|||||||
'validator/stringLength',
|
'validator/stringLength',
|
||||||
'validator/date',
|
'validator/date',
|
||||||
'validator/integer',
|
'validator/integer',
|
||||||
'validator/between'], function () {
|
'validator/between',
|
||||||
|
'validator/confirm',
|
||||||
|
'validator/remote',
|
||||||
|
'validator/emailAddress'],
|
||||||
|
function () {
|
||||||
});
|
});
|
150
template/admin/admin.html
Normal file
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,17 +1,12 @@
|
|||||||
{% extends "admin_base.html" %}
|
<div ms-controller="add_contest">
|
||||||
{% block body %}
|
|
||||||
{% verbatim %}
|
|
||||||
<div ms-controller="add_contest">
|
|
||||||
|
|
||||||
|
|
||||||
<form id="add-contest-form">
|
<form id="add-contest-form">
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<label>比赛题目</label>
|
<label>比赛名称</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" name="name" class="form-control">
|
<input type="text" name="name" class="form-control" ms-duplex="title">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
@ -19,7 +14,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<textarea id="editor" placeholder="这里输入内容" autofocus></textarea>
|
<textarea id="editor" placeholder="这里输入内容" autofocus ms-duplex="description"></textarea>
|
||||||
|
<small ms-visible="description==''" style="color:red">请填写比赛描述</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@ -30,12 +26,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" name="start_time" id="contest_start_time">
|
<input type="text" class="form-control" name="start_time" id="contest_start_time"
|
||||||
|
ms-duplex="startTime">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" name="end_time" id="contest_end_time">
|
<input type="text" class="form-control" name="end_time" id="contest_end_time" ms-duplex="endTime">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -56,18 +53,24 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛">
|
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="password">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="radio" name="mode">OI
|
<label><input type="radio" name="mode" ms-duplex-checked="model">
|
||||||
<input type="radio" name="mode">ACM
|
<small>OI</small>
|
||||||
|
</label>
|
||||||
|
<label><input type="radio" name="mode">
|
||||||
|
<small>ACM</small>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="checkbox" value="open_rank">开放排名
|
<label class="text"><input type="checkbox" ms-duplex-checked="openRank">
|
||||||
|
<small>开放排名</small>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -80,41 +83,58 @@
|
|||||||
<div class="panel panel-default problem-panel" ms-attr-id="problem-{{ problem.id }}">
|
<div class="panel panel-default problem-panel" ms-attr-id="problem-{{ problem.id }}">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<span class="panel-title">题目{{$index + 1}} </span>
|
<span class="panel-title">题目{{$index + 1}} </span>
|
||||||
<a href="javascript:void(0)" class="btn btn-primary btn-sm"
|
<a href="javascript:void(0)" class="btn btn-primary btn-sm" ms-click="toggle(problem)">
|
||||||
ms-click="toggle_problem(problem)">
|
{{getBtnContent(problem)}}
|
||||||
{{ problem.toggle_string }}
|
|
||||||
</a>
|
</a>
|
||||||
<a href="javascript:void(0)" class="btn btn-danger btn-sm"
|
<a href="javascript:void(0)" class="btn btn-danger btn-sm" ms-click="del_problem(problem)">
|
||||||
ms-click="del_problem(problem)">
|
|
||||||
删除
|
删除
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body" ms-attr-id="problem-{{ problem.id }}-body">
|
<div class="panel-body" ms-visible="problem.visible">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<label>题目</label>
|
<label>题目</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" name="problem_name[]" class="form-control">
|
<input type="text" name="problem_name[]" class="form-control"
|
||||||
|
ms-duplex="problem.title">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<label>题目描述</label>
|
||||||
<label>cpu</label>
|
<textarea ms-attr-id="problem-{{ problem.id }}-description" placeholder="这里输入内容"
|
||||||
</div>
|
ms-duplex="problem.description"></textarea>
|
||||||
<div class="col-md-6">
|
<small ms-visible="problem.description==''" style="color:red">请填写题目描述</small>
|
||||||
<label>内存</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" name="cpu[]" class="form-control">
|
<label>提示</label>
|
||||||
|
<textarea ms-attr-id="problem-{{ problem.id }}-hint" placeholder="这里输入内容" ms-duplex="problem.hint"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-3">
|
||||||
<div class="form-group">
|
<div class="form-group"><label>内存</label>
|
||||||
<input type="text" name="memory[]" class="form-control">
|
<input type="number" name="memory[]" class="form-control" ms-duplex="problem.memory">
|
||||||
</div>
|
</div>
|
||||||
</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="col-md-12">
|
||||||
|
|
||||||
<label>样例</label>
|
<label>样例</label>
|
||||||
@ -130,8 +150,8 @@
|
|||||||
<span class="panel-title">样例{{$index + 1}}</span>
|
<span class="panel-title">样例{{$index + 1}}</span>
|
||||||
|
|
||||||
<a href="javascript:void(0)" class="btn btn-primary btn-sm"
|
<a href="javascript:void(0)" class="btn btn-primary btn-sm"
|
||||||
ms-click="toggle_sample(problem, sample)">
|
ms-click="toggle(sample)">
|
||||||
{{ sample.toggle_string }}
|
{{getBtnContent(sample)}}
|
||||||
</a>
|
</a>
|
||||||
<a href="javascript:void(0)" class="btn btn-danger btn-sm"
|
<a href="javascript:void(0)" class="btn btn-danger btn-sm"
|
||||||
ms-click="del_sample(problem, sample)">
|
ms-click="del_sample(problem, sample)">
|
||||||
@ -139,23 +159,19 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body"
|
<div class="panel-body row" ms-visible="sample.visible">
|
||||||
ms-attr-id="problem-{{ problem.id }}-sampleio-{{ sample.id }}-body">
|
<div class="col-md-6">
|
||||||
<div class="col-md-12">
|
<div class="form-group">
|
||||||
<label>样例输入</label>
|
<label>样例输入</label>
|
||||||
|
<textarea class="form-control" rows="5"
|
||||||
|
ms-duplex="sample.input"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12">
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<textarea class="form-control" rows="5"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12">
|
|
||||||
<label>样例输出</label>
|
<label>样例输出</label>
|
||||||
</div>
|
<textarea class="form-control" rows="5"
|
||||||
<div class="col-md-12">
|
ms-duplex="sample.output"></textarea>
|
||||||
<div class="form-group">
|
|
||||||
<textarea class="form-control" rows="5"></textarea>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -163,23 +179,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<label>测试数据</label>
|
<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>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div ms-attr-id="problem-{{ problem.id }}-uploader">选择文件</div>
|
<div ms-attr-id="problem-{{ problem.id }}-uploader">选择文件</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<input type="submit" class="btn btn-success btn-lg" value="发布比赛">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endverbatim %}
|
|
||||||
{% endblock %}
|
<script src="/static/js/app/admin/contest/contest.js"></script>
|
||||||
{% block js_block %}
|
<link href="/static/css/tagEditor/jquery.tag-editor.css" rel="stylesheet">
|
||||||
<script src="/static/js/app/admin/contest/contest.js"></script>
|
|
||||||
{% endblock %}
|
|
@ -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]-->
|
<![endif]-->
|
||||||
<!-- browser happy end -->
|
<!-- browser happy end -->
|
||||||
|
|
||||||
<div class="container">
|
<div class="container" ms-controller="admin">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- admin left begin-->
|
<!-- admin left begin-->
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<li class="list-group-header">List header</li>
|
<li class="list-group-header">List header</li>
|
||||||
<li class="list-group-item active"><a href="#">Home</a></li>
|
<li class="list-group-item" id="li-index"><a href="#index">主页</a></li>
|
||||||
<li class="list-group-item"><a href="#">Library</a></li>
|
<li class="list-group-item" id="li-announcement"><a href="#announcement">公告</a></li>
|
||||||
<li class="list-group-item"><a href="#">Applications</a></li>
|
<li class="list-group-item"><a href="#">Applications</a></li>
|
||||||
<li class="list-group-header">Another list header</li>
|
<li class="list-group-header">Another list header</li>
|
||||||
<li class="list-group-item"><a href="#">Help</a></li>
|
<li class="list-group-item"><a href="#">Help</a></li>
|
||||||
@ -108,7 +108,7 @@
|
|||||||
<script src="/static/js/config.js"></script>
|
<script src="/static/js/config.js"></script>
|
||||||
<script src="/static/js/require.js"></script>
|
<script src="/static/js/require.js"></script>
|
||||||
<script>
|
<script>
|
||||||
require(["bootstrap"]);
|
require(["bootstrap", "admin"]);
|
||||||
</script>
|
</script>
|
||||||
{% block js_block %}{% endblock %}
|
{% block js_block %}{% endblock %}
|
||||||
<!-- footer begin -->
|
<!-- footer begin -->
|
||||||
|
@ -1,10 +1,34 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "oj_base.html" %}
|
||||||
<html>
|
{% block body %}
|
||||||
<head lang="en">
|
<div class="container main">
|
||||||
<meta charset="UTF-8">
|
<div class="col-md-6 col-md-offset-3">
|
||||||
<title></title>
|
<h2 class="text-center">修改密码</h2>
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
</body>
|
<form id="change_password-form">
|
||||||
</html>
|
<div class="form-group">
|
||||||
|
<label for="username">用户名</label>
|
||||||
|
<input type="text" class="form-control input-lg" id="username" name="username" placeholder="用户名"
|
||||||
|
autofocus>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">旧密码</label>
|
||||||
|
<input type="password" class="form-control input-lg" id="password" name="password" placeholder="密码">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new_password">新密码</label>
|
||||||
|
<input type="password" class="form-control input-lg" id="new_password" name="new_password" placeholder="新密码">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="confirm_password">确认密码</label>
|
||||||
|
<input type="password" class="form-control input-lg" id="confirm_password" name="confirm_password" placeholder="确认密码">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn btn-primary">提交</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block js_block %}
|
||||||
|
<script src="/static/js/app/oj/account/change_password.js"></script>
|
||||||
|
{% endblock %}
|
@ -1,6 +1,6 @@
|
|||||||
{% extends "oj_base.html" %}
|
{% extends "oj_base.html" %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container">
|
<div class="container main">
|
||||||
<div class="col-md-6 col-md-offset-3">
|
<div class="col-md-6 col-md-offset-3">
|
||||||
<h2 class="text-center">用户登录</h2>
|
<h2 class="text-center">用户登录</h2>
|
||||||
|
|
||||||
|
@ -1,10 +1,38 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "oj_base.html" %}
|
||||||
<html>
|
{% block body %}
|
||||||
<head lang="en">
|
<div class="container main">
|
||||||
<meta charset="UTF-8">
|
<div class="col-md-6 col-md-offset-3">
|
||||||
<title></title>
|
<h2 class="text-center">用户注册</h2>
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
</body>
|
<form id="register-form">
|
||||||
</html>
|
<div class="form-group">
|
||||||
|
<label for="username">用户名</label>
|
||||||
|
<input type="text" class="form-control input-lg" id="username" name="username" placeholder="用户名"
|
||||||
|
autofocus>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="real_name">真实姓名</label>
|
||||||
|
<input type="text" class="form-control input-lg" id="real_name" name="real_name" placeholder="真实姓名">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">邮箱地址</label>
|
||||||
|
<input type="email" class="form-control input-lg" id="email" name="email" placeholder="邮箱地址">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">密码</label>
|
||||||
|
<input type="password" class="form-control input-lg" id="password" name="password" placeholder="密码">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="confirm_password">确认密码</label>
|
||||||
|
<input type="password" class="form-control input-lg" id="confirm_password" name="confirm_password" placeholder="确认密码">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn btn-primary">提交</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block js_block %}
|
||||||
|
<script src="/static/js/app/oj/account/register.js"></script>
|
||||||
|
{% endblock %}
|
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