mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2024-09-21 08:23:20 +00:00
Merge branch 'dev' into hohoTT-dev
Conflicts: oj/settings.py oj/urls.py problem/views.py
This commit is contained in:
commit
b37b0d34c6
2
.gitignore
vendored
2
.gitignore
vendored
@ -56,3 +56,5 @@ db.sqlite3
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
log/
|
log/
|
||||||
release/
|
release/
|
||||||
|
tmp/
|
||||||
|
test_case/
|
@ -149,15 +149,21 @@ class UserAPITest(APITestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
self.url = reverse("user_list_api")
|
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):
|
def test_success_get_data(self):
|
||||||
|
self.client.login(username="testx", password="testxx")
|
||||||
self.assertEqual(self.client.get(self.url).data["code"], 0)
|
self.assertEqual(self.client.get(self.url).data["code"], 0)
|
||||||
|
|
||||||
def test_error_admin_type(self):
|
def test_error_admin_type(self):
|
||||||
|
self.client.login(username="testx", password="testxx")
|
||||||
response = self.client.get(self.url + "?admin_type=error")
|
response = self.client.get(self.url + "?admin_type=error")
|
||||||
self.assertEqual(response.data, {"code": 1, "data": u"参数错误"})
|
self.assertEqual(response.data, {"code": 1, "data": u"参数错误"})
|
||||||
|
|
||||||
def test_query_by_keyword(self):
|
def test_query_by_keyword(self):
|
||||||
|
self.client.login(username="testx", password="testxx")
|
||||||
user1 = User.objects.create(username="test1", real_name="aa")
|
user1 = User.objects.create(username="test1", real_name="aa")
|
||||||
user1.set_password("testaa")
|
user1.set_password("testaa")
|
||||||
user1.save()
|
user1.save()
|
||||||
|
@ -8,7 +8,7 @@ class AdminRequiredMiddleware(object):
|
|||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
path = request.path_info
|
path = request.path_info
|
||||||
if path.startswith("/admin/") or path.startswith("/api/admin/"):
|
if path.startswith("/admin/") or path.startswith("/api/admin/"):
|
||||||
if not request.user.is_authenticated():
|
if not(request.user.is_authenticated() and request.user.admin_type):
|
||||||
if request.is_ajax():
|
if request.is_ajax():
|
||||||
return HttpResponse(json.dumps({"code": 1, "data": u"请先登录"}),
|
return HttpResponse(json.dumps({"code": 1, "data": u"请先登录"}),
|
||||||
content_type="application/json")
|
content_type="application/json")
|
||||||
|
@ -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)
|
||||||
|
|
@ -22,7 +22,6 @@ class Migration(migrations.Migration):
|
|||||||
('join_group_setting', models.IntegerField()),
|
('join_group_setting', models.IntegerField()),
|
||||||
('visible', models.BooleanField(default=True)),
|
('visible', models.BooleanField(default=True)),
|
||||||
('admin', models.ForeignKey(related_name='my_groups', to=settings.AUTH_USER_MODEL)),
|
('admin', models.ForeignKey(related_name='my_groups', to=settings.AUTH_USER_MODEL)),
|
||||||
('members', models.ManyToManyField(to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'db_table': 'group',
|
'db_table': 'group',
|
||||||
@ -42,4 +41,21 @@ class Migration(migrations.Migration):
|
|||||||
'db_table': 'join_group_request',
|
'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'),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -11,7 +11,7 @@ class Group(models.Model):
|
|||||||
admin = models.ForeignKey(User, related_name="my_groups")
|
admin = models.ForeignKey(User, related_name="my_groups")
|
||||||
# 0是公开 1是需要申请后加入 2是不允许任何人加入
|
# 0是公开 1是需要申请后加入 2是不允许任何人加入
|
||||||
join_group_setting = models.IntegerField()
|
join_group_setting = models.IntegerField()
|
||||||
members = models.ManyToManyField(User)
|
members = models.ManyToManyField(User, through="UserGroupRelation")
|
||||||
# 解散小组后,这一项改为False
|
# 解散小组后,这一项改为False
|
||||||
visible = models.BooleanField(default=True)
|
visible = models.BooleanField(default=True)
|
||||||
|
|
||||||
@ -19,6 +19,15 @@ class Group(models.Model):
|
|||||||
db_table = "group"
|
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):
|
class JoinGroupRequest(models.Model):
|
||||||
group = models.ForeignKey(User)
|
group = models.ForeignKey(User)
|
||||||
user = models.ForeignKey(User, related_name="my_join_group_requests")
|
user = models.ForeignKey(User, related_name="my_join_group_requests")
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from .models import Group
|
from account.serializers import UserSerializer
|
||||||
|
from .models import Group, UserGroupRelation
|
||||||
|
|
||||||
|
|
||||||
class CreateGroupSerializer(serializers.Serializer):
|
class CreateGroupSerializer(serializers.Serializer):
|
||||||
@ -11,13 +12,14 @@ class CreateGroupSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
|
|
||||||
class EditGroupSerializer(serializers.Serializer):
|
class EditGroupSerializer(serializers.Serializer):
|
||||||
|
group_id = serializers.IntegerField()
|
||||||
name = serializers.CharField(max_length=20)
|
name = serializers.CharField(max_length=20)
|
||||||
description = serializers.CharField(max_length=300)
|
description = serializers.CharField(max_length=300)
|
||||||
join_group_setting = serializers.IntegerField()
|
join_group_setting = serializers.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
class JoinGroupRequestSerializer(serializers.Serializer):
|
class JoinGroupRequestSerializer(serializers.Serializer):
|
||||||
group = serializers.IntegerField()
|
group_id = serializers.IntegerField()
|
||||||
message = serializers.CharField(max_length=30)
|
message = serializers.CharField(max_length=30)
|
||||||
|
|
||||||
|
|
||||||
@ -25,3 +27,16 @@ class GroupSerializer(serializers.ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Group
|
model = Group
|
||||||
exclude = ["members"]
|
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())
|
129
group/views.py
129
group/views.py
@ -5,18 +5,45 @@ from rest_framework.views import APIView
|
|||||||
|
|
||||||
from utils.shortcuts import error_response, serializer_invalid_response, success_response, paginate
|
from utils.shortcuts import error_response, serializer_invalid_response, success_response, paginate
|
||||||
from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN
|
from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN
|
||||||
|
from account.decorators import login_required
|
||||||
|
|
||||||
from .models import Group, JoinGroupRequest
|
from .models import Group, JoinGroupRequest, UserGroupRelation
|
||||||
from .serializers import (CreateGroupSerializer, EditGroupSerializer,
|
from .serializers import (CreateGroupSerializer, EditGroupSerializer,
|
||||||
JoinGroupRequestSerializer, GroupSerializer)
|
JoinGroupRequestSerializer, GroupSerializer,
|
||||||
|
GroupMemberSerializer, EditGroupMemberSerializer)
|
||||||
|
|
||||||
|
|
||||||
class GroupAdminAPIView(APIView):
|
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):
|
def post(self, request):
|
||||||
"""
|
"""
|
||||||
创建小组的api
|
创建小组的api
|
||||||
---
|
---
|
||||||
request_serializer: CreateGroupSerializer
|
request_serializer: CreateGroupSerializer
|
||||||
|
response_serializer: GroupSerializer
|
||||||
"""
|
"""
|
||||||
serializer = CreateGroupSerializer(data=request.data)
|
serializer = CreateGroupSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
@ -34,12 +61,13 @@ class GroupAdminAPIView(APIView):
|
|||||||
修改小组信息的api
|
修改小组信息的api
|
||||||
---
|
---
|
||||||
request_serializer: EditGroupSerializer
|
request_serializer: EditGroupSerializer
|
||||||
|
response_serializer: GroupSerializer
|
||||||
"""
|
"""
|
||||||
serializer = EditGroupSerializer(data=request.data)
|
serializer = EditGroupSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
try:
|
try:
|
||||||
group = Group.objects.get(id=data["id"], admin=request.user)
|
group = self.get_group(request, data["group_id"])
|
||||||
except Group.DoesNotExist:
|
except Group.DoesNotExist:
|
||||||
return error_response(u"小组不存在")
|
return error_response(u"小组不存在")
|
||||||
group.name = data["name"]
|
group.name = data["name"]
|
||||||
@ -52,21 +80,96 @@ class GroupAdminAPIView(APIView):
|
|||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""
|
"""
|
||||||
查询小组列表或者单个小组的信息
|
查询小组列表或者单个小组的信息,查询单个小组需要传递group_id参数,否则返回全部
|
||||||
|
---
|
||||||
|
response_serializer: GroupSerializer
|
||||||
"""
|
"""
|
||||||
group_id = request.GET.get("group_id", None)
|
group_id = request.GET.get("group_id", None)
|
||||||
if group_id:
|
if group_id:
|
||||||
try:
|
try:
|
||||||
if request.user.admin_type == SUPER_ADMIN:
|
group = self.get_group(request, group_id)
|
||||||
group = Group.object.get(id=group_id)
|
|
||||||
else:
|
|
||||||
group = Group.object.get(id=group_id, admin=request.user)
|
|
||||||
return success_response(GroupSerializer(group).data)
|
return success_response(GroupSerializer(group).data)
|
||||||
except Group.DoesNotExist:
|
except Group.DoesNotExist:
|
||||||
return error_response(u"小组不存在")
|
return error_response(u"小组不存在")
|
||||||
else:
|
else:
|
||||||
if request.user.admin_type == SUPER_ADMIN:
|
groups = self.get_groups(request)
|
||||||
groups = Group.objects.filter(visible=True)
|
return paginate(request, groups, GroupSerializer)
|
||||||
else:
|
|
||||||
groups = Group.objects.filter(admin=request.user, visible=True)
|
|
||||||
|
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)
|
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")
|
@ -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',
|
||||||
@ -51,6 +50,7 @@ INSTALLED_APPS = (
|
|||||||
'utils',
|
'utils',
|
||||||
'group',
|
'group',
|
||||||
'problem',
|
'problem',
|
||||||
|
'admin',
|
||||||
|
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'rest_framework_swagger',
|
'rest_framework_swagger',
|
||||||
|
10
oj/urls.py
10
oj/urls.py
@ -9,9 +9,13 @@ from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterA
|
|||||||
from announcement.views import AnnouncementAPIView, AnnouncementAdminAPIView
|
from announcement.views import AnnouncementAPIView, AnnouncementAdminAPIView
|
||||||
from group.views import GroupAdminAPIView
|
from group.views import GroupAdminAPIView
|
||||||
from admin.views import AdminTemplateView
|
from admin.views import AdminTemplateView
|
||||||
|
|
||||||
from problem.views import ProblemAdminAPIView
|
from problem.views import ProblemAdminAPIView
|
||||||
|
from problem.views import TestCaseUploadAPIView
|
||||||
|
|
||||||
|
|
||||||
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/admin.html"), name="admin_spa_page"),
|
url(r'^admin/$', TemplateView.as_view(template_name="admin/admin.html"), name="admin_spa_page"),
|
||||||
@ -29,12 +33,12 @@ urlpatterns = [
|
|||||||
url(r'^announcement/(?P<announcement_id>\d+)/$', "announcement.views.announcement_page", name="announcement_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/announcements/$', AnnouncementAPIView.as_view(), name="announcement_list_api"),
|
||||||
url(r'^api/users/$', UserAPIView.as_view(), name="user_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'^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/group/$', GroupAdminAPIView.as_view(), name="group_admin_api"),
|
||||||
|
url(r'^api/admin/problem/$', ProblemAdminAPIView.as_view(), name="problem_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"),
|
||||||
]
|
]
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
]
|
@ -1,14 +1,20 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
import zipfile
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import hashlib
|
||||||
import json
|
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 rest_framework.views import APIView
|
||||||
|
|
||||||
from django.db.models import Q
|
from utils.shortcuts import serializer_invalid_response, error_response, success_response, paginate, rand_str
|
||||||
|
from .serizalizers import CreateProblemSerializer, EditProblemSerializer, ProblemSerializer
|
||||||
from serizalizers import CreateProblemSerializer, EditProblemSerializer, ProblemSerializer
|
|
||||||
from .models import Problem, ProblemTag
|
from .models import Problem, ProblemTag
|
||||||
from utils.shortcuts import serializer_invalid_response, error_response, success_response, paginate
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def problem_page(request, problem_id):
|
def problem_page(request, problem_id):
|
||||||
@ -97,3 +103,84 @@ class ProblemAPIView(APIView):
|
|||||||
problem = problem.filter(Q(difficulty__contains=keyword))
|
problem = problem.filter(Q(difficulty__contains=keyword))
|
||||||
|
|
||||||
return paginate(request, problem, ProblemSerializer)
|
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"测试用例压缩文件格式错误,请保证测试用例文件在根目录下直接压缩")
|
@ -19,6 +19,13 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
description:{
|
||||||
|
validators: {
|
||||||
|
notEmpty: {
|
||||||
|
message: "请输入描述"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
start_time: {
|
start_time: {
|
||||||
validators: {
|
validators: {
|
||||||
notEmpty: {
|
notEmpty: {
|
||||||
@ -93,9 +100,18 @@ 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";
|
||||||
@ -107,17 +123,20 @@ require(["jquery", "avalon", "editor", "uploader", "datetimepicker",
|
|||||||
|
|
||||||
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={id: problem_id, title: "", cpu: "", memory: "", description: "",samples: [], webuploader: {}, visible: true};
|
||||||
problem["samples"] = [];
|
|
||||||
problem["webuploader"] = {};
|
|
||||||
problem["toggle_string"] = "折叠";
|
|
||||||
vm.problems.push(problem);
|
vm.problems.push(problem);
|
||||||
uploader("#problem-" + problem_id + "-uploader");
|
uploader("#problem-" + problem_id + "-uploader","");
|
||||||
console.log(vm.problems);
|
editor("#problem-" + problem_id + "-description")
|
||||||
$("#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[]"]'));
|
||||||
@ -127,31 +146,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();
|
||||||
@ -168,7 +177,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")
|
||||||
|
108
static/src/js/app/admin/problem/add_problem.js
Normal file
108
static/src/js/app/admin/problem/add_problem.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
require(["jquery", "avalon", "editor", "uploader", "validation"],
|
||||||
|
function ($, avalon, editor, uploader) {
|
||||||
|
avalon.vmodels.add_problem = null;
|
||||||
|
$("#add-problem-form")
|
||||||
|
.formValidation({
|
||||||
|
framework: "bootstrap",
|
||||||
|
fields: {
|
||||||
|
title: {
|
||||||
|
validators: {
|
||||||
|
notEmpty: {
|
||||||
|
message: "请填写题目名称"
|
||||||
|
},
|
||||||
|
stringLength: {
|
||||||
|
min: 1,
|
||||||
|
max: 30,
|
||||||
|
message: "名称不能超过30个字"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
description:{
|
||||||
|
validators: {
|
||||||
|
notEmpty: {
|
||||||
|
message: "请输入描述"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cpu: {
|
||||||
|
validators: {
|
||||||
|
notEmpty: {
|
||||||
|
message: "请输入cpu时间"
|
||||||
|
},
|
||||||
|
integer: {
|
||||||
|
message: "请输入一个合法的数字"
|
||||||
|
},
|
||||||
|
between: {
|
||||||
|
inclusive: true,
|
||||||
|
min: 1,
|
||||||
|
max: 5000,
|
||||||
|
message: "只能在1-5000之间"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
memory: {
|
||||||
|
validators: {
|
||||||
|
notEmpty: {
|
||||||
|
message: "请输入内存"
|
||||||
|
},
|
||||||
|
integer: {
|
||||||
|
message: "请输入一个合法的数字"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("success.form.fv", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var ajaxData = {
|
||||||
|
title: vm.title,
|
||||||
|
description: vm.description,
|
||||||
|
cpu: vm.cpu,
|
||||||
|
memory: vm.memory,
|
||||||
|
samples: []
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0; i < vm.samples.length; i++) {
|
||||||
|
ajaxData.samples.push({input: vm.samples[i].input, output: vm.samples[i].output});
|
||||||
|
}
|
||||||
|
console.log(ajaxData);
|
||||||
|
});
|
||||||
|
var problemDiscription = editor("#problemDescription");
|
||||||
|
var testCaseUploader = uploader("#testCaseFile", "/admin/api/testCase");//{
|
||||||
|
|
||||||
|
/*auto: true,
|
||||||
|
swf: '/static/js/lib/webuploader/Uploader.swf',
|
||||||
|
server: 'http://webuploader.duapp.com/server/fileupload.php',
|
||||||
|
multiple:false,
|
||||||
|
accept: {
|
||||||
|
title: 'Zip',
|
||||||
|
extensions: 'zip',
|
||||||
|
mimeTypes: 'zip/*'
|
||||||
|
}*/
|
||||||
|
// });
|
||||||
|
var vm = avalon.define({
|
||||||
|
$id: "add_problem",
|
||||||
|
title: "",
|
||||||
|
description: "",
|
||||||
|
cpu: 0,
|
||||||
|
memory: 0,
|
||||||
|
samples: [],
|
||||||
|
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 "展开";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
avalon.scan();
|
||||||
|
});
|
@ -9,8 +9,15 @@ define("csrf",function(){
|
|||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
function csrfHeader(xhr){
|
function csrfHeader(){
|
||||||
xhr.setRequestHeader("X-CSRFToken", get_cookie("csrftoken"));
|
// jquery的请求
|
||||||
|
if(arguments.length == 1) {
|
||||||
|
arguments[0].setRequestHeader("X-CSRFToken", get_cookie("csrftoken"));
|
||||||
|
}
|
||||||
|
// 百度webuploader 的请求
|
||||||
|
else if(arguments.length == 3){
|
||||||
|
arguments[2]["X-CSRFToken"] = get_cookie("csrftoken");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return csrfHeader;
|
return csrfHeader;
|
||||||
});
|
});
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
define("uploader", ["webuploader"], function(webuploader){
|
define("uploader", ["webuploader"], function(webuploader){
|
||||||
function uploader(selector) {
|
function uploader(selector, server) {
|
||||||
return webuploader.create({
|
return webuploader.create({
|
||||||
|
|
||||||
// swf文件路径
|
// swf文件路径
|
||||||
swf: "/js/Uploader.swf",
|
swf: "/js/Uploader.swf",
|
||||||
|
|
||||||
// 文件接收服务端。
|
// 文件接收服务端。
|
||||||
server: "http://webuploader.duapp.com/server/fileupload.php",
|
server: server,
|
||||||
|
|
||||||
// 选择文件的按钮。可选。
|
// 选择文件的按钮。可选。
|
||||||
// 内部根据当前运行是创建,可能是input元素,也可能是flash.
|
// 内部根据当前运行是创建,可能是input元素,也可能是flash.
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
<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">
|
||||||
@ -14,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">
|
||||||
@ -25,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>
|
||||||
|
|
||||||
@ -51,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>
|
||||||
|
|
||||||
@ -75,23 +83,22 @@
|
|||||||
<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">
|
<div class="col-md-6">
|
||||||
@ -102,14 +109,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" name="cpu[]" class="form-control">
|
<input type="text" name="cpu[]" class="form-control" ms-duplex="problem.cpu">
|
||||||
</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" name="memory[]" class="form-control">
|
<input type="text" name="memory[]" class="form-control" ms-duplex="problem.memory">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<label>题目描述</label>
|
||||||
|
<textarea ms-attr-id="problem-{{ problem.id }}-description" placeholder="这里输入内容"
|
||||||
|
ms-duplex="problem.description"></textarea>
|
||||||
|
<small ms-visible="problem.description==''" style="color:red">请填写题目描述</small>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
|
|
||||||
<label>样例</label>
|
<label>样例</label>
|
||||||
@ -125,8 +136,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)">
|
||||||
@ -134,23 +145,18 @@
|
|||||||
</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" ms-duplex="sample.output"></textarea>
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="form-group">
|
|
||||||
<textarea class="form-control" rows="5"></textarea>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -163,14 +169,18 @@
|
|||||||
<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>
|
||||||
|
|
||||||
|
78
template/admin/problem/add_problem.html
Normal file
78
template/admin/problem/add_problem.html
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<div ms-controller="add_problem" class="col-md-9">
|
||||||
|
<form id="add-problem-form">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>题目标题</label>
|
||||||
|
<input type="text" name="title" class="form-control" ms-duplex="title">
|
||||||
|
</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="col-md-6">
|
||||||
|
<label>cpu</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label>内存</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" name="cpu" class="form-control" ms-duplex="cpu">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" name="memory" class="form-control" ms-duplex="memory">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<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">
|
||||||
|
<label>测试数据</label>
|
||||||
|
</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>
|
@ -1,4 +1,8 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
import hashlib
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
@ -87,3 +91,8 @@ def paginate(request, query_set, object_serializer):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
return success_response(data)
|
return success_response(data)
|
||||||
|
|
||||||
|
|
||||||
|
def rand_str(length=32):
|
||||||
|
string = hashlib.md5(str(time.time()) + str(random.randrange(1, 9999999900))).hexdigest()
|
||||||
|
return string[0:length]
|
Loading…
Reference in New Issue
Block a user