Merge branch 'dev' into hohoTT-dev

Conflicts:
	oj/settings.py
	oj/urls.py
	problem/views.py
This commit is contained in:
hohoTT 2015-08-10 15:20:17 +08:00
commit b37b0d34c6
26 changed files with 701 additions and 95 deletions

2
.gitignore vendored
View File

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

View File

@ -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()

View File

@ -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")

View File

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

View File

@ -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'),
),
] ]

View File

@ -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")

View File

@ -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())

View File

@ -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
View File

3
install/admin.py Normal file
View File

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

View File

3
install/models.py Normal file
View File

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

3
install/tests.py Normal file
View File

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

12
install/views.py Normal file
View File

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

View File

@ -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',

View File

@ -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"),
] ]

View File

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

View File

@ -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"测试用例压缩文件格式错误,请保证测试用例文件在根目录下直接压缩")

View File

@ -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")

View 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();
});

View File

@ -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;
}); });

View File

@ -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.

View File

@ -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>

View 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>

View File

@ -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]