Accept Merge Request #280 dev -> master : (dev -> master)
Merge Request: dev -> master Created By: @virusdefender Accepted By: @virusdefender URL: https://coding.net/u/virusdefender/p/qduoj/git/merge/280
This commit is contained in:
+35
-45
@@ -1,58 +1,48 @@
|
||||
# coding=utf-8
|
||||
import functools
|
||||
from functools import wraps
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
|
||||
from utils.shortcuts import error_response, error_page
|
||||
from .models import User, SUPER_ADMIN
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
from utils.shortcuts import error_response
|
||||
from .models import SUPER_ADMIN, ADMIN
|
||||
|
||||
|
||||
def login_required(func):
|
||||
@wraps(func)
|
||||
def check(*args, **kwargs):
|
||||
# 在class based views 里面,args 有两个元素,一个是self, 第二个才是request,
|
||||
# 在function based views 里面,args 只有request 一个参数
|
||||
class BasePermissionDecorator(object):
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
|
||||
def __get__(self, obj, obj_type):
|
||||
return functools.partial(self.__call__, obj)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if len(args) == 2:
|
||||
request = args[-1]
|
||||
self.request = args[1]
|
||||
else:
|
||||
request = args[0]
|
||||
if request.user.is_authenticated():
|
||||
return func(*args, **kwargs)
|
||||
if request.is_ajax():
|
||||
return error_response(u"请先登录")
|
||||
self.request = args[0]
|
||||
|
||||
if self.check_permission():
|
||||
return self.func(*args, **kwargs)
|
||||
else:
|
||||
return HttpResponseRedirect("/login/")
|
||||
return check
|
||||
if self.request.is_ajax():
|
||||
return error_response(u"请先登录")
|
||||
else:
|
||||
return HttpResponseRedirect("/login/")
|
||||
|
||||
def check_permission(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def admin_required(func):
|
||||
@wraps(func)
|
||||
def check(*args, **kwargs):
|
||||
if len(args) == 2:
|
||||
request = args[-1]
|
||||
else:
|
||||
request = args[0]
|
||||
if request.user.is_authenticated() and request.user.admin_type:
|
||||
return func(*args, **kwargs)
|
||||
if request.is_ajax():
|
||||
return error_response(u"需要管理员权限")
|
||||
else:
|
||||
return error_page(request, u"需要管理员权限,如果没有登录,请先登录")
|
||||
return check
|
||||
class login_required(BasePermissionDecorator):
|
||||
def check_permission(self):
|
||||
return self.request.user.is_authenticated()
|
||||
|
||||
|
||||
def super_admin_required(func):
|
||||
@wraps(func)
|
||||
def check(*args, **kwargs):
|
||||
if len(args) == 2:
|
||||
request = args[-1]
|
||||
else:
|
||||
request = args[0]
|
||||
if request.user.is_authenticated() and request.user.admin_type == SUPER_ADMIN:
|
||||
return func(*args, **kwargs)
|
||||
if request.is_ajax():
|
||||
return error_response(u"需要超级管理员权限")
|
||||
else:
|
||||
return error_page(request, u"需要超级管理员权限")
|
||||
class super_admin_required(BasePermissionDecorator):
|
||||
def check_permission(self):
|
||||
return self.request.user.is_authenticated() and self.request.user.admin_type == SUPER_ADMIN
|
||||
|
||||
return check
|
||||
|
||||
class admin_required(BasePermissionDecorator):
|
||||
def check_permission(self):
|
||||
return self.request.user.is_authenticated() and self.request.user.admin_type in [SUPER_ADMIN, ADMIN]
|
||||
@@ -7,7 +7,7 @@ from .models import User
|
||||
class UserLoginSerializer(serializers.Serializer):
|
||||
username = serializers.CharField(max_length=30)
|
||||
password = serializers.CharField(max_length=30)
|
||||
captcha = serializers.CharField(required=False, min_length=4, max_length=4)
|
||||
captcha = serializers.CharField(min_length=4, max_length=4)
|
||||
|
||||
|
||||
class UsernameCheckSerializer(serializers.Serializer):
|
||||
@@ -52,3 +52,9 @@ class ApplyResetPasswordSerializer(serializers.Serializer):
|
||||
username = serializers.CharField(max_length=30)
|
||||
email = serializers.EmailField()
|
||||
captcha = serializers.CharField(max_length=4, min_length=4)
|
||||
|
||||
|
||||
class ResetPasswordSerializer(serializers.Serializer):
|
||||
token = serializers.CharField(min_length=1, max_length=40)
|
||||
password = serializers.CharField(min_length=6, max_length=30)
|
||||
captcha = serializers.CharField(max_length=4, min_length=4)
|
||||
@@ -3,7 +3,8 @@ from django.conf.urls import include, url
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from .tests import (LoginRequiredCBVTestWithArgs, LoginRequiredCBVTestWithoutArgs,
|
||||
AdminRequiredCBVTestWithArgs, AdminRequiredCBVTestWithoutArgs)
|
||||
AdminRequiredCBVTestWithArgs, AdminRequiredCBVTestWithoutArgs,
|
||||
SuperAdminRequiredCBVTestWithArgs, SuperAdminRequiredCBVTestWithoutArgs)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
@@ -16,5 +17,11 @@ urlpatterns = [
|
||||
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()),
|
||||
|
||||
url(r'^super_admin_required_test/fbv/1/$', "account.tests.super_admin_required_FBV_test_without_args"),
|
||||
url(r'^super_admin_required_test/fbv/(?P<problem_id>\d+)/$', "account.tests.super_admin_required_FBC_test_with_args"),
|
||||
url(r'^super_admin_required_test/cbv/1/$', SuperAdminRequiredCBVTestWithoutArgs.as_view()),
|
||||
url(r'^super_admin_required_test/cbv/(?P<problem_id>\d+)/$', SuperAdminRequiredCBVTestWithArgs.as_view()),
|
||||
|
||||
url(r'^login/$', TemplateView.as_view(template_name="oj/account/login.html"), name="user_login_page"),
|
||||
]
|
||||
|
||||
+203
-86
@@ -1,17 +1,16 @@
|
||||
# coding=utf-8
|
||||
import json
|
||||
import time
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase, Client
|
||||
from django.http import HttpResponse
|
||||
from django.contrib import auth
|
||||
|
||||
from rest_framework.test import APITestCase, APIClient
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
|
||||
from .models import User, SUPER_ADMIN
|
||||
from .decorators import login_required, admin_required
|
||||
from .models import User, SUPER_ADMIN, REGULAR_USER, ADMIN
|
||||
from .decorators import login_required, admin_required, super_admin_required
|
||||
|
||||
|
||||
class UserLoginTest(TestCase):
|
||||
@@ -21,26 +20,44 @@ class UserLoginTest(TestCase):
|
||||
self.assertTemplateUsed(response, "oj/account/login.html")
|
||||
|
||||
|
||||
def create_user(username="test", real_name="test_real_name", email="test@qq.com",
|
||||
password="111111", admin_type=REGULAR_USER):
|
||||
user = User.objects.create(username=username, real_name=real_name, email=email, admin_type=admin_type)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
def set_captcha(session):
|
||||
session["_django_captcha_key"] = "aaaa"
|
||||
session["_django_captcha_expires_time"] = time.time() + 10000
|
||||
session.save()
|
||||
|
||||
|
||||
class UserLoginAPITest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.url = reverse("user_login_api")
|
||||
user = User.objects.create(username="test")
|
||||
user.set_password("test")
|
||||
user.save()
|
||||
self.user = create_user()
|
||||
set_captcha(self.client.session)
|
||||
|
||||
def test_invalid_data(self):
|
||||
data = {"username": "test"}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_captcha(self):
|
||||
error_data = {"username": "test", "password": "test11", "captcha": "1111"}
|
||||
response = self.client.post(self.url, data=error_data)
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"验证码错误"})
|
||||
|
||||
def test_error_username_or_password(self):
|
||||
error_data = {"username": "test", "password": "test11"}
|
||||
error_data = {"username": "test", "password": "test11", "captcha": "aaaa"}
|
||||
response = self.client.post(self.url, data=error_data)
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"用户名或密码错误"})
|
||||
|
||||
def test_success_login(self):
|
||||
data = {"username": "test", "password": "test"}
|
||||
def test_login_successfully(self):
|
||||
data = {"username": "test", "password": "111111", "captcha": "aaaa"}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 0, "data": u"登录成功"})
|
||||
|
||||
@@ -49,69 +66,75 @@ class UsernameCheckTest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.url = reverse("username_check_api")
|
||||
User.objects.create(username="testtest")
|
||||
create_user()
|
||||
|
||||
def test_invalid_data(self):
|
||||
response = self.client.post(self.url, data={"username111": "testtest"})
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
response = self.client.get(self.url, data={"username111": "testtest"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_username_exists(self):
|
||||
response = self.client.post(self.url, data={"username": "testtest"})
|
||||
self.assertEqual(response.data, {"code": 0, "data": True})
|
||||
response = self.client.get(self.url, data={"username": "test"})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_username_does_not_exist(self):
|
||||
response = self.client.post(self.url, data={"username": "testtest123"})
|
||||
self.assertEqual(response.data, {"code": 0, "data": False})
|
||||
response = self.client.get(self.url, data={"username": "testtest123"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class EmailCheckTest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.url = reverse("email_check_api")
|
||||
User.objects.create(email="11@qq.com")
|
||||
create_user()
|
||||
|
||||
def test_invalid_data(self):
|
||||
response = self.client.post(self.url, data={"email000": "11@qq.com"})
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
response = self.client.get(self.url, data={"email000": "11@qq.com"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_email_exists(self):
|
||||
response = self.client.post(self.url, data={"email": "11@qq.com"})
|
||||
self.assertEqual(response.data, {"code": 0, "data": True})
|
||||
response = self.client.get(self.url, data={"email": "test@qq.com"})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
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})
|
||||
response = self.client.get(self.url, data={"email": "33testtest@qq.com"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class UserRegisterAPITest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.url = reverse("user_register_api")
|
||||
set_captcha(self.client.session)
|
||||
|
||||
def test_invalid_data(self):
|
||||
data = {"username": "test", "real_name": "TT"}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_captcha(self):
|
||||
data = {"username": "test", "real_name": "TT", "password": "qqqqqq", "email": "6060@qq.com", "captcha": "bbaa"}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"验证码错误"})
|
||||
|
||||
def test_short_password(self):
|
||||
data = {"username": "test", "real_name": "TT", "password": "qq", "email": "6060@qq.com"}
|
||||
data = {"username": "test", "real_name": "TT", "password": "qq", "email": "6060@qq.com", "captcha": "aaaa"}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_same_username(self):
|
||||
User.objects.create(username="aa")
|
||||
data = {"username": "aa", "real_name": "ww", "password": "zzzzzzz", "email": "6060@qq.com"}
|
||||
create_user()
|
||||
data = {"username": "test", "real_name": "ww", "password": "zzzzzzz", "email": "606fds0@qq.com", "captcha": "aaaa"}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"用户名已存在"})
|
||||
|
||||
def test_same_email(self):
|
||||
User.objects.create(username="bb", email="8080@qq.com")
|
||||
data = {"username": "aa", "real_name": "ww", "password": "zzzzzzz", "email": "8080@qq.com"}
|
||||
create_user(username="test1", email="test1@qq.com")
|
||||
data = {"username": "aa", "real_name": "ww", "password": "zzzzzzz", "email": "test1@qq.com", "captcha": "aaaa"}
|
||||
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"}
|
||||
def test_register_successfully(self):
|
||||
data = {"username": "cc", "real_name": "dd", "password": "xxxxxx", "email": "9090@qq.com", "captcha": "aaaa"}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 0, "data": u"注册成功!"})
|
||||
|
||||
@@ -120,18 +143,27 @@ class UserChangePasswordAPITest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.url = reverse("user_change_password_api")
|
||||
user = User.objects.create(username="test")
|
||||
user.set_password("aaabbb")
|
||||
user.save()
|
||||
self.client.login(username="test",password="aaabbb")
|
||||
create_user()
|
||||
self.client.login(username="test",password="111111")
|
||||
set_captcha(self.client.session)
|
||||
|
||||
def test_captcha(self):
|
||||
data = {"old_password": "aaaccc", "new_password": "aaaddd", "captcha": "abba"}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"验证码错误"})
|
||||
|
||||
def test_invalid_data(self):
|
||||
data = {"new_password": "aaaddd", "captcha": "aaaa"}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_error_old_password(self):
|
||||
data = {"old_password": "aaaccc", "new_password": "aaaddd"}
|
||||
data = {"old_password": "aaaccc", "new_password": "aaaddd", "captcha": "aaaa"}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"密码不正确,请重新修改!"})
|
||||
|
||||
def test_success_change_password(self):
|
||||
data = {"username": "test", "old_password": "aaabbb", "new_password": "aaaccc"}
|
||||
def test_change_password_successfully(self):
|
||||
data = {"old_password": "111111", "new_password": "aaaccc", "captcha": "aaaa"}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 0, "data": u"用户密码修改成功!"})
|
||||
|
||||
@@ -140,81 +172,82 @@ class UserAdminAPITest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.url = reverse("user_admin_api")
|
||||
self.user1 = User.objects.create(username="testx", real_name="xx", admin_type=SUPER_ADMIN)
|
||||
self.user1.set_password("testxx")
|
||||
self.user1.save()
|
||||
self.user = User.objects.create(username="testy", real_name="yy", admin_type=SUPER_ADMIN)
|
||||
self.user.set_password("testyy")
|
||||
self.user.save()
|
||||
self.client.login(username="testx", password="testxx")
|
||||
self.user = create_user(admin_type=SUPER_ADMIN)
|
||||
self.client.login(username="test", password="111111")
|
||||
|
||||
# 以下是编辑用户的测试
|
||||
def test_success_get_data(self):
|
||||
self.assertEqual(self.client.get(self.url).data["code"], 0)
|
||||
|
||||
def test_super_admin_required(self):
|
||||
create_user(username="test1", email="test1@qq.com", admin_type=ADMIN)
|
||||
self.client.login(username="test1", password="111111")
|
||||
|
||||
self.assertEqual(json.loads(self.client.get(self.url, HTTP_X_REQUESTED_WITH="XMLHttpRequest").content),
|
||||
{"code": 1, "data": u"请先登录"})
|
||||
self.assertEqual(json.loads(self.client.put(self.url, HTTP_X_REQUESTED_WITH="XMLHttpRequest").content),
|
||||
{"code": 1, "data": u"请先登录"})
|
||||
|
||||
# 这个拦截操作其实是 Middleware 完成的
|
||||
create_user(username="test2", email="test2@qq.com")
|
||||
self.client.login(username="test2", password="111111")
|
||||
self.assertEqual(json.loads(self.client.get(self.url, HTTP_X_REQUESTED_WITH="XMLHttpRequest").content),
|
||||
{"code": 1, "data": u"请先登录"})
|
||||
self.assertEqual(json.loads(self.client.put(self.url, HTTP_X_REQUESTED_WITH="XMLHttpRequest").content),
|
||||
{"code": 1, "data": u"请先登录"})
|
||||
|
||||
def test_put_invalid_data(self):
|
||||
data = {"username": "test", "password": "testaa", "email": "60@qq.com", "admin_type": "2"}
|
||||
data = {"username": "test", "password": "testaa"}
|
||||
response = self.client.put(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_user_does_not_exist(self):
|
||||
data = {"id": 3, "username": "test0", "real_name": "test00",
|
||||
data = {"id": 8888, "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_username_exists(self):
|
||||
data = {"id": self.user.id, "username": "testx", "real_name": "test00",
|
||||
create_user(username="test1", email="test1@qq.com")
|
||||
data = {"id": self.user.id, "username": "test1", "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_user_edit_not_password_successfully(self):
|
||||
data = {"id": self.user.id, "username": "test0", "real_name": "test00",
|
||||
def test_edit_user_without_changing_password(self):
|
||||
data = {"id": self.user.id, "username": "test2", "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_user_edit_change_password_successfully(self):
|
||||
data = {"id": self.user.id, "username": "test0", "real_name": "test00", "password": "111111",
|
||||
def test_user_edit_with_changing_password(self):
|
||||
data = {"id": self.user.id, "username": "test", "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"))
|
||||
self.assertIsNotNone(auth.authenticate(username="test", password="111111"))
|
||||
|
||||
def test_search_user(self):
|
||||
r = self.assertEqual(self.client.get(self.url + "?keyword=11").status_code, 200)
|
||||
|
||||
def test_error_admin_type(self):
|
||||
response = self.client.get(self.url + "?admin_type=error")
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"参数错误"})
|
||||
|
||||
# 以下是用户分页的测试
|
||||
def test_query_by_keyword(self):
|
||||
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 UserInfoAPITest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.url = reverse('user_info_api')
|
||||
user = User.objects.create(username="test1", real_name="aa")
|
||||
user.set_password("testaa")
|
||||
user.save()
|
||||
self.user = create_user()
|
||||
|
||||
def test_get_data_successfully(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
self.assertEqual(self.client.get(self.url).data["code"], 0)
|
||||
self.client.login(username="test", password="111111")
|
||||
data = self.client.get(self.url).data
|
||||
self.assertEqual(data["code"], 0)
|
||||
self.assertEqual(data["data"]["username"], self.user.username)
|
||||
|
||||
def test_get_data_without_logging_in(self):
|
||||
self.assertEqual(self.client.get(self.url, HTTP_X_REQUESTED_WITH="XMLHttpRequest").data["code"], 1)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -324,7 +357,7 @@ class AdminRequiredDecoratorTest(TestCase):
|
||||
def test_fbv_without_args(self):
|
||||
# 没登陆
|
||||
response = self.client.get("/admin_required_test/fbv/1/")
|
||||
self.assertTemplateUsed(response, "utils/error.html")
|
||||
self.assertRedirects(response, "/login/")
|
||||
|
||||
# 登陆后
|
||||
self.client.login(username="test", password="test")
|
||||
@@ -334,7 +367,7 @@ class AdminRequiredDecoratorTest(TestCase):
|
||||
def test_fbv_with_args(self):
|
||||
# 没登陆
|
||||
response = self.client.get("/admin_required_test/fbv/1024/")
|
||||
self.assertTemplateUsed(response, "utils/error.html")
|
||||
self.assertRedirects(response, "/login/")
|
||||
|
||||
# 登陆后
|
||||
self.client.login(username="test", password="test")
|
||||
@@ -343,8 +376,8 @@ class AdminRequiredDecoratorTest(TestCase):
|
||||
|
||||
def test_cbv_without_args(self):
|
||||
# 没登陆
|
||||
response = self.client.get("/admin_required_test/cbv/1/")
|
||||
self.assertTemplateUsed(response, "utils/error.html")
|
||||
response = self.client.get("/admin_required_test/cbv/1/", HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"请先登录"})
|
||||
|
||||
# 登陆后
|
||||
self.client.login(username="test", password="test")
|
||||
@@ -354,7 +387,7 @@ class AdminRequiredDecoratorTest(TestCase):
|
||||
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.assertEqual(response.data, {"code": 1, "data": u"请先登录"})
|
||||
|
||||
# 登陆后
|
||||
self.client.login(username="test", password="test")
|
||||
@@ -362,16 +395,100 @@ class AdminRequiredDecoratorTest(TestCase):
|
||||
self.assertEqual(response.content, "1024")
|
||||
|
||||
|
||||
@super_admin_required
|
||||
def super_admin_required_FBV_test_without_args(request):
|
||||
return HttpResponse("function based view test1")
|
||||
|
||||
|
||||
@super_admin_required
|
||||
def super_admin_required_FBC_test_with_args(request, problem_id):
|
||||
return HttpResponse(problem_id)
|
||||
|
||||
|
||||
class SuperAdminRequiredCBVTestWithoutArgs(APIView):
|
||||
@super_admin_required
|
||||
def get(self, request):
|
||||
return HttpResponse("class based view login required test1")
|
||||
|
||||
|
||||
class SuperAdminRequiredCBVTestWithArgs(APIView):
|
||||
@super_admin_required
|
||||
def get(self, request, problem_id):
|
||||
return HttpResponse(problem_id)
|
||||
|
||||
|
||||
class SuperAdminRequiredDecoratorTest(TestCase):
|
||||
urls = 'account.test_urls'
|
||||
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
create_user(admin_type=SUPER_ADMIN)
|
||||
|
||||
def test_fbv_without_args(self):
|
||||
# 没登陆
|
||||
response = self.client.get("/super_admin_required_test/fbv/1/")
|
||||
self.assertRedirects(response, "/login/")
|
||||
|
||||
# 登陆后
|
||||
self.client.login(username="test", password="111111")
|
||||
response = self.client.get("/super_admin_required_test/fbv/1/")
|
||||
self.assertEqual(response.content, "function based view test1")
|
||||
|
||||
def test_fbv_with_args(self):
|
||||
# 没登陆
|
||||
response = self.client.get("/super_admin_required_test/fbv/1024/")
|
||||
self.assertRedirects(response, "/login/")
|
||||
|
||||
# 登陆后
|
||||
self.client.login(username="test", password="111111")
|
||||
response = self.client.get("/super_admin_required_test/fbv/1024/")
|
||||
self.assertEqual(response.content, "1024")
|
||||
|
||||
def test_cbv_without_args(self):
|
||||
# 没登陆
|
||||
response = self.client.get("/super_admin_required_test/cbv/1/", HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"请先登录"})
|
||||
|
||||
# 登陆后
|
||||
self.client.login(username="test", password="111111")
|
||||
response = self.client.get("/super_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("/super_admin_required_test/cbv/1024/", HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"请先登录"})
|
||||
|
||||
# 登陆后
|
||||
self.client.login(username="test", password="111111")
|
||||
response = self.client.get("/super_admin_required_test/cbv/10086/")
|
||||
self.assertEqual(response.content, "10086")
|
||||
|
||||
|
||||
class UserLogoutTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
user = User.objects.create(username="test")
|
||||
user.admin_type = 1
|
||||
user.set_password("1")
|
||||
user.save()
|
||||
create_user()
|
||||
|
||||
def test_logout_success(self):
|
||||
self.client = Client()
|
||||
self.client.login(username="test", password="1")
|
||||
self.client.login(username="test", password="111111")
|
||||
response = self.client.get("/logout/")
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
|
||||
class IndexPageTest(TestCase):
|
||||
def setUp(self):
|
||||
create_user()
|
||||
self.client = Client()
|
||||
|
||||
def test_not_login_user(self):
|
||||
self.assertTemplateUsed(self.client.get("/"), "oj/index.html")
|
||||
|
||||
def test_no_referer_redirect(self):
|
||||
self.client.login(username="test", password="111111")
|
||||
self.assertRedirects(self.client.get("/"), "/problems/")
|
||||
|
||||
def test_visit_with_referer(self):
|
||||
self.client.login(username="test", password="111111")
|
||||
self.assertTemplateUsed(self.client.get("/", HTTP_REFERER="/about/"), "oj/index.html")
|
||||
|
||||
+46
-12
@@ -6,21 +6,21 @@ from django.shortcuts import render
|
||||
from django.db.models import Q
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import MultipleObjectsReturned
|
||||
from django.utils.timezone import now
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from utils.shortcuts import serializer_invalid_response, error_response, success_response, paginate, rand_str
|
||||
from utils.shortcuts import (serializer_invalid_response, error_response,
|
||||
success_response, error_page, paginate, rand_str)
|
||||
from utils.captcha import Captcha
|
||||
from mail.tasks import send_email
|
||||
|
||||
from envelopes import Envelope
|
||||
|
||||
from .decorators import login_required
|
||||
from .models import User
|
||||
from .serializers import (UserLoginSerializer, UsernameCheckSerializer,
|
||||
UserRegisterSerializer, UserChangePasswordSerializer,
|
||||
EmailCheckSerializer, UserSerializer, EditUserSerializer,
|
||||
ApplyResetPasswordSerializer)
|
||||
ApplyResetPasswordSerializer, ResetPasswordSerializer)
|
||||
from .decorators import super_admin_required
|
||||
|
||||
|
||||
@@ -34,13 +34,9 @@ class UserLoginAPIView(APIView):
|
||||
serializer = UserLoginSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
data = serializer.data
|
||||
|
||||
if "captcha" not in data:
|
||||
return error_response(u"请填写验证码!")
|
||||
captcha = Captcha(request)
|
||||
if not captcha.check(data["captcha"]):
|
||||
return error_response(u"验证码错误")
|
||||
|
||||
user = auth.authenticate(username=data["username"], password=data["password"])
|
||||
# 用户名或密码错误的话 返回None
|
||||
if user:
|
||||
@@ -244,21 +240,59 @@ class ApplyResetPasswordAPIView(APIView):
|
||||
user = User.objects.get(username=data["username"], email=data["email"])
|
||||
except User.DoesNotExist:
|
||||
return error_response(u"用户不存在")
|
||||
if user.reset_password_token_create_time and (now() - user.reset_password_token_create_time).total_seconds() < 20 * 60:
|
||||
return error_response(u"20分钟内只能找回一次密码")
|
||||
user.reset_password_token = rand_str()
|
||||
user.reset_password_token_create_time = now()
|
||||
user.save()
|
||||
email_template = codecs.open(settings.TEMPLATES[0]["DIRS"][0] + "utils/reset_password_email.html", "r", "utf-8").read()
|
||||
|
||||
email_template = email_template.replace("{{ username }}", user.username).replace("{{ link }}", request.scheme + "://" + request.META['HTTP_HOST'] + "/reset_password/?token=" + user.reset_password_token)
|
||||
email_template = email_template.replace("{{ username }}", user.username).\
|
||||
replace("{{ website_name }}", settings.WEBSITE_INFO["website_name"]).\
|
||||
replace("{{ link }}", request.scheme + "://" + request.META['HTTP_HOST'] + "/reset_password/?token=" + user.reset_password_token)
|
||||
|
||||
send_email(user.email, user.username, u"qduoj 密码找回邮件", email_template)
|
||||
return success_response(u"邮件发生成功")
|
||||
send_email(settings.WEBSITE_INFO["website_name"],
|
||||
user.email,
|
||||
user.username,
|
||||
settings.WEBSITE_INFO["website_name"] + u" 密码找回邮件",
|
||||
email_template)
|
||||
return success_response(u"邮件发送成功")
|
||||
else:
|
||||
return serializer_invalid_response(serializer)
|
||||
|
||||
|
||||
class ResetPasswordAPIView(APIView):
|
||||
pass
|
||||
def post(self, request):
|
||||
serializer = ResetPasswordSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
data = serializer.data
|
||||
captcha = Captcha(request)
|
||||
if not captcha.check(data["captcha"]):
|
||||
return error_response(u"验证码错误")
|
||||
try:
|
||||
user = User.objects.get(reset_password_token=data["token"])
|
||||
except User.DoesNotExist:
|
||||
return error_response(u"token 不存在")
|
||||
if (now() - user.reset_password_token_create_time).total_seconds() > 30 * 60:
|
||||
return error_response(u"token 已经过期,请在30分钟内重置密码")
|
||||
user.reset_password_token = None
|
||||
user.set_password(data["password"])
|
||||
user.save()
|
||||
return success_response(u"密码重置成功")
|
||||
else:
|
||||
return serializer_invalid_response(serializer)
|
||||
|
||||
|
||||
def user_index_page(request, username):
|
||||
return render(request, "oj/account/user_index.html")
|
||||
|
||||
|
||||
def auth_page(request):
|
||||
if not request.user.is_authenticated():
|
||||
return render(request, "oj/account/oauth.html")
|
||||
callback = request.GET.get("callback", None)
|
||||
if not callback:
|
||||
return error_page(request, u"参数错误")
|
||||
token = rand_str()
|
||||
request.user.auth_token = token
|
||||
return render(request, "oj/account/oauth.html", {"callback": callback, "token": token})
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# coding=utf-8
|
||||
from functools import wraps
|
||||
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
|
||||
from utils.shortcuts import error_response, error_page
|
||||
|
||||
from account.models import SUPER_ADMIN
|
||||
from .models import Announcement
|
||||
|
||||
|
||||
def check_user_announcement_permission(func):
|
||||
@wraps(func)
|
||||
def _check_user_announcement_permission(*args, **kwargs):
|
||||
"""
|
||||
这个函数检测当前用户能否查看这个公告
|
||||
"""
|
||||
# CBV 的情况,第一个参数是self,第二个参数是request
|
||||
if len(args) == 2:
|
||||
request = args[-1]
|
||||
else:
|
||||
request = args[0]
|
||||
|
||||
if "announcement_id" not in kwargs:
|
||||
return error_page(request, u"参数错误")
|
||||
announcement_id = kwargs["announcement_id"]
|
||||
|
||||
try:
|
||||
announcement = Announcement.objects.get(id=announcement_id, visible=True)
|
||||
except Announcement.DoesNotExist:
|
||||
return error_page(request, u"公告不存在")
|
||||
|
||||
# 如果公告是只有部分小组可见的
|
||||
if not announcement.is_global:
|
||||
# 用户必须是登录状态的
|
||||
if not request.user.is_authenticated():
|
||||
return HttpResponseRedirect("/login/")
|
||||
if not announcement.groups.filter(id__in=request.user.group_set.all()).exists():
|
||||
return error_page(request, u"公告不存在")
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return _check_user_announcement_permission
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('announcement', '0003_auto_20150922_1703'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='announcement',
|
||||
name='groups',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='announcement',
|
||||
name='is_global',
|
||||
),
|
||||
]
|
||||
@@ -19,9 +19,6 @@ class Announcement(models.Model):
|
||||
last_update_time = models.DateTimeField(auto_now=True)
|
||||
# 是否可见 false的话相当于删除
|
||||
visible = models.BooleanField(default=True)
|
||||
# 公告可见范围 True 是全局可见 False 是部分小组可见,需要在下面的字段中存储可见的小组
|
||||
is_global = models.BooleanField()
|
||||
groups = models.ManyToManyField(Group)
|
||||
|
||||
class Meta:
|
||||
db_table = "announcement"
|
||||
|
||||
@@ -8,8 +8,6 @@ from .models import Announcement
|
||||
class CreateAnnouncementSerializer(serializers.Serializer):
|
||||
title = serializers.CharField(max_length=50)
|
||||
content = serializers.CharField(max_length=10000)
|
||||
is_global = serializers.BooleanField()
|
||||
groups = serializers.ListField(child=serializers.IntegerField(), required=False, default=[])
|
||||
|
||||
|
||||
class AnnouncementSerializer(serializers.ModelSerializer):
|
||||
@@ -30,5 +28,3 @@ class EditAnnouncementSerializer(serializers.Serializer):
|
||||
title = serializers.CharField(max_length=50)
|
||||
content = serializers.CharField(max_length=10000)
|
||||
visible = serializers.BooleanField()
|
||||
is_global = serializers.BooleanField()
|
||||
groups = serializers.ListField(child=serializers.IntegerField(), required=False, default=[])
|
||||
|
||||
+39
-146
@@ -1,171 +1,64 @@
|
||||
# coding=utf-8
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from rest_framework.test import APITestCase, APIClient
|
||||
|
||||
from account.models import User
|
||||
from account.tests import create_user
|
||||
from group.models import Group
|
||||
from announcement.models import Announcement
|
||||
from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN
|
||||
from account.models import ADMIN, SUPER_ADMIN
|
||||
|
||||
|
||||
class AnnouncementAdminAPITest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.url = reverse("announcement_admin_api")
|
||||
user1 = User.objects.create(username="test1", admin_type=SUPER_ADMIN)
|
||||
user1.set_password("testaa")
|
||||
user1.save()
|
||||
user2 = User.objects.create(username="test2", admin_type=ADMIN)
|
||||
user2.set_password("testbb")
|
||||
user2.save()
|
||||
self.group = Group.objects.create(name="group1", description="des0",
|
||||
join_group_setting=0, visible=True,
|
||||
admin=user2)
|
||||
self.announcement = Announcement.objects.create(title="bb",
|
||||
content="BB",
|
||||
created_by=User.objects.get(username="test2"),
|
||||
is_global=False)
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.url = reverse("announcement_admin_api")
|
||||
self.user1 = create_user(admin_type=SUPER_ADMIN)
|
||||
|
||||
# 以下是发布公告的测试
|
||||
def test_invalid_format(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
data = {"title": "test1"}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
self.user2 = create_user(username="test1", email="test1@qq.com", admin_type=SUPER_ADMIN)
|
||||
|
||||
def test_group_at_least_one(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
data = {"title": "title0", "content": "content0", "is_global": False}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"至少选择一个小组"})
|
||||
self.announcement = Announcement.objects.create(title="bb",
|
||||
content="BB",
|
||||
created_by=self.user1)
|
||||
|
||||
def test_global_announcement_successfully(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
data = {"title": "title0", "content": "content0", "is_global": True}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 0, "data": u"公告发布成功!"})
|
||||
def test_create_announcement_successfully(self):
|
||||
self.client.login(username="test", password="111111")
|
||||
data = {"title": "title0", "content": "content0"}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 0, "data": u"公告发布成功!"})
|
||||
|
||||
def test_group_announcement_successfully(self):
|
||||
self.client.login(username="test2", password="testbb")
|
||||
data = {"title": "title0", "content": "content0", "is_global": False, "groups": [self.group.id]}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 0, "data": u"公告发布成功!"})
|
||||
|
||||
def test_global_announcement_does_not_has_privileges(self):
|
||||
self.client.login(username="test2", password="testbb")
|
||||
data = {"title": "title0", "content": "content0", "is_global": True}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"只有超级管理员可以创建全局公告"})
|
||||
|
||||
# 以下是编辑公告的测试
|
||||
def test_put_invalid_data(self):
|
||||
self.client.login(username="test1", 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="test1", password="testaa")
|
||||
announcement = Announcement.objects.create(title="aa",
|
||||
content="AA",
|
||||
created_by=User.objects.get(username="test1"),
|
||||
is_global=True)
|
||||
data = {"id": announcement.id + 1, "title": "11", "content": "22",
|
||||
"visible": True, "is_global": True}
|
||||
response = self.client.put(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"公告不存在"})
|
||||
|
||||
def test_edit_global_announcement_successfully(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
data = {"id": self.announcement.id, "title": "11", "content": "22",
|
||||
"visible": True, "is_global": True}
|
||||
response = self.client.put(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 0)
|
||||
|
||||
def test_edit_group_announcement_successfully(self):
|
||||
self.client.login(username="test2", password="testbb")
|
||||
data = {"id": self.announcement.id, "title": "11", "content": "22",
|
||||
"visible": True, "is_global": False, "groups": [self.group.id]}
|
||||
response = self.client.put(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 0)
|
||||
self.assertEqual(response.data["data"]["title"], "11")
|
||||
self.assertEqual(response.data["data"]["content"], "22")
|
||||
|
||||
def test_edit_group_at_least_one(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
data = {"id": self.announcement.id, "title": "title0", "content": "content0",
|
||||
"visible": True, "is_global": False}
|
||||
response = self.client.put(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"至少选择一个小组"})
|
||||
|
||||
# 以下是公告分页的测试
|
||||
def test_get_data_successfully(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
self.assertEqual(self.client.get(self.url).data["code"], 0)
|
||||
|
||||
def test_keyword_global_announcement(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
Announcement.objects.create(title="aa",
|
||||
content="AA",
|
||||
created_by=User.objects.get(username="test1"),
|
||||
visible=True,
|
||||
is_global=True)
|
||||
|
||||
Announcement.objects.create(title="bb",
|
||||
content="BB",
|
||||
created_by=User.objects.get(username="test1"),
|
||||
visible=False,
|
||||
is_global=True)
|
||||
|
||||
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)
|
||||
|
||||
def test_keyword_group_announcement(self):
|
||||
self.client.login(username="test2", password="testbb")
|
||||
Announcement.objects.create(title="aa",
|
||||
content="AA",
|
||||
created_by=User.objects.get(username="test2"),
|
||||
visible=True,
|
||||
is_global=False)
|
||||
|
||||
Announcement.objects.create(title="cc",
|
||||
content="CC",
|
||||
created_by=User.objects.get(username="test2"),
|
||||
visible=False,
|
||||
is_global=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)
|
||||
def test_edit_announcement_successfully(self):
|
||||
self.client.login(username="test", password="111111")
|
||||
data = {"id": self.announcement.id, "title": "11", "content": "22", "visible": True}
|
||||
response = self.client.put(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 0)
|
||||
|
||||
|
||||
class AnnouncementPageTest(TestCase):
|
||||
def setUp(self):
|
||||
user = User.objects.create(username="test")
|
||||
user.set_password("testaa")
|
||||
user.save()
|
||||
Announcement.objects.create(title="aa",
|
||||
content="AA",
|
||||
created_by=User.objects.get(username="test"),
|
||||
visible=True,
|
||||
is_global=True)
|
||||
self.client = Client()
|
||||
user = create_user()
|
||||
self.a1 = Announcement.objects.create(title="aa",
|
||||
content="AA",
|
||||
created_by=user,
|
||||
visible=True,
|
||||
)
|
||||
|
||||
Announcement.objects.create(title="bb",
|
||||
content="BB",
|
||||
created_by=User.objects.get(username="test"),
|
||||
visible=False,
|
||||
is_global=True)
|
||||
self.a2 = Announcement.objects.create(title="bb",
|
||||
content="BB",
|
||||
created_by=User.objects.get(username="test"),
|
||||
visible=False
|
||||
)
|
||||
|
||||
def test_visit_announcement_successfully(self):
|
||||
response = self.client.get('/announcement/1/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.get('/announcement/' + str(self.a1.id) + "/")
|
||||
self.assertTemplateUsed(response, "oj/announcement/announcement.html")
|
||||
|
||||
def test_announcement_does_not_exist(self):
|
||||
response = self.client.get('/announcement/3/')
|
||||
response = self.client.get('/announcement/10086/')
|
||||
self.assertTemplateUsed(response, "utils/error.html")
|
||||
|
||||
def test_visit_hidden_announcement(self):
|
||||
response = self.client.get('/announcement/' + str(self.a2.id) + "/")
|
||||
self.assertTemplateUsed(response, "utils/error.html")
|
||||
|
||||
+15
-42
@@ -6,20 +6,26 @@ from utils.shortcuts import serializer_invalid_response, error_response, success
|
||||
|
||||
from utils.shortcuts import paginate, error_page
|
||||
from account.models import SUPER_ADMIN, ADMIN
|
||||
from account.decorators import super_admin_required
|
||||
from group.models import Group
|
||||
from .models import Announcement
|
||||
from .serializers import (CreateAnnouncementSerializer, AnnouncementSerializer,
|
||||
EditAnnouncementSerializer)
|
||||
from .decorators import check_user_announcement_permission
|
||||
|
||||
|
||||
@check_user_announcement_permission
|
||||
def announcement_page(request, announcement_id):
|
||||
announcement = Announcement.objects.get(id=announcement_id, visible=True)
|
||||
"""
|
||||
公告的详情页面
|
||||
"""
|
||||
try:
|
||||
announcement = Announcement.objects.get(id=announcement_id, visible=True)
|
||||
except Announcement.DoesNotExist:
|
||||
return error_page(request, u"公告不存在")
|
||||
return render(request, "oj/announcement/announcement.html", {"announcement": announcement})
|
||||
|
||||
|
||||
class AnnouncementAdminAPIView(APIView):
|
||||
@super_admin_required
|
||||
def post(self, request):
|
||||
"""
|
||||
公告发布json api接口
|
||||
@@ -29,29 +35,12 @@ class AnnouncementAdminAPIView(APIView):
|
||||
serializer = CreateAnnouncementSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
data = serializer.data
|
||||
groups = []
|
||||
# 如果不是全局公告,就去查询一下小组的id 列表中的内容,注意用户身份
|
||||
if not data["is_global"]:
|
||||
if request.user.admin_type == SUPER_ADMIN:
|
||||
groups = Group.objects.filter(id__in=data["groups"])
|
||||
else:
|
||||
groups = Group.objects.filter(id__in=data["groups"], admin=request.user)
|
||||
if not groups.count():
|
||||
return error_response(u"至少选择一个小组")
|
||||
else:
|
||||
if request.user.admin_type != SUPER_ADMIN:
|
||||
return error_response(u"只有超级管理员可以创建全局公告")
|
||||
|
||||
announcement = Announcement.objects.create(title=data["title"],
|
||||
content=data["content"],
|
||||
created_by=request.user,
|
||||
is_global=data["is_global"])
|
||||
|
||||
announcement.groups.add(*groups)
|
||||
Announcement.objects.create(title=data["title"], content=data["content"], created_by=request.user)
|
||||
return success_response(u"公告发布成功!")
|
||||
else:
|
||||
return serializer_invalid_response(serializer)
|
||||
|
||||
@super_admin_required
|
||||
def put(self, request):
|
||||
"""
|
||||
公告编辑json api接口
|
||||
@@ -63,43 +52,27 @@ class AnnouncementAdminAPIView(APIView):
|
||||
if serializer.is_valid():
|
||||
data = serializer.data
|
||||
try:
|
||||
if request.user.admin_type == SUPER_ADMIN:
|
||||
announcement = Announcement.objects.get(id=data["id"])
|
||||
else:
|
||||
announcement = Announcement.objects.get(id=data["id"], created_by=request.user)
|
||||
announcement = Announcement.objects.get(id=data["id"])
|
||||
except Announcement.DoesNotExist:
|
||||
return error_response(u"公告不存在")
|
||||
groups = []
|
||||
if not data["is_global"]:
|
||||
if request.user.admin_type == SUPER_ADMIN:
|
||||
groups = Group.objects.filter(id__in=data["groups"])
|
||||
else:
|
||||
groups = Group.objects.filter(id__in=data["groups"], admin=request.user)
|
||||
if not groups.count():
|
||||
return error_response(u"至少选择一个小组")
|
||||
|
||||
announcement.title = data["title"]
|
||||
announcement.content = data["content"]
|
||||
announcement.visible = data["visible"]
|
||||
announcement.is_global = data["is_global"]
|
||||
announcement.save()
|
||||
|
||||
# 重建小组和公告的对应关系
|
||||
announcement.groups.clear()
|
||||
announcement.groups.add(*groups)
|
||||
return success_response(AnnouncementSerializer(announcement).data)
|
||||
else:
|
||||
return serializer_invalid_response(serializer)
|
||||
|
||||
@super_admin_required
|
||||
def get(self, request):
|
||||
"""
|
||||
公告分页json api接口
|
||||
---
|
||||
response_serializer: AnnouncementSerializer
|
||||
"""
|
||||
if request.user.admin_type == SUPER_ADMIN:
|
||||
announcement = Announcement.objects.all().order_by("-last_update_time")
|
||||
else:
|
||||
announcement = Announcement.objects.filter(created_by=request.user)
|
||||
announcement = Announcement.objects.all().order_by("-create_time")
|
||||
visible = request.GET.get("visible", None)
|
||||
if visible:
|
||||
announcement = announcement.filter(visible=(visible == "true"))
|
||||
|
||||
+10
-1
@@ -32,11 +32,13 @@ def check_user_contest_permission(func):
|
||||
else:
|
||||
return HttpResponseRedirect("/login/")
|
||||
|
||||
# kwargs 就包含了url 里面的播或参数
|
||||
# kwargs 就包含了 url 里面的参数
|
||||
if "contest_id" in kwargs:
|
||||
contest_id = kwargs["contest_id"]
|
||||
elif "contest_id" in request.data:
|
||||
contest_id = request.data["contest_id"]
|
||||
elif "contest_id" in request.GET:
|
||||
contest_id = request.GET["contest_id"]
|
||||
else:
|
||||
if request.is_ajax():
|
||||
return error_response(u"参数错误")
|
||||
@@ -54,6 +56,13 @@ def check_user_contest_permission(func):
|
||||
if request.user.admin_type == SUPER_ADMIN or request.user == contest.created_by:
|
||||
return func(*args, **kwargs)
|
||||
|
||||
# 管理员可见隐藏的比赛,已经先判断了身份
|
||||
if not contest.visible:
|
||||
if request.is_ajax():
|
||||
return error_response(u"比赛不存在")
|
||||
else:
|
||||
return error_page(request, u"比赛不存在")
|
||||
|
||||
# 有密码的公开赛
|
||||
if contest.contest_type == PASSWORD_PROTECTED_CONTEST:
|
||||
# 没有输入过密码
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import utils.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contest', '0012_auto_20151008_1124'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='contestproblemtestcase',
|
||||
name='problem',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='contestsubmission',
|
||||
name='contest',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='contestsubmission',
|
||||
name='problem',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='contestsubmission',
|
||||
name='user',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='contest',
|
||||
name='mode',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='contest',
|
||||
name='show_user_submission',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='contestproblem',
|
||||
name='score',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='contestproblem',
|
||||
name='is_public',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='contestproblem',
|
||||
name='last_update_time',
|
||||
field=models.DateTimeField(null=True, blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='contestproblem',
|
||||
name='hint',
|
||||
field=utils.models.RichTextField(null=True, blank=True),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='ContestProblemTestCase',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='ContestSubmission',
|
||||
),
|
||||
]
|
||||
+2
-39
@@ -22,12 +22,8 @@ CONTEST_UNDERWAY = 0
|
||||
class Contest(models.Model):
|
||||
title = models.CharField(max_length=40, unique=True)
|
||||
description = RichTextField()
|
||||
# 比赛模式:0 即为是acm模式,1 即为是按照总的 ac 题目数量排名模式
|
||||
mode = models.IntegerField()
|
||||
# 是否显示实时排名结果
|
||||
real_time_rank = models.BooleanField()
|
||||
# 是否显示别人的提交记录
|
||||
show_user_submission = models.BooleanField()
|
||||
# 只能超级管理员创建公开赛,管理员只能创建小组内部的比赛
|
||||
# 如果这一项不为空,即为有密码的公开赛,没有密码的可以为小组赛或者是公开赛(此时用比赛的类型来表示)
|
||||
password = models.CharField(max_length=30, blank=True, null=True)
|
||||
@@ -68,46 +64,13 @@ class ContestProblem(AbstractProblem):
|
||||
contest = models.ForeignKey(Contest)
|
||||
# 比如A B 或者1 2 或者 a b 将按照这个排序
|
||||
sort_index = models.CharField(max_length=30)
|
||||
score = models.IntegerField(default=0)
|
||||
# 是否已经公开了题目,防止重复公开
|
||||
is_public = models.BooleanField(default=False)
|
||||
|
||||
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"
|
||||
|
||||
|
||||
class ContestSubmission(models.Model):
|
||||
"""
|
||||
用于保存比赛提交和排名的一些数据,加快检索速度
|
||||
"""
|
||||
user = models.ForeignKey(User)
|
||||
problem = models.ForeignKey(ContestProblem)
|
||||
contest = models.ForeignKey(Contest)
|
||||
total_submission_number = models.IntegerField(default=1)
|
||||
# 这道题是 AC 还是没过
|
||||
ac = models.BooleanField()
|
||||
# ac 用时以秒计
|
||||
ac_time = models.IntegerField(default=0)
|
||||
# 总的时间,用于acm 类型的,也需要保存罚时
|
||||
total_time = models.IntegerField(default=0)
|
||||
# 第一个解出此题目
|
||||
first_achieved = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
db_table = "contest_submission"
|
||||
|
||||
|
||||
class ContestRank(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
contest = models.ForeignKey(Contest)
|
||||
|
||||
+35
-2
@@ -8,8 +8,7 @@ from rest_framework.test import APITestCase, APIClient
|
||||
from account.models import User
|
||||
from group.models import Group
|
||||
from contest.models import Contest, ContestProblem
|
||||
from .models import ContestSubmission
|
||||
from .models import GROUP_CONTEST, PASSWORD_PROTECTED_CONTEST
|
||||
from .models import GROUP_CONTEST, PASSWORD_PROTECTED_CONTEST, PUBLIC_CONTEST
|
||||
from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN
|
||||
|
||||
|
||||
@@ -582,5 +581,39 @@ class ContestListPageTest(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class ContestProblemMySubmissionListTest(TestCase):
|
||||
# 以下是我比赛单个题目的提交列表的测试
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.user1 = User.objects.create(username="test1", admin_type=SUPER_ADMIN)
|
||||
self.user1.set_password("testaa")
|
||||
self.user1.save()
|
||||
self.user2 = User.objects.create(username="test2", admin_type=REGULAR_USER)
|
||||
self.user2.set_password("testbb")
|
||||
self.user2.save()
|
||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
||||
contest_type=PUBLIC_CONTEST, show_rank=True,
|
||||
show_user_submission=True,
|
||||
start_time="2015-08-15T10:00:00.000Z",
|
||||
end_time="2015-08-30T12:00:00.000Z",
|
||||
created_by=User.objects.get(username="test1"))
|
||||
self.contest_problem = ContestProblem.objects.create(title="titlex",
|
||||
description="descriptionx",
|
||||
input_description="input1_description",
|
||||
output_description="output1_description",
|
||||
test_case_id="1",
|
||||
samples=json.dumps([{"input": "1 1", "output": "2"}]),
|
||||
time_limit=100,
|
||||
memory_limit=1000,
|
||||
hint="hint1",
|
||||
created_by=self.user1,
|
||||
contest=self.global_contest,
|
||||
sort_index="a")
|
||||
|
||||
def test_contestsList_page_not_exist(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
response = self.client.get('/contest/1/submissions/999/')
|
||||
self.assertTemplateUsed(response, "utils/error.html")
|
||||
|
||||
|
||||
|
||||
|
||||
+106
-12
@@ -19,7 +19,8 @@ from account.models import SUPER_ADMIN, User
|
||||
from account.decorators import login_required
|
||||
from group.models import Group
|
||||
from utils.cache import get_cache_redis
|
||||
from .models import (Contest, ContestProblem, ContestSubmission, CONTEST_ENDED,
|
||||
from submission.models import Submission
|
||||
from .models import (Contest, ContestProblem, CONTEST_ENDED,
|
||||
CONTEST_NOT_START, CONTEST_UNDERWAY, ContestRank)
|
||||
from .models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST
|
||||
from .decorators import check_user_contest_permission
|
||||
@@ -293,7 +294,6 @@ def contest_page(request, contest_id):
|
||||
单个比赛的详情页
|
||||
"""
|
||||
contest = Contest.objects.get(id=contest_id)
|
||||
|
||||
return render(request, "oj/contest/contest_index.html", {"contest": contest})
|
||||
|
||||
|
||||
@@ -304,15 +304,17 @@ def contest_problem_page(request, contest_id, contest_problem_id):
|
||||
"""
|
||||
contest = Contest.objects.get(id=contest_id)
|
||||
try:
|
||||
contest_problem = ContestProblem.objects.get(id=contest_problem_id, visible=True)
|
||||
problem = ContestProblem.objects.get(id=contest_problem_id, visible=True)
|
||||
except ContestProblem.DoesNotExist:
|
||||
return error_page(request, u"比赛题目不存在")
|
||||
warning = u"您已经提交过本题的正确答案,重复提交可能造成时间累计。"
|
||||
show_warning = False
|
||||
|
||||
try:
|
||||
submission = ContestSubmission.objects.get(user=request.user, contest=contest, problem=contest_problem)
|
||||
show_warning = submission.ac
|
||||
except ContestSubmission.DoesNotExist:
|
||||
rank = ContestRank.objects.get(user=request.user, contest=contest)
|
||||
# 提示已经 ac 过这道题了
|
||||
show_warning = rank.submission_info.get(str(problem.id), {"is_ac": False})["is_ac"]
|
||||
except ContestRank.DoesNotExist:
|
||||
pass
|
||||
|
||||
# 已经结束
|
||||
@@ -324,14 +326,16 @@ def contest_problem_page(request, contest_id, contest_problem_id):
|
||||
warning = u"比赛没有开始,您是管理员,可以提交和测试题目,但是目前的提交不会计入排名。"
|
||||
|
||||
show_submit_code_area = False
|
||||
if contest.status == CONTEST_UNDERWAY:
|
||||
show_submit_code_area = True
|
||||
if request.user.admin_type == SUPER_ADMIN or request.user == contest.created_by:
|
||||
if contest.status == CONTEST_UNDERWAY or \
|
||||
request.user.admin_type == SUPER_ADMIN or \
|
||||
request.user == contest.created_by:
|
||||
show_submit_code_area = True
|
||||
|
||||
return render(request, "oj/contest/contest_problem.html", {"contest_problem": contest_problem, "contest": contest,
|
||||
"samples": json.loads(contest_problem.samples),
|
||||
"show_warning": show_warning, "warning": warning,
|
||||
return render(request, "oj/problem/contest_problem.html", {"problem": problem,
|
||||
"contest": contest,
|
||||
"samples": json.loads(problem.samples),
|
||||
"show_warning": show_warning,
|
||||
"warning": warning,
|
||||
"show_submit_code_area": show_submit_code_area})
|
||||
|
||||
|
||||
@@ -442,3 +446,93 @@ class ContestTimeAPIView(APIView):
|
||||
return success_response({"start": int((contest.start_time - now()).total_seconds() * 1000),
|
||||
"end": int((contest.end_time - now()).total_seconds() * 1000),
|
||||
"status": contest.status})
|
||||
|
||||
|
||||
@login_required
|
||||
def contest_problem_my_submissions_list_page(request, contest_id, contest_problem_id):
|
||||
"""
|
||||
我比赛单个题目的所有提交列表
|
||||
"""
|
||||
try:
|
||||
Contest.objects.get(id=contest_id)
|
||||
except Contest.DoesNotExist:
|
||||
return error_page(request, u"比赛不存在")
|
||||
try:
|
||||
contest_problem = ContestProblem.objects.get(id=contest_problem_id, visible=True)
|
||||
except ContestProblem.DoesNotExist:
|
||||
return error_page(request, u"比赛问题不存在")
|
||||
submissions = Submission.objects.filter(user_id=request.user.id, problem_id=contest_problem.id).\
|
||||
order_by("-create_time").\
|
||||
values("id", "result", "create_time", "accepted_answer_time", "language")
|
||||
return render(request, "oj/submission/problem_my_submissions_list.html",
|
||||
{"submissions": submissions, "problem": contest_problem})
|
||||
|
||||
|
||||
@check_user_contest_permission
|
||||
def contest_problem_submissions_list_page(request, contest_id, page=1):
|
||||
"""
|
||||
单个比赛中的所有提交(包含自己和别人,自己可查提交结果,其他人不可查)
|
||||
"""
|
||||
try:
|
||||
contest = Contest.objects.get(id=contest_id)
|
||||
except Contest.DoesNotExist:
|
||||
return error_page(request, u"比赛不存在")
|
||||
|
||||
submissions = Submission.objects.filter(contest_id=contest_id).\
|
||||
values("id", "contest_id", "problem_id", "result", "create_time",
|
||||
"accepted_answer_time", "language", "user_id").order_by("-create_time")
|
||||
|
||||
user_id = request.GET.get("user_id", None)
|
||||
if user_id:
|
||||
submissions = submissions.filter(user_id=request.GET.get("user_id"))
|
||||
|
||||
# 封榜的时候只能看到自己的提交
|
||||
if not contest.real_time_rank:
|
||||
if not (request.user.admin_type == SUPER_ADMIN or request.user == contest.created_by):
|
||||
submissions = submissions.filter(user_id=request.user.id)
|
||||
|
||||
language = request.GET.get("language", None)
|
||||
filter = None
|
||||
if language:
|
||||
submissions = submissions.filter(language=int(language))
|
||||
filter = {"name": "language", "content": language}
|
||||
result = request.GET.get("result", None)
|
||||
if result:
|
||||
submissions = submissions.filter(result=int(result))
|
||||
filter = {"name": "result", "content": result}
|
||||
paginator = Paginator(submissions, 20)
|
||||
|
||||
# 为查询题目标题创建新字典
|
||||
title = {}
|
||||
contest_problems = ContestProblem.objects.filter(contest=contest)
|
||||
for item in contest_problems:
|
||||
title[item.id] = item.title
|
||||
for item in submissions:
|
||||
item['title'] = title[item['problem_id']]
|
||||
|
||||
try:
|
||||
current_page = paginator.page(int(page))
|
||||
except Exception:
|
||||
return error_page(request, u"不存在的页码")
|
||||
previous_page = next_page = None
|
||||
try:
|
||||
previous_page = current_page.previous_page_number()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
next_page = current_page.next_page_number()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for item in current_page:
|
||||
# 自己提交的 管理员和创建比赛的可以看到所有的提交链接
|
||||
if item["user_id"] == request.user.id or request.user.admin_type == SUPER_ADMIN or \
|
||||
request.user == contest.created_by:
|
||||
item["show_link"] = True
|
||||
else:
|
||||
item["show_link"] = False
|
||||
|
||||
return render(request, "oj/contest/submissions_list.html",
|
||||
{"submissions": current_page, "page": int(page),
|
||||
"previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20,
|
||||
"contest": contest, "filter": filter, "user_id": user_id})
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -1,11 +0,0 @@
|
||||
# coding=utf-8
|
||||
from rest_framework import serializers
|
||||
|
||||
from account.models import User
|
||||
|
||||
|
||||
class CreateContestSubmissionSerializer(serializers.Serializer):
|
||||
contest_id = serializers.IntegerField()
|
||||
problem_id = serializers.IntegerField()
|
||||
language = serializers.IntegerField()
|
||||
code = serializers.CharField(max_length=3000)
|
||||
@@ -1,172 +0,0 @@
|
||||
# coding=utf-8
|
||||
import json
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from django.core.urlresolvers import reverse
|
||||
from rest_framework.test import APITestCase, APIClient
|
||||
|
||||
from account.models import User, REGULAR_USER, ADMIN, SUPER_ADMIN
|
||||
from contest.models import Contest, ContestProblem
|
||||
from contest.models import PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST
|
||||
from submission.models import Submission
|
||||
|
||||
|
||||
class ContestSubmissionAPITest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.url = reverse('contest_submission_api')
|
||||
self.user1 = User.objects.create(username="test1", admin_type=SUPER_ADMIN)
|
||||
self.user1.set_password("testaa")
|
||||
self.user1.save()
|
||||
self.user2 = User.objects.create(username="test2", admin_type=REGULAR_USER)
|
||||
self.user2.set_password("testbb")
|
||||
self.user2.save()
|
||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
||||
contest_type=PUBLIC_CONTEST, show_rank=True,
|
||||
show_user_submission=True,
|
||||
start_time="2015-08-15T10:00:00.000Z",
|
||||
end_time="2015-08-30T12:00:00.000Z",
|
||||
created_by=User.objects.get(username="test1"))
|
||||
self.contest_problem = ContestProblem.objects.create(title="titlex",
|
||||
description="descriptionx",
|
||||
input_description="input1_description",
|
||||
output_description="output1_description",
|
||||
test_case_id="1",
|
||||
samples=json.dumps([{"input": "1 1", "output": "2"}]),
|
||||
time_limit=100,
|
||||
memory_limit=1000,
|
||||
hint="hint1",
|
||||
created_by=User.objects.get(username="test1"),
|
||||
contest=Contest.objects.get(title="titlex"),
|
||||
sort_index="a")
|
||||
|
||||
# 以下是创建比赛的提交
|
||||
def test_invalid_format(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
data = {"contest_id": self.global_contest.id, "language": 1}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_contest_submission_successfully(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
data = {"contest_id": self.global_contest.id, "problem_id": self.contest_problem.id,
|
||||
"language": 1, "code": '#include "stdio.h"\nint main(){\n\treturn 0;\n}'}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 0)
|
||||
|
||||
def test_contest_problem_does_not_exist(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
data = {"contest_id": self.global_contest.id, "problem_id": self.contest_problem.id + 10,
|
||||
"language": 1, "code": '#include "stdio.h"\nint main(){\n\treturn 0;\n}'}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"题目不存在"})
|
||||
|
||||
|
||||
class ContestProblemMySubmissionListTest(TestCase):
|
||||
# 以下是我比赛单个题目的提交列表的测试
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.user1 = User.objects.create(username="test1", admin_type=SUPER_ADMIN)
|
||||
self.user1.set_password("testaa")
|
||||
self.user1.save()
|
||||
self.user2 = User.objects.create(username="test2", admin_type=REGULAR_USER)
|
||||
self.user2.set_password("testbb")
|
||||
self.user2.save()
|
||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
||||
contest_type=PUBLIC_CONTEST, show_rank=True,
|
||||
show_user_submission=True,
|
||||
start_time="2015-08-15T10:00:00.000Z",
|
||||
end_time="2015-08-30T12:00:00.000Z",
|
||||
created_by=User.objects.get(username="test1"))
|
||||
self.contest_problem = ContestProblem.objects.create(title="titlex",
|
||||
description="descriptionx",
|
||||
input_description="input1_description",
|
||||
output_description="output1_description",
|
||||
test_case_id="1",
|
||||
samples=json.dumps([{"input": "1 1", "output": "2"}]),
|
||||
time_limit=100,
|
||||
memory_limit=1000,
|
||||
hint="hint1",
|
||||
created_by=self.user1,
|
||||
contest=self.global_contest,
|
||||
sort_index="a")
|
||||
|
||||
def test_contestsList_page_not_exist(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
response = self.client.get('/contest/1/submissions/999/')
|
||||
self.assertTemplateUsed(response, "utils/error.html")
|
||||
|
||||
|
||||
class SubmissionAPITest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.url = reverse('contest_submission_admin_api_view')
|
||||
self.userA = User.objects.create(username="test1", admin_type=ADMIN)
|
||||
self.userA.set_password("testaa")
|
||||
self.userA.save()
|
||||
self.userS = User.objects.create(username="test2", admin_type=SUPER_ADMIN)
|
||||
self.userS.set_password("testbb")
|
||||
self.userS.save()
|
||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
||||
contest_type=PASSWORD_PROTECTED_CONTEST, show_rank=True,
|
||||
show_user_submission=True,
|
||||
start_time="2015-08-15T10:00:00.000Z",
|
||||
end_time="2015-08-15T12:00:00.000Z",
|
||||
password="aacc", created_by=self.userS
|
||||
)
|
||||
self.problem = ContestProblem.objects.create(title="title1",
|
||||
description="description1",
|
||||
input_description="input1_description",
|
||||
output_description="output1_description",
|
||||
test_case_id="1",
|
||||
sort_index="1",
|
||||
samples=json.dumps([{"input": "1 1", "output": "2"}]),
|
||||
time_limit=100,
|
||||
memory_limit=1000,
|
||||
hint="hint1",
|
||||
contest=self.global_contest,
|
||||
created_by=self.userS)
|
||||
self.submission = Submission.objects.create(user_id=self.userA.id,
|
||||
language=1,
|
||||
code='#include "stdio.h"\nint main(){\n\treturn 0;\n}',
|
||||
problem_id=self.problem.id)
|
||||
self.submissionS = Submission.objects.create(user_id=self.userS.id,
|
||||
language=2,
|
||||
code='#include "stdio.h"\nint main(){\n\treturn 0;\n}',
|
||||
problem_id=self.problem.id)
|
||||
|
||||
def test_submission_contest_does_not_exist(self):
|
||||
self.client.login(username="test2", password="testbb")
|
||||
response = self.client.get(self.url + "?contest_id=99")
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_submission_contest_parameter_error(self):
|
||||
self.client.login(username="test2", password="testbb")
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_submission_access_denied(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
response = self.client.get(self.url + "?problem_id=" + str(self.problem.id))
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_submission_access_denied_with_contest_id(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
response = self.client.get(self.url + "?contest_id=" + str(self.global_contest.id))
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_get_submission_successfully(self):
|
||||
self.client.login(username="test2", password="testbb")
|
||||
response = self.client.get(
|
||||
self.url + "?contest_id=" + str(self.global_contest.id) + "&problem_id=" + str(self.problem.id))
|
||||
self.assertEqual(response.data["code"], 0)
|
||||
|
||||
def test_get_submission_successfully_problem(self):
|
||||
self.client.login(username="test2", password="testbb")
|
||||
response = self.client.get(self.url + "?problem_id=" + str(self.problem.id))
|
||||
self.assertEqual(response.data["code"], 0)
|
||||
|
||||
def test_get_submission_problem_do_not_exist(self):
|
||||
self.client.login(username="test2", password="testbb")
|
||||
response = self.client.get(self.url + "?problem_id=9999")
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
@@ -1,186 +0,0 @@
|
||||
# coding=utf-8
|
||||
import json
|
||||
from datetime import datetime
|
||||
import redis
|
||||
import pytz
|
||||
from django.shortcuts import render
|
||||
from django.core.paginator import Paginator
|
||||
from django.utils import timezone
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from judge.judger_controller.tasks import judge
|
||||
|
||||
from account.decorators import login_required
|
||||
from account.models import SUPER_ADMIN
|
||||
|
||||
from contest.decorators import check_user_contest_permission
|
||||
from contest.models import Contest, ContestProblem
|
||||
|
||||
from utils.shortcuts import serializer_invalid_response, error_response, success_response, error_page, paginate
|
||||
from utils.cache import get_cache_redis
|
||||
from submission.models import Submission
|
||||
from .serializers import CreateContestSubmissionSerializer
|
||||
from submission.serializers import SubmissionSerializer
|
||||
|
||||
|
||||
class ContestSubmissionAPIView(APIView):
|
||||
@check_user_contest_permission
|
||||
def post(self, request):
|
||||
"""
|
||||
创建比赛的提交
|
||||
---
|
||||
request_serializer: CreateContestSubmissionSerializer
|
||||
"""
|
||||
serializer = CreateContestSubmissionSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
data = serializer.data
|
||||
contest = Contest.objects.get(id=data["contest_id"])
|
||||
try:
|
||||
problem = ContestProblem.objects.get(contest=contest, id=data["problem_id"])
|
||||
# 更新题目提交计数器
|
||||
problem.total_submit_number += 1
|
||||
problem.save()
|
||||
except ContestProblem.DoesNotExist:
|
||||
return error_response(u"题目不存在")
|
||||
submission = Submission.objects.create(user_id=request.user.id, language=int(data["language"]),
|
||||
contest_id=contest.id, code=data["code"], problem_id=problem.id)
|
||||
try:
|
||||
judge.delay(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
||||
except Exception:
|
||||
return error_response(u"提交判题任务失败")
|
||||
# 修改用户解题状态
|
||||
problems_status = request.user.problems_status
|
||||
if "contest_problems" not in problems_status:
|
||||
problems_status["contest_problems"] = {}
|
||||
problems_status["contest_problems"][str(data["problem_id"])] = 2
|
||||
request.user.problems_status = problems_status
|
||||
request.user.save()
|
||||
# 增加redis 中判题队列长度的计数器
|
||||
r = get_cache_redis()
|
||||
r.incr("judge_queue_length")
|
||||
return success_response({"submission_id": submission.id})
|
||||
else:
|
||||
return serializer_invalid_response(serializer)
|
||||
|
||||
|
||||
@login_required
|
||||
def contest_problem_my_submissions_list_page(request, contest_id, contest_problem_id):
|
||||
"""
|
||||
我比赛单个题目的所有提交列表
|
||||
"""
|
||||
try:
|
||||
Contest.objects.get(id=contest_id)
|
||||
except Contest.DoesNotExist:
|
||||
return error_page(request, u"比赛不存在")
|
||||
try:
|
||||
contest_problem = ContestProblem.objects.get(id=contest_problem_id, visible=True)
|
||||
except ContestProblem.DoesNotExist:
|
||||
return error_page(request, u"比赛问题不存在")
|
||||
submissions = Submission.objects.filter(user_id=request.user.id, problem_id=contest_problem.id).order_by(
|
||||
"-create_time"). \
|
||||
values("id", "result", "create_time", "accepted_answer_time", "language")
|
||||
return render(request, "oj/contest/my_submissions_list.html",
|
||||
{"submissions": submissions, "problem": contest_problem})
|
||||
|
||||
|
||||
@check_user_contest_permission
|
||||
def contest_problem_submissions_list_page(request, contest_id, page=1):
|
||||
"""
|
||||
单个比赛中的所有提交(包含自己和别人,自己可查提交结果,其他人不可查)
|
||||
"""
|
||||
try:
|
||||
contest = Contest.objects.get(id=contest_id)
|
||||
except Contest.DoesNotExist:
|
||||
return error_page(request, u"比赛不存在")
|
||||
|
||||
submissions = Submission.objects.filter(contest_id=contest_id).\
|
||||
values("id", "contest_id", "problem_id", "result", "create_time",
|
||||
"accepted_answer_time", "language", "user_id").order_by("-create_time")
|
||||
|
||||
user_id = request.GET.get("user_id", None)
|
||||
if user_id:
|
||||
submissions = submissions.filter(user_id=request.GET.get("user_id"))
|
||||
|
||||
# 封榜的时候只能看到自己的提交
|
||||
if not contest.real_time_rank:
|
||||
if not (request.user.admin_type == SUPER_ADMIN or request.user == contest.created_by):
|
||||
submissions = submissions.filter(user_id=request.user.id)
|
||||
|
||||
language = request.GET.get("language", None)
|
||||
filter = None
|
||||
if language:
|
||||
submissions = submissions.filter(language=int(language))
|
||||
filter = {"name": "language", "content": language}
|
||||
result = request.GET.get("result", None)
|
||||
if result:
|
||||
submissions = submissions.filter(result=int(result))
|
||||
filter = {"name": "result", "content": result}
|
||||
paginator = Paginator(submissions, 20)
|
||||
|
||||
# 为查询题目标题创建新字典
|
||||
title = {}
|
||||
contest_problems = ContestProblem.objects.filter(contest=contest)
|
||||
for item in contest_problems:
|
||||
title[item.id] = item.title
|
||||
for item in submissions:
|
||||
item['title'] = title[item['problem_id']]
|
||||
|
||||
try:
|
||||
current_page = paginator.page(int(page))
|
||||
except Exception:
|
||||
return error_page(request, u"不存在的页码")
|
||||
previous_page = next_page = None
|
||||
try:
|
||||
previous_page = current_page.previous_page_number()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
next_page = current_page.next_page_number()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for item in current_page:
|
||||
# 自己提交的 管理员和创建比赛的可以看到所有的提交链接
|
||||
if item["user_id"] == request.user.id or request.user.admin_type == SUPER_ADMIN or \
|
||||
request.user == contest.created_by:
|
||||
item["show_link"] = True
|
||||
else:
|
||||
item["show_link"] = False
|
||||
|
||||
return render(request, "oj/contest/submissions_list.html",
|
||||
{"submissions": current_page, "page": int(page),
|
||||
"previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20,
|
||||
"contest": contest, "filter": filter, "user_id": user_id})
|
||||
|
||||
|
||||
class ContestSubmissionAdminAPIView(APIView):
|
||||
def get(self, request):
|
||||
"""
|
||||
查询比赛提交,单个比赛题目提交的adminAPI
|
||||
---
|
||||
response_serializer: SubmissionSerializer
|
||||
"""
|
||||
problem_id = request.GET.get("problem_id", None)
|
||||
contest_id = request.GET.get("contest_id", None)
|
||||
if contest_id:
|
||||
try:
|
||||
contest = Contest.objects.get(pk=contest_id)
|
||||
except Contest.DoesNotExist:
|
||||
return error_response(u"比赛不存在!")
|
||||
if request.user.admin_type != SUPER_ADMIN and contest.created_by != request.user:
|
||||
return error_response(u"您无权查看该信息!")
|
||||
submissions = Submission.objects.filter(contest_id=contest_id).order_by("-create_time")
|
||||
else:
|
||||
if problem_id:
|
||||
try:
|
||||
contest_problem = ContestProblem.objects.get(pk=problem_id)
|
||||
except ContestProblem.DoesNotExist:
|
||||
return error_response(u"问题不存在!")
|
||||
if request.user.admin_type != SUPER_ADMIN and contest_problem.contest.created_by != request.user:
|
||||
return error_response(u"您无权查看该信息!")
|
||||
submissions = Submission.objects.filter(contest_id=contest_problem.contest_id).order_by("-create_time")
|
||||
else:
|
||||
return error_response(u"参数错误!")
|
||||
if problem_id:
|
||||
submissions = submissions.filter(problem_id=problem_id)
|
||||
return paginate(request, submissions, SubmissionSerializer)
|
||||
+16
-11
@@ -1,14 +1,19 @@
|
||||
# coding=utf-8
|
||||
import os
|
||||
from envelopes import Envelope
|
||||
|
||||
SMTP_CONFIG = {"smtp_server": "smtp.mxhichina.com",
|
||||
"email": "noreply@qduoj.com",
|
||||
"password": os.environ.get("smtp_password", "111111"),
|
||||
"tls": False}
|
||||
|
||||
|
||||
def send_email(*args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
'''
|
||||
envelope = Envelope(from_addr=("noreply@qduoj.com", u"qduoj 密码找回邮件", email_template),
|
||||
to_addr=(user.email, user.username),
|
||||
subject=u"qduoj 密码找回邮件",
|
||||
html_body=email_template)
|
||||
envelope.send("smtp.mxhichina.com", login="noreply@qduoj.com", password="092122302Zarpe2015", tls=False)
|
||||
'''
|
||||
def send_email(from_name, to_email, to_name, subject, content):
|
||||
envelope = Envelope(from_addr=(SMTP_CONFIG["email"], from_name),
|
||||
to_addr=(to_email, to_name),
|
||||
subject=subject,
|
||||
html_body=content)
|
||||
envelope.send(SMTP_CONFIG["smtp_server"],
|
||||
login=SMTP_CONFIG["email"],
|
||||
password=SMTP_CONFIG["password"],
|
||||
tls=SMTP_CONFIG["tls"])
|
||||
|
||||
+1
-2
@@ -50,7 +50,6 @@ INSTALLED_APPS = (
|
||||
'submission',
|
||||
'mq',
|
||||
'contest',
|
||||
'contest_submission',
|
||||
'mail',
|
||||
|
||||
'django_extensions',
|
||||
@@ -59,7 +58,7 @@ INSTALLED_APPS = (
|
||||
|
||||
if DEBUG:
|
||||
INSTALLED_APPS += (
|
||||
'debug_toolbar',
|
||||
# 'debug_toolbar',
|
||||
'rest_framework_swagger',
|
||||
)
|
||||
|
||||
|
||||
+7
-7
@@ -19,13 +19,12 @@ from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView,
|
||||
from admin.views import AdminTemplateView
|
||||
|
||||
from problem.views import TestCaseUploadAPIView, ProblemTagAdminAPIView, ProblemAdminAPIView
|
||||
from submission.views import (SubmissionAPIView, SubmissionAdminAPIView,
|
||||
SubmissionShareAPIView, SubmissionRejudgeAdminAPIView)
|
||||
from contest_submission.views import ContestSubmissionAPIView, ContestSubmissionAdminAPIView
|
||||
from submission.views import (SubmissionAPIView, SubmissionAdminAPIView, ContestSubmissionAPIView,
|
||||
SubmissionShareAPIView, SubmissionRejudgeAdminAPIView,
|
||||
ContestSubmissionAdminAPIView)
|
||||
from monitor.views import QueueLengthMonitorAPIView
|
||||
from utils.views import SimditorImageUploadAPIView
|
||||
|
||||
from contest_submission.views import contest_problem_my_submissions_list_page
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
@@ -78,16 +77,16 @@ urlpatterns = [
|
||||
url(r'^contest/(?P<contest_id>\d+)/problem/(?P<contest_problem_id>\d+)/$', "contest.views.contest_problem_page",
|
||||
name="contest_problem_page"),
|
||||
url(r'^contest/(?P<contest_id>\d+)/problem/(?P<contest_problem_id>\d+)/submissions/$',
|
||||
"contest_submission.views.contest_problem_my_submissions_list_page",
|
||||
"contest.views.contest_problem_my_submissions_list_page",
|
||||
name="contest_problem_my_submissions_list_page"),
|
||||
|
||||
url(r'^contest/(?P<contest_id>\d+)/$', "contest.views.contest_page", name="contest_page"),
|
||||
url(r'^contest/(?P<contest_id>\d+)/problems/$', "contest.views.contest_problems_list_page",
|
||||
name="contest_problems_list_page"),
|
||||
url(r'^contest/(?P<contest_id>\d+)/submissions/$', "contest_submission.views.contest_problem_submissions_list_page",
|
||||
url(r'^contest/(?P<contest_id>\d+)/submissions/$', "contest.views.contest_problem_submissions_list_page",
|
||||
name="contest_problem_submissions_list_page"),
|
||||
url(r'^contest/(?P<contest_id>\d+)/submissions/(?P<page>\d+)/$',
|
||||
"contest_submission.views.contest_problem_submissions_list_page", name="contest_problem_submissions_list_page"),
|
||||
"contest.views.contest_problem_submissions_list_page", name="contest_problem_submissions_list_page"),
|
||||
|
||||
url(r'^contests/$', "contest.views.contest_list_page", name="contest_list_page"),
|
||||
url(r'^contests/(?P<page>\d+)/$', "contest.views.contest_list_page", name="contest_list_page"),
|
||||
@@ -128,6 +127,7 @@ urlpatterns = [
|
||||
|
||||
url(r'^account/settings/$', TemplateView.as_view(template_name="oj/account/settings.html"), name="account_setting_page"),
|
||||
url(r'^account/settings/avatar/$', TemplateView.as_view(template_name="oj/account/avatar.html"), name="avatar_settings_page"),
|
||||
url(r'^account/auth/$', "account.views.auth_page", name="auth_login_page"),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -17,12 +17,16 @@ def check_user_problem_permission(func):
|
||||
request = args[0]
|
||||
|
||||
# 这是在后台使用的url middleware 已经确保用户是登录状态的了
|
||||
if request.user.admin_type == SUPER_ADMIN:
|
||||
return func(*args, **kwargs)
|
||||
try:
|
||||
Problem.objects.get(id=request.data.get("problem_id", -1), created_by=request.user)
|
||||
problem = Problem.objects.get(id=request.data.get("id", -1))
|
||||
except Problem.DoesNotExist:
|
||||
return error_response(u"问题不存在")
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if request.user.admin_type == SUPER_ADMIN:
|
||||
return func(*args, **kwargs)
|
||||
else:
|
||||
if problem.created_by != request.user:
|
||||
return error_response(u"问题不存在")
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return check
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('problem', '0009_auto_20151008_1125'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='problem',
|
||||
name='last_update_time',
|
||||
field=models.DateTimeField(null=True, blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='problem',
|
||||
name='source',
|
||||
field=models.CharField(max_length=200, null=True, blank=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import utils.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('problem', '0010_auto_20151017_1226'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='problem',
|
||||
name='hint',
|
||||
field=utils.models.RichTextField(null=True, blank=True),
|
||||
),
|
||||
]
|
||||
+5
-4
@@ -26,11 +26,12 @@ class AbstractProblem(models.Model):
|
||||
# 测试用例id 这个id 可以用来拼接得到测试用例的文件存储位置
|
||||
test_case_id = models.CharField(max_length=40)
|
||||
# 提示
|
||||
hint = models.TextField(blank=True, null=True)
|
||||
hint = RichTextField(blank=True, null=True)
|
||||
# 创建时间
|
||||
create_time = models.DateTimeField(auto_now_add=True)
|
||||
# 最后更新时间
|
||||
# last_update_time = models.DateTimeField(auto_now=True)
|
||||
# 最后更新时间,不适用auto_now,因为本 model 里面的提交数量是变化的,导致每次 last_update_time 也更新了
|
||||
# 需要每次编辑后手动赋值
|
||||
last_update_time = models.DateTimeField(blank=True, null=True)
|
||||
# 这个题是谁创建的
|
||||
created_by = models.ForeignKey(User)
|
||||
# 时间限制 单位是毫秒
|
||||
@@ -63,4 +64,4 @@ class Problem(AbstractProblem):
|
||||
# 标签
|
||||
tags = models.ManyToManyField(ProblemTag)
|
||||
# 来源
|
||||
source = models.CharField(max_length=30, blank=True, null=True)
|
||||
source = models.CharField(max_length=200, blank=True, null=True)
|
||||
|
||||
@@ -25,13 +25,13 @@ class CreateProblemSerializer(serializers.Serializer):
|
||||
# [{"input": "1 1", "output": "2"}]
|
||||
samples = ProblemSampleSerializer()
|
||||
test_case_id = serializers.CharField(max_length=40)
|
||||
source = serializers.CharField(max_length=30, required=False, default=None)
|
||||
time_limit = serializers.IntegerField(min_value=1)
|
||||
memory_limit = serializers.IntegerField(min_value=1)
|
||||
difficulty = serializers.IntegerField()
|
||||
tags = serializers.ListField(child=serializers.CharField(max_length=10))
|
||||
hint = serializers.CharField(max_length=3000, allow_blank=True)
|
||||
visible = visible = serializers.BooleanField()
|
||||
source = serializers.CharField(max_length=100, required=False, default=None)
|
||||
visible = serializers.BooleanField()
|
||||
|
||||
|
||||
class ProblemTagSerializer(serializers.ModelSerializer):
|
||||
@@ -61,7 +61,7 @@ class EditProblemSerializer(serializers.Serializer):
|
||||
input_description = serializers.CharField(max_length=10000)
|
||||
output_description = serializers.CharField(max_length=10000)
|
||||
test_case_id = serializers.CharField(max_length=40)
|
||||
source = serializers.CharField(max_length=30)
|
||||
source = serializers.CharField(max_length=100)
|
||||
time_limit = serializers.IntegerField(min_value=1)
|
||||
memory_limit = serializers.IntegerField(min_value=1)
|
||||
difficulty = serializers.IntegerField()
|
||||
@@ -69,7 +69,3 @@ class EditProblemSerializer(serializers.Serializer):
|
||||
samples = ProblemSampleSerializer()
|
||||
hint = serializers.CharField(max_length=3000, allow_blank=True)
|
||||
visible = serializers.BooleanField()
|
||||
|
||||
|
||||
class CreateProblemTagSerializer(serializers.Serializer):
|
||||
name = serializers.CharField(max_length=10)
|
||||
|
||||
+19
-47
@@ -7,16 +7,15 @@ from django.core.urlresolvers import reverse
|
||||
from rest_framework.test import APITestCase, APIClient
|
||||
|
||||
from account.models import User, SUPER_ADMIN
|
||||
from account.tests import create_user
|
||||
from problem.models import Problem, ProblemTag
|
||||
|
||||
|
||||
class ProblemPageTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(username="test", admin_type=SUPER_ADMIN)
|
||||
self.user.set_password("testaa")
|
||||
self.user.save()
|
||||
self.client.login(username="test", password="testaa")
|
||||
self.user = create_user()
|
||||
self.client.login(username="test", password="111111")
|
||||
self.problem = Problem.objects.create(title="title1",
|
||||
description="description1",
|
||||
input_description="input1_description",
|
||||
@@ -31,11 +30,11 @@ class ProblemPageTest(TestCase):
|
||||
created_by=User.objects.get(username="test"))
|
||||
|
||||
def test_visit_problem_successfully(self):
|
||||
response = self.client.get('/problem/1/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.get('/problem/' + str(self.problem.id) + "/")
|
||||
self.assertTemplateUsed(response, "oj/problem/problem.html")
|
||||
|
||||
def test_problem_does_not_exist(self):
|
||||
response = self.client.get('/problem/3/')
|
||||
response = self.client.get('/problem/3000/')
|
||||
self.assertTemplateUsed(response, "utils/error.html")
|
||||
|
||||
|
||||
@@ -60,10 +59,8 @@ class ProblemAdminTest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.url = reverse("problem_admin_api")
|
||||
self.user = User.objects.create(username="test", admin_type=SUPER_ADMIN)
|
||||
self.user.set_password("testaa")
|
||||
self.user.save()
|
||||
self.client.login(username="test", password="testaa")
|
||||
self.user = create_user(admin_type=SUPER_ADMIN)
|
||||
self.client.login(username="test", password="111111")
|
||||
ProblemTag.objects.create(name="tag1")
|
||||
ProblemTag.objects.create(name="tag2")
|
||||
self.problem = Problem.objects.create(title="title1",
|
||||
@@ -77,15 +74,9 @@ class ProblemAdminTest(APITestCase):
|
||||
memory_limit=1000,
|
||||
difficulty=1,
|
||||
hint="hint1",
|
||||
created_by=User.objects.get(username="test"))
|
||||
created_by=self.user)
|
||||
|
||||
# 以下是发布题目的测试
|
||||
def test_invalid_format(self):
|
||||
data = {"title": "test1"}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_release_problem_successfully(self):
|
||||
def test_create_problem_successfully(self):
|
||||
data = {"title": "title2",
|
||||
"description": "description2",
|
||||
"input_description": "input_description2",
|
||||
@@ -97,30 +88,15 @@ class ProblemAdminTest(APITestCase):
|
||||
"memory_limit": "1000",
|
||||
"difficulty": "1",
|
||||
"hint": "hint1",
|
||||
"visible": True,
|
||||
"tags": [1]}
|
||||
response = self.client.post(self.url, data=json.dumps(data), content_type="application/json")
|
||||
self.assertEqual(response.data["code"], 0)
|
||||
|
||||
# 以下是编辑题目的测试
|
||||
def test_invalid_data(self):
|
||||
data = {"title": "test0"}
|
||||
response = self.client.put(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_problem_does_not_exist(self):
|
||||
tags = ProblemTag.objects.filter(id__in=[1])
|
||||
self.problem.tags.add(*tags)
|
||||
data = self._create_data(2, False, [1])
|
||||
response = self.client.put(self.url, data=json.dumps(data), content_type="application/json")
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"该题目不存在!"})
|
||||
|
||||
def test_edit_problem_successfully(self):
|
||||
tags = ProblemTag.objects.filter(id__in=[1])
|
||||
self.problem.tags.add(*tags)
|
||||
data = self._create_data(1, True, [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"]))
|
||||
data = self._create_data(self.problem.id, True, [1, 2])
|
||||
response = self.client.put(self.url, data=json.dumps(data), content_type="application/json")
|
||||
self.assertEqual(response.data["code"], 0)
|
||||
|
||||
@@ -139,11 +115,11 @@ class ProblemAdminTest(APITestCase):
|
||||
self.assertEqual(item["visible"], True)
|
||||
|
||||
def test_query_problem_does_not_exist(self):
|
||||
data = {"problem_id": 2}
|
||||
data = {"problem_id": 200}
|
||||
response = self.client.get(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"题目不存在"})
|
||||
|
||||
def test_query_problem_exists(self):
|
||||
def test_query_existed_problem(self):
|
||||
data = {"problem_id": self.problem.id}
|
||||
response = self.client.get(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 0)
|
||||
@@ -153,10 +129,8 @@ class ProblemTagAdminAPITest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.url = reverse('problem_tag_admin_api')
|
||||
self.user = User.objects.create(username="testx", admin_type=SUPER_ADMIN)
|
||||
self.user.set_password("testxx")
|
||||
self.user.save()
|
||||
self.client.login(username="testx", password="testxx")
|
||||
self.user = create_user(admin_type=SUPER_ADMIN)
|
||||
self.client.login(username="test", password="111111")
|
||||
ProblemTag.objects.create(name="tag1")
|
||||
|
||||
# 以下是返回所有的问题的标签
|
||||
@@ -168,10 +142,8 @@ class ProblemListPageTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.url = reverse('problem_list_page', kwargs={"page": 1})
|
||||
self.user = User.objects.create(username="test", admin_type=SUPER_ADMIN)
|
||||
self.user.set_password("testaa")
|
||||
self.user.save()
|
||||
self.client.login(username="test", password="testaa")
|
||||
self.user = create_user(admin_type=SUPER_ADMIN)
|
||||
self.client.login(username="test", password="111111")
|
||||
ProblemTag.objects.create(name="tag1")
|
||||
ProblemTag.objects.create(name="tag2")
|
||||
self.problem = Problem.objects.create(title="title1",
|
||||
@@ -185,7 +157,7 @@ class ProblemListPageTest(TestCase):
|
||||
memory_limit=1000,
|
||||
difficulty=1,
|
||||
hint="hint1",
|
||||
created_by=User.objects.get(username="test"))
|
||||
created_by=self.user)
|
||||
|
||||
def test_problemListPage_not_exist(self):
|
||||
response = self.client.get('/problems/999/')
|
||||
|
||||
+15
-14
@@ -9,21 +9,19 @@ import logging
|
||||
from django.shortcuts import render
|
||||
from django.db.models import Q, Count
|
||||
from django.core.paginator import Paginator
|
||||
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from django.utils.timezone import now
|
||||
from django.conf import settings
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from account.models import SUPER_ADMIN
|
||||
from account.decorators import super_admin_required
|
||||
from utils.shortcuts import (serializer_invalid_response, error_response,
|
||||
success_response, paginate, rand_str, error_page)
|
||||
from .serizalizers import (CreateProblemSerializer, EditProblemSerializer, ProblemSerializer,
|
||||
ProblemTagSerializer, CreateProblemTagSerializer)
|
||||
ProblemTagSerializer)
|
||||
from .models import Problem, ProblemTag
|
||||
from .decorators import check_user_problem_permission
|
||||
|
||||
|
||||
logger = logging.getLogger("app_info")
|
||||
|
||||
|
||||
@@ -59,7 +57,7 @@ class ProblemAdminAPIView(APIView):
|
||||
if serializer.is_valid():
|
||||
data = serializer.data
|
||||
try:
|
||||
Problem.objects.get(title=data["title"], description=data["description"])
|
||||
Problem.objects.get(title=data["title"])
|
||||
return error_response(u"添加失败,存在重复的题目")
|
||||
except Problem.DoesNotExist:
|
||||
pass
|
||||
@@ -110,6 +108,7 @@ class ProblemAdminAPIView(APIView):
|
||||
problem.samples = json.dumps(data["samples"])
|
||||
problem.hint = data["hint"]
|
||||
problem.visible = data["visible"]
|
||||
problem.last_update_time = now()
|
||||
|
||||
# 删除原有的标签的对应关系
|
||||
problem.tags.remove(*problem.tags.all())
|
||||
@@ -155,7 +154,7 @@ class ProblemAdminAPIView(APIView):
|
||||
keyword = request.GET.get("keyword", None)
|
||||
if keyword:
|
||||
problems = problems.filter(Q(title__contains=keyword) |
|
||||
Q(description__contains=keyword))
|
||||
Q(description__contains=keyword))
|
||||
|
||||
return paginate(request, problems, ProblemSerializer)
|
||||
|
||||
@@ -164,6 +163,7 @@ class TestCaseUploadAPIView(APIView):
|
||||
"""
|
||||
上传题目的测试用例
|
||||
"""
|
||||
|
||||
def _is_legal_test_case_file_name(self, file_name):
|
||||
# 正整数开头的 .in 或者.out 结尾的
|
||||
regex = r"^[1-9]\d*\.(in|out)$"
|
||||
@@ -183,17 +183,16 @@ class TestCaseUploadAPIView(APIView):
|
||||
except IOError as e:
|
||||
logger.error(e)
|
||||
return error_response(u"上传失败")
|
||||
|
||||
test_case_file = zipfile.ZipFile(tmp_zip, 'r')
|
||||
try:
|
||||
test_case_file = zipfile.ZipFile(tmp_zip, 'r')
|
||||
except Exception:
|
||||
return error_response(u"解压失败")
|
||||
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):
|
||||
@@ -251,7 +250,8 @@ class TestCaseUploadAPIView(APIView):
|
||||
"striped_output_md5": striped_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))
|
||||
with open(test_case_dir + "info", "w") as f:
|
||||
f.write(json.dumps(file_info))
|
||||
|
||||
return success_response({"test_case_id": problem_test_dir,
|
||||
"file_list": {"input": l[0::2],
|
||||
@@ -311,7 +311,8 @@ def problem_list_page(request, page=1):
|
||||
pass
|
||||
|
||||
# 右侧标签列表 按照关联的题目的数量排序 排除题目数量为0的
|
||||
tags = ProblemTag.objects.annotate(problem_number=Count("problem")).filter(problem_number__gt=0).order_by("-problem_number")
|
||||
tags = ProblemTag.objects.annotate(problem_number=Count("problem")).filter(problem_number__gt=0).order_by(
|
||||
"-problem_number")
|
||||
|
||||
return render(request, "oj/problem/problem_list.html",
|
||||
{"problems": current_page, "page": int(page),
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -1,5 +1,17 @@
|
||||
@font-face{
|
||||
font-family: 'source_code_pro';
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-stretch: normal;
|
||||
src: url('/static/css/fonts/source_code_pro/EOT/SourceCodePro-Regular.eot') format('embedded-opentype'),
|
||||
url('/static/css/fonts/source_code_pro/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2') format('woff2'),
|
||||
url('/static/css/fonts/source_code_pro/WOFF/OTF/SourceCodePro-Regular.otf.woff') format('woff'),
|
||||
url('/static/css/fonts/source_code_pro/OTF/SourceCodePro-Regular.otf') format('opentype'),
|
||||
url('/static/css/fonts/source_code_pro/TTF/SourceCodePro-Regular.ttf') format('truetype');
|
||||
}
|
||||
|
||||
body, button, input, select, textarea, h1, h2, h3, h4, h5, h6 {
|
||||
font-family: Georgia, STHeiti, "Microsoft Yahei", SimSun, "Droid Sans";
|
||||
font-family: "source_code_pro", Georgia, STHeiti, "Microsoft Yahei", SimSun, "Droid Sans";
|
||||
}
|
||||
|
||||
html {
|
||||
@@ -39,12 +51,14 @@ label {
|
||||
}
|
||||
|
||||
|
||||
pre {
|
||||
font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
|
||||
pre, code {
|
||||
font-family: "source_code_pro";
|
||||
background-color: white;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.CodeMirror-code{
|
||||
font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
|
||||
font-family: "source_code_pro";
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require(["jquery", "avalon", "bootstrap"], function ($, avalon) {
|
||||
require(["jquery", "avalon", "csrfToken", "bsAlert", "bootstrap"], function ($, avalon, csrfTokenHeader, bsAlert) {
|
||||
|
||||
avalon.ready(function () {
|
||||
|
||||
@@ -109,24 +109,10 @@ require(["jquery", "avalon", "bootstrap"], function ($, avalon) {
|
||||
vm.template_url = "template/group/group_detail.html";
|
||||
});
|
||||
|
||||
vm.$watch("showEditProblemPage", function (problemId) {
|
||||
vm.problemId = problemId;
|
||||
vm.template_url = "template/problem/edit_problem.html";
|
||||
});
|
||||
|
||||
vm.$watch("showProblemListPage", function () {
|
||||
vm.template_url = "template/problem/problem_list.html";
|
||||
});
|
||||
|
||||
vm.$watch("showGroupListPage", function () {
|
||||
vm.template_url = "template/group/group.html";
|
||||
});
|
||||
|
||||
vm.$watch("showProblemSubmissionPage", function (problemId) {
|
||||
vm.problemId = problemId;
|
||||
vm.template_url = "template/problem/submission_list.html";
|
||||
});
|
||||
|
||||
vm.$watch("showContestProblemPage", function (problemId, contestId, contestMode) {
|
||||
vm.$problemId = problemId;
|
||||
vm.$contestId = contestId;
|
||||
@@ -155,7 +141,15 @@ require(["jquery", "avalon", "bootstrap"], function ($, avalon) {
|
||||
show_template("template/" + hash + ".html");
|
||||
}
|
||||
};
|
||||
setTimeout(function(){li_active("#li-" + hash.replace("/", "-"));}, 500)
|
||||
setTimeout(function(){li_active("#li-" + hash.replace("/", "-"));}, 500);
|
||||
|
||||
$.ajaxSetup({
|
||||
beforeSend: csrfTokenHeader,
|
||||
dataType: "json",
|
||||
error: function(){
|
||||
bsAlert("请求失败");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,112 +1,73 @@
|
||||
require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
|
||||
require(["jquery", "avalon", "csrfToken", "bsAlert", "validator", "pager", "editorComponent"],
|
||||
function ($, avalon, csrfTokenHeader, bsAlert, editor) {
|
||||
avalon.ready(function () {
|
||||
|
||||
var createAnnouncementEditor = editor("#create-announcement-editor");
|
||||
var editAnnouncementEditor = editor("#edit-announcement-editor");
|
||||
if (avalon.vmodels.announcement){
|
||||
var vm = avalon.vmodels.announcement;
|
||||
announcementList = [];
|
||||
}
|
||||
else {
|
||||
|
||||
var vm = avalon.define({
|
||||
$id: "announcement",
|
||||
//通用变量
|
||||
announcementList: [], // 公告列表数据项
|
||||
previousPage: 0, // 之前的页数
|
||||
nextPage: 0, // 之后的页数
|
||||
page: 1, // 当前页数
|
||||
editingAnnouncementId: 0, // 正在编辑的公告的ID, 为零说明未在编辑
|
||||
totalPage: 1, // 总页数
|
||||
showVisibleOnly: false, //仅显示可见公告
|
||||
// 编辑
|
||||
announcementList: [],
|
||||
isEditing: false,
|
||||
showVisibleOnly: false,
|
||||
|
||||
//编辑器同步变量
|
||||
announcementId: -1,
|
||||
newTitle: "",
|
||||
announcementVisible: 0,
|
||||
showGlobalViewRadio: true,
|
||||
isGlobal: true,
|
||||
allGroups: [],
|
||||
getNext: function () {
|
||||
if (!vm.nextPage)
|
||||
return;
|
||||
getPageData(vm.page + 1);
|
||||
},
|
||||
getPrevious: function () {
|
||||
if (!vm.previousPage)
|
||||
return;
|
||||
getPageData(vm.page - 1);
|
||||
},
|
||||
getBtnClass: function (btnType) {
|
||||
if (btnType == "next") {
|
||||
return vm.nextPage ? "btn btn-primary" : "btn btn-primary disabled";
|
||||
}
|
||||
else {
|
||||
return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled";
|
||||
announcementVisible: false,
|
||||
|
||||
pager: {
|
||||
getPage: function(page){
|
||||
getPage(page);
|
||||
}
|
||||
},
|
||||
|
||||
createAnnouncementEditor: {
|
||||
editorId: "create-announcement-editor",
|
||||
placeholder: "公告内容"
|
||||
},
|
||||
|
||||
editAnnouncementEditor: {
|
||||
editorId: "edit-announcement-editor",
|
||||
placeholder: "公告内容"
|
||||
},
|
||||
|
||||
editAnnouncement: function (announcement) {
|
||||
vm.newTitle = announcement.title;
|
||||
editAnnouncementEditor.setValue(announcement.content);
|
||||
vm.announcementId = announcement.id;
|
||||
avalon.vmodels.editAnnouncementEditor.content = announcement.content;
|
||||
vm.announcementVisible = announcement.visible;
|
||||
if (vm.editingAnnouncementId == announcement.id)
|
||||
vm.editingAnnouncementId = 0;
|
||||
else
|
||||
vm.editingAnnouncementId = announcement.id;
|
||||
vm.isGlobal = announcement.is_global;
|
||||
for (var i = 0; i < announcement.groups.length; i++) {
|
||||
for (var j = 0; j < vm.allGroups.length; j++) {
|
||||
if (announcement.groups[i] == vm.allGroups[j].id) {
|
||||
vm.allGroups[j].isSelected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
editAnnouncementEditor.focus();
|
||||
vm.isEditing = true;
|
||||
},
|
||||
cancelEdit: function () {
|
||||
vm.editingAnnouncementId = 0;
|
||||
vm.isEditing = false;
|
||||
},
|
||||
submitChange: function () {
|
||||
var title = vm.newTitle;
|
||||
var content = editAnnouncementEditor.getValue();
|
||||
var content = avalon.vmodels.editAnnouncementEditor.content;
|
||||
|
||||
if (content == "" || title == "") {
|
||||
bsAlert("标题和内容都不能为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
var selectedGroups = [];
|
||||
if (!vm.isGlobal) {
|
||||
for (var i = 0; i < vm.allGroups.length; i++) {
|
||||
if (vm.allGroups[i].isSelected) {
|
||||
selectedGroups.push(vm.allGroups[i].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!vm.isGlobal && !selectedGroups.length) {
|
||||
bsAlert("请至少选择一个小组");
|
||||
return false;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
beforeSend: csrfTokenHeader,
|
||||
url: "/api/admin/announcement/",
|
||||
contentType: "application/json;charset=UTF-8",
|
||||
dataType: "json",
|
||||
method: "put",
|
||||
data: JSON.stringify({
|
||||
id: vm.editingAnnouncementId,
|
||||
id: vm.announcementId,
|
||||
title: title,
|
||||
content: content,
|
||||
visible: vm.announcementVisible,
|
||||
is_global: vm.isGlobal,
|
||||
groups: selectedGroups
|
||||
visible: vm.announcementVisible
|
||||
}),
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
bsAlert("修改成功");
|
||||
vm.editingAnnouncementId = 0;
|
||||
getPageData(1);
|
||||
vm.isEditing = false;
|
||||
getPage(1);
|
||||
}
|
||||
else {
|
||||
bsAlert(data.data);
|
||||
@@ -116,105 +77,47 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
vm.$watch("showVisibleOnly", function () {
|
||||
getPageData(1);
|
||||
getPage(1);
|
||||
avalon.vmodels.announcementPager.currentPage = 1;
|
||||
});
|
||||
}
|
||||
|
||||
getPageData(1);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/user/",
|
||||
method: "get",
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
var admin_type = data.data.admin_type;
|
||||
if (data.data.admin_type == 1) {
|
||||
vm.isGlobal = false;
|
||||
vm.showGlobalViewRadio = false;
|
||||
|
||||
}
|
||||
}
|
||||
$.ajax({
|
||||
url: "/api/admin/group/",
|
||||
method: "get",
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
if (!data.data.length) {
|
||||
if (admin_type != 2)
|
||||
bsAlert("您的用户权限只能创建组内公告,但是您还没有创建过小组");
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < data.data.length; i++) {
|
||||
var item = data.data[i];
|
||||
item["isSelected"] = false;
|
||||
vm.allGroups.push(item);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bsAlert(data.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function getPageData(page) {
|
||||
var url = "/api/admin/announcement/?paging=true&page=" + page + "&page_size=10";
|
||||
function getPage(page) {
|
||||
var url = "/api/admin/announcement/?paging=true&page=" + page + "&page_size=20";
|
||||
if (vm.showVisibleOnly)
|
||||
url += "&visible=true";
|
||||
$.ajax({
|
||||
url: url,
|
||||
dataType: "json",
|
||||
method: "get",
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
vm.announcementList = data.data.results;
|
||||
vm.totalPage = data.data.total_page;
|
||||
vm.previousPage = data.data.previous_page;
|
||||
vm.nextPage = data.data.next_page;
|
||||
vm.page = page;
|
||||
avalon.vmodels.announcementPager.totalPage = data.data.total_page;
|
||||
}
|
||||
else {
|
||||
bs_alert(data.data);
|
||||
bsAlert(data.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//新建公告表单验证与数据提交
|
||||
$("#announcement-form").validator().on('submit', function (e) {
|
||||
$("#announcement-form").validator().on('submit', function (e) {
|
||||
if (!e.isDefaultPrevented()) {
|
||||
var title = $("#title").val();
|
||||
var content = createAnnouncementEditor.getValue();
|
||||
var content = avalon.vmodels.createAnnouncementEditor.content;
|
||||
if (content == "") {
|
||||
bsAlert("请填写公告内容");
|
||||
return false;
|
||||
}
|
||||
var selectedGroups = [];
|
||||
if (!vm.isGlobal) {
|
||||
for (var i = 0; i < vm.allGroups.length; i++) {
|
||||
if (vm.allGroups[i].isSelected) {
|
||||
selectedGroups.push(vm.allGroups[i].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!vm.isGlobal && !selectedGroups.length) {
|
||||
bsAlert("请至少选择一个小组");
|
||||
return false;
|
||||
}
|
||||
$.ajax({
|
||||
beforeSend: csrfTokenHeader,
|
||||
url: "/api/admin/announcement/",
|
||||
contentType: "application/json;charset=UTF-8",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify({
|
||||
title: title,
|
||||
content: content,
|
||||
is_global: vm.isGlobal,
|
||||
groups: selectedGroups
|
||||
content: content
|
||||
}),
|
||||
dataType: "json",
|
||||
method: "post",
|
||||
@@ -222,8 +125,8 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
|
||||
if (!data.code) {
|
||||
bsAlert("提交成功!");
|
||||
$("#title").val("");
|
||||
createAnnouncementEditor.setValue("");
|
||||
getPageData(1);
|
||||
avalon.vmodels.createAnnouncementEditor.content = "";
|
||||
getPage(1);
|
||||
} else {
|
||||
bsAlert(data.data);
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ require(["jquery", "chart"], function ($, Chart) {
|
||||
$("#clear-chart-data").click(function(){
|
||||
for(var i = 0;i < dataCounter;i++) {
|
||||
chart.removeData();
|
||||
dataCounter = 0;
|
||||
}
|
||||
dataCounter = 0;
|
||||
});
|
||||
|
||||
var intervalId = setInterval(getMonitorData, 3000);
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagEditor", "validator", "jqueryUI"],
|
||||
require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagEditor", "validator", "jqueryUI", "editorComponent"],
|
||||
function ($, avalon, editor, uploader, bsAlert, csrfTokenHeader) {
|
||||
avalon.ready(function () {
|
||||
|
||||
$("#add-problem-form").validator()
|
||||
.on('submit', function (e) {
|
||||
if (!e.isDefaultPrevented()){
|
||||
if (!e.isDefaultPrevented()) {
|
||||
if (vm.testCaseId == "") {
|
||||
bsAlert("你还没有上传测试数据!");
|
||||
return false;
|
||||
}
|
||||
if (vm.description == "") {
|
||||
if (avalon.vmodels.problemDescriptionEditor.content == "") {
|
||||
bsAlert("题目描述不能为空!");
|
||||
return false;
|
||||
}
|
||||
if (vm.timeLimit < 100 || vm.timeLimit > 5000) {
|
||||
bsAlert("保证时间限制是一个100-5000的合法整数");
|
||||
if (vm.timeLimit < 30 || vm.timeLimit > 5000) {
|
||||
bsAlert("保证时间限制是一个30-5000的合法整数");
|
||||
return false;
|
||||
}
|
||||
if (vm.samples.length == 0) {
|
||||
@@ -35,12 +35,12 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
|
||||
var ajaxData = {
|
||||
id: avalon.vmodels.admin.problemId,
|
||||
title: vm.title,
|
||||
description: vm.description,
|
||||
description: avalon.vmodels.problemDescriptionEditor.content,
|
||||
time_limit: vm.timeLimit,
|
||||
memory_limit: vm.memoryLimit,
|
||||
samples: [],
|
||||
test_case_id: vm.testCaseId,
|
||||
hint: vm.hint,
|
||||
hint: avalon.vmodels.problemHintEditor.content,
|
||||
source: vm.source,
|
||||
visible: vm.visible,
|
||||
tags: tags,
|
||||
@@ -50,7 +50,10 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
|
||||
};
|
||||
|
||||
for (var i = 0; i < vm.samples.$model.length; i++) {
|
||||
ajaxData.samples.push({input: vm.samples.$model[i].input, output: vm.samples.$model[i].output});
|
||||
ajaxData.samples.push({
|
||||
input: vm.samples.$model[i].input,
|
||||
output: vm.samples.$model[i].output
|
||||
});
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
@@ -74,56 +77,18 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
|
||||
}
|
||||
});
|
||||
|
||||
var testCaseUploader = uploader("#testCaseFile", "/api/admin/test_case_upload/", function (file, response) {
|
||||
if (response.code)
|
||||
bsAlert(response.data);
|
||||
else {
|
||||
vm.testCaseId = response.data.test_case_id;
|
||||
vm.uploadSuccess = true;
|
||||
vm.testCaseList = [];
|
||||
for (var i = 0; i < response.data.file_list.input.length; i++) {
|
||||
vm.testCaseList.push({
|
||||
input: response.data.file_list.input[i],
|
||||
output: response.data.file_list.output[i]
|
||||
});
|
||||
}
|
||||
bsAlert("测试数据添加成功!共添加" + vm.testCaseList.length + "组测试数据");
|
||||
}
|
||||
});
|
||||
|
||||
var hintEditor = editor("#hint");
|
||||
var problemDescription = editor("#problemDescription");
|
||||
if (avalon.vmodels.addProblem) {
|
||||
var vm = avalon.vmodels.addProblem;
|
||||
vm.title = "";
|
||||
vm.description = "";
|
||||
vm.timeLimit = 1000;
|
||||
vm.memoryLimit = 256;
|
||||
vm.samples = [{input: "", output: "", "visible": true}];
|
||||
vm.hint = "";
|
||||
vm.visible = true;
|
||||
vm.difficulty = 0;
|
||||
vm.tags = [];
|
||||
vm.inputDescription = "";
|
||||
vm.outputDescription = "";
|
||||
vm.testCaseId = "";
|
||||
vm.testCaseList = [];
|
||||
vm.uploadSuccess = false;
|
||||
vm.source = "";
|
||||
hintEditor.setValue("");
|
||||
problemDescription.setValue("");
|
||||
}
|
||||
else
|
||||
var vm = avalon.define({
|
||||
$id: "addProblem",
|
||||
title: "",
|
||||
description: "",
|
||||
timeLimit: 1000,
|
||||
memoryLimit: 256,
|
||||
memoryLimit: 128,
|
||||
samples: [{input: "", output: "", "visible": true}],
|
||||
hint: "",
|
||||
visible: true,
|
||||
difficulty: 0,
|
||||
difficulty: "1",
|
||||
tags: [],
|
||||
inputDescription: "",
|
||||
outputDescription: "",
|
||||
@@ -131,6 +96,17 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
|
||||
testCaseList: [],
|
||||
uploadSuccess: false,
|
||||
source: "",
|
||||
uploadProgress: 0,
|
||||
|
||||
problemDescriptionEditor: {
|
||||
editorId: "problem-description-editor",
|
||||
placeholder: "题目描述"
|
||||
},
|
||||
problemHintEditor: {
|
||||
editorId: "problem-hint-editor",
|
||||
placeholder: "提示"
|
||||
},
|
||||
|
||||
addSample: function () {
|
||||
vm.samples.push({input: "", output: "", "visible": true});
|
||||
},
|
||||
@@ -149,6 +125,29 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
|
||||
}
|
||||
});
|
||||
|
||||
var testCaseUploader = uploader("#testCaseFile", "/api/admin/test_case_upload/",
|
||||
function (file, response) {
|
||||
if (response.code) {
|
||||
vm.uploadProgress = 0;
|
||||
bsAlert(response.data);
|
||||
}
|
||||
else {
|
||||
vm.testCaseId = response.data.test_case_id;
|
||||
vm.uploadSuccess = true;
|
||||
vm.testCaseList = [];
|
||||
for (var i = 0; i < response.data.file_list.input.length; i++) {
|
||||
vm.testCaseList.push({
|
||||
input: response.data.file_list.input[i],
|
||||
output: response.data.file_list.output[i]
|
||||
});
|
||||
}
|
||||
bsAlert("测试数据添加成功!共添加" + vm.testCaseList.length + "组测试数据");
|
||||
}
|
||||
},
|
||||
function (file, percentage) {
|
||||
vm.uploadProgress = percentage;
|
||||
});
|
||||
|
||||
var tagAutoCompleteList = [];
|
||||
|
||||
$.ajax({
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagEditor", "validator", "jqueryUI"],
|
||||
require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagEditor", "validator", "jqueryUI", "editorComponent"],
|
||||
function ($, avalon, editor, uploader, bsAlert, csrfTokenHeader) {
|
||||
|
||||
avalon.ready(function () {
|
||||
|
||||
$("#edit-problem-form").validator()
|
||||
.on('submit', function (e) {
|
||||
if (!e.isDefaultPrevented()){
|
||||
if (!e.isDefaultPrevented()) {
|
||||
if (vm.testCaseId == "") {
|
||||
bsAlert("你还没有上传测试数据!");
|
||||
return false;
|
||||
}
|
||||
if (vm.description == "") {
|
||||
if (avalon.vmodels.problemDescriptionEditor.content == "") {
|
||||
bsAlert("题目描述不能为空!");
|
||||
return false;
|
||||
}
|
||||
@@ -36,12 +36,12 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
|
||||
var ajaxData = {
|
||||
id: avalon.vmodels.admin.problemId,
|
||||
title: vm.title,
|
||||
description: vm.description,
|
||||
description: avalon.vmodels.problemDescriptionEditor.content,
|
||||
time_limit: vm.timeLimit,
|
||||
memory_limit: vm.memoryLimit,
|
||||
samples: [],
|
||||
test_case_id: vm.testCaseId,
|
||||
hint: vm.hint,
|
||||
hint: avalon.vmodels.problemHintEditor.content,
|
||||
source: vm.source,
|
||||
visible: vm.visible,
|
||||
tags: tags,
|
||||
@@ -51,7 +51,10 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
|
||||
};
|
||||
|
||||
for (var i = 0; i < vm.samples.$model.length; i++) {
|
||||
ajaxData.samples.push({input: vm.samples.$model[i].input, output: vm.samples.$model[i].output});
|
||||
ajaxData.samples.push({
|
||||
input: vm.samples.$model[i].input,
|
||||
output: vm.samples.$model[i].output
|
||||
});
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
@@ -75,78 +78,78 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (avalon.vmodels.editProblem) {
|
||||
var vm = avalon.vmodels.editProblem;
|
||||
vm.title= "",
|
||||
vm.description= "";
|
||||
vm.timeLimit= -1;
|
||||
vm.memoryLimit= -1;
|
||||
vm.samples= [];
|
||||
vm.hint= "";
|
||||
vm.visible= true;
|
||||
vm.difficulty= 0;
|
||||
vm.inputDescription= "";
|
||||
vm.outputDescription= "";
|
||||
vm.testCaseIdd= "";
|
||||
vm.uploadSuccess= false;
|
||||
vm.source= "";
|
||||
vm.testCaseList= [];
|
||||
}
|
||||
else
|
||||
var vm = avalon.define({
|
||||
$id: "editProblem",
|
||||
title: "",
|
||||
description: "",
|
||||
timeLimit: -1,
|
||||
memoryLimit: -1,
|
||||
samples: [],
|
||||
hint: "",
|
||||
visible: true,
|
||||
difficulty: 0,
|
||||
inputDescription: "",
|
||||
outputDescription: "",
|
||||
testCaseIdd: "",
|
||||
uploadSuccess: false,
|
||||
source: "",
|
||||
testCaseList: [],
|
||||
addSample: function () {
|
||||
vm.samples.push({input: "", output: "", "visible": true});
|
||||
},
|
||||
delSample: function (sample) {
|
||||
if (confirm("你确定要删除么?")) {
|
||||
vm.samples.remove(sample);
|
||||
if (avalon.vmodels.editProblem) {
|
||||
var vm = avalon.vmodels.editProblem;
|
||||
}
|
||||
else
|
||||
var vm = avalon.define({
|
||||
$id: "editProblem",
|
||||
title: "",
|
||||
timeLimit: -1,
|
||||
memoryLimit: -1,
|
||||
samples: [],
|
||||
visible: true,
|
||||
difficulty: "1",
|
||||
inputDescription: "",
|
||||
outputDescription: "",
|
||||
testCaseIdd: "",
|
||||
uploadSuccess: false,
|
||||
source: "",
|
||||
testCaseList: [],
|
||||
uploadProgress: 0,
|
||||
|
||||
problemDescriptionEditor: {
|
||||
editorId: "problem-description-editor",
|
||||
placeholder: "题目描述"
|
||||
},
|
||||
problemHintEditor: {
|
||||
editorId: "problem-hint-editor",
|
||||
placeholder: "提示"
|
||||
},
|
||||
|
||||
addSample: function () {
|
||||
vm.samples.push({input: "", output: "", "visible": true});
|
||||
},
|
||||
delSample: function (sample) {
|
||||
if (confirm("你确定要删除么?")) {
|
||||
vm.samples.remove(sample);
|
||||
}
|
||||
},
|
||||
toggleSample: function (sample) {
|
||||
sample.visible = !sample.visible;
|
||||
},
|
||||
getBtnContent: function (item) {
|
||||
if (item.visible)
|
||||
return "折叠";
|
||||
return "展开";
|
||||
},
|
||||
showProblemListPage: function () {
|
||||
avalon.vmodels.admin.template_url = "template/problem/problem_list.html";
|
||||
}
|
||||
});
|
||||
var testCaseUploader = uploader("#testCaseFile", "/api/admin/test_case_upload/",
|
||||
function (file, response) {
|
||||
if (response.code) {
|
||||
vm.uploadProgress = 0;
|
||||
bsAlert(response.data);
|
||||
}
|
||||
else {
|
||||
vm.testCaseId = response.data.test_case_id;
|
||||
vm.uploadSuccess = true;
|
||||
vm.testCaseList = [];
|
||||
for (var i = 0; i < response.data.file_list.input.length; i++) {
|
||||
vm.testCaseList.push({
|
||||
input: response.data.file_list.input[i],
|
||||
output: response.data.file_list.output[i]
|
||||
});
|
||||
}
|
||||
bsAlert("测试数据添加成功!共添加" + vm.testCaseList.length + "组测试数据");
|
||||
}
|
||||
},
|
||||
toggleSample: function (sample) {
|
||||
sample.visible = !sample.visible;
|
||||
},
|
||||
getBtnContent: function (item) {
|
||||
if (item.visible)
|
||||
return "折叠";
|
||||
return "展开";
|
||||
},
|
||||
showProblemListPage: function(){
|
||||
vm.$fire("up!showProblemListPage");
|
||||
function (file, percentage) {
|
||||
vm.uploadProgress = percentage;
|
||||
}
|
||||
});
|
||||
var hintEditor = editor("#hint");
|
||||
var descriptionEditor = editor("#problemDescription");
|
||||
var testCaseUploader = uploader("#testCaseFile", "/api/admin/test_case_upload/", function (file, response) {
|
||||
if (response.code)
|
||||
bsAlert(response.data);
|
||||
else {
|
||||
vm.testCaseId = response.data.test_case_id;
|
||||
vm.uploadSuccess = true;
|
||||
vm.testCaseList = [];
|
||||
for (var i = 0; i < response.data.file_list.input.length; i++) {
|
||||
vm.testCaseList.push({
|
||||
input: response.data.file_list.input[i],
|
||||
output: response.data.file_list.output[i]
|
||||
});
|
||||
}
|
||||
bsAlert("测试数据添加成功!共添加" + vm.testCaseList.length + "组测试数据");
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/admin/problem/?problem_id=" + avalon.vmodels.admin.problemId,
|
||||
@@ -159,7 +162,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
|
||||
else {
|
||||
var problem = data.data;
|
||||
vm.title = problem.title;
|
||||
vm.description = problem.description;
|
||||
avalon.vmodels.problemDescriptionEditor.content = problem.description;
|
||||
vm.timeLimit = problem.time_limit;
|
||||
vm.memoryLimit = problem.memory_limit;
|
||||
for (var i = 0; i < problem.samples.length; i++) {
|
||||
@@ -169,7 +172,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
|
||||
visible: false
|
||||
})
|
||||
}
|
||||
vm.hint = problem.hint;
|
||||
avalon.vmodels.problemHintEditor.content = problem.hint;
|
||||
vm.visible = problem.visible;
|
||||
vm.difficulty = problem.difficulty;
|
||||
vm.inputDescription = problem.input_description;
|
||||
@@ -177,8 +180,6 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
|
||||
vm.testCaseId = problem.test_case_id;
|
||||
vm.source = problem.source;
|
||||
var problemTags = problem.tags;
|
||||
hintEditor.setValue(vm.hint);
|
||||
descriptionEditor.setValue(vm.description);
|
||||
$.ajax({
|
||||
url: "/api/admin/tag/",
|
||||
dataType: "json",
|
||||
|
||||
@@ -1,54 +1,44 @@
|
||||
require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfTokenHeader, bsAlert) {
|
||||
require(["jquery", "avalon", "csrfToken", "bsAlert", "pager"], function ($, avalon, csrfTokenHeader, bsAlert) {
|
||||
|
||||
avalon.ready(function () {
|
||||
if(avalon.vmodels.problemList){
|
||||
vm = avalon.vmodels.problemList;
|
||||
problemList = [];
|
||||
}
|
||||
else {
|
||||
var vm = avalon.define({
|
||||
$id: "problemList",
|
||||
problemList: [],
|
||||
previousPage: 0,
|
||||
nextPage: 0,
|
||||
page: 1,
|
||||
totalPage: 1,
|
||||
|
||||
keyword: "",
|
||||
showVisibleOnly: false,
|
||||
getNext: function () {
|
||||
if (!vm.nextPage)
|
||||
return;
|
||||
getPageData(vm.page + 1);
|
||||
},
|
||||
getPrevious: function () {
|
||||
if (!vm.previousPage)
|
||||
return;
|
||||
getPageData(vm.page - 1);
|
||||
},
|
||||
getBtnClass: function (btn) {
|
||||
if (btn == "next") {
|
||||
return vm.nextPage ? "btn btn-primary" : "btn btn-primary disabled";
|
||||
}
|
||||
else {
|
||||
return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled";
|
||||
|
||||
pager: {
|
||||
getPage: function (page) {
|
||||
getPage(page);
|
||||
}
|
||||
},
|
||||
getPage: function (page_index) {
|
||||
getPageData(page_index);
|
||||
},
|
||||
|
||||
showEditProblemPage: function (problemId) {
|
||||
vm.$fire("up!showEditProblemPage", problemId);
|
||||
avalon.vmodels.admin.problemId = problemId;
|
||||
avalon.vmodels.admin.template_url = "template/problem/edit_problem.html";
|
||||
},
|
||||
showProblemSubmissionPage: function(problemId){
|
||||
vm.$fire("up!showProblemSubmissionPage", problemId);
|
||||
avalon.vmodels.admin.problemId = problemId;
|
||||
avalon.vmodels.admin.template_url = "template/problem/submission_list.html";
|
||||
},
|
||||
|
||||
search: function(){
|
||||
getPage(1);
|
||||
avalon.vmodels.problemPager.currentPage = 1;
|
||||
}
|
||||
});
|
||||
vm.$watch("showVisibleOnly", function () {
|
||||
getPageData(1);
|
||||
getPage(1);
|
||||
avalon.vmodels.problemPager.currentPage = 1;
|
||||
});
|
||||
}
|
||||
getPageData(1);
|
||||
function getPageData(page) {
|
||||
|
||||
function getPage(page) {
|
||||
var url = "/api/admin/problem/?paging=true&page=" + page + "&page_size=10";
|
||||
if (vm.keyword != "")
|
||||
url += "&keyword=" + vm.keyword;
|
||||
@@ -61,10 +51,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
vm.problemList = data.data.results;
|
||||
vm.totalPage = data.data.total_page;
|
||||
vm.previousPage = data.data.previous_page;
|
||||
vm.nextPage = data.data.next_page;
|
||||
vm.page = page;
|
||||
avalon.vmodels.problemPager.totalPage = data.data.total_page;
|
||||
}
|
||||
else {
|
||||
bsAlert(data.data);
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfTokenHeader, bsAlert) {
|
||||
require(["jquery", "avalon", "csrfToken", "bsAlert", "pager"], function ($, avalon, csrfTokenHeader, bsAlert) {
|
||||
|
||||
avalon.ready(function () {
|
||||
|
||||
if (avalon.vmodels.submissionList){
|
||||
if (avalon.vmodels.submissionList) {
|
||||
var vm = avalon.vmodels.submissionList;
|
||||
}
|
||||
else {
|
||||
|
||||
var vm = avalon.define({
|
||||
$id: "submissionList",
|
||||
submissionList: [],
|
||||
@@ -14,65 +13,43 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
|
||||
nextPage: 0,
|
||||
page: 1,
|
||||
totalPage: 1,
|
||||
results : {
|
||||
0: "Accepted",
|
||||
1: "Runtime Error",
|
||||
2: "Time Limit Exceeded",
|
||||
3: "Memory Limit Exceeded",
|
||||
4: "Compile Error",
|
||||
5: "Format Error",
|
||||
6: "Wrong Answer",
|
||||
7: "System Error",
|
||||
8: "Waiting"
|
||||
},
|
||||
getNext: function () {
|
||||
if (!vm.nextPage)
|
||||
return;
|
||||
getPageData(vm.page + 1);
|
||||
results: {
|
||||
0: "Accepted",
|
||||
1: "Runtime Error",
|
||||
2: "Time Limit Exceeded",
|
||||
3: "Memory Limit Exceeded",
|
||||
4: "Compile Error",
|
||||
5: "Format Error",
|
||||
6: "Wrong Answer",
|
||||
7: "System Error",
|
||||
8: "Waiting"
|
||||
},
|
||||
getPrevious: function () {
|
||||
if (!vm.previousPage)
|
||||
return;
|
||||
getPageData(vm.page - 1);
|
||||
},
|
||||
getBtnClass: function (btn) {
|
||||
if (btn == "next") {
|
||||
return vm.nextPage ? "btn btn-primary" : "btn btn-primary disabled";
|
||||
}
|
||||
else {
|
||||
return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled";
|
||||
pager: {
|
||||
getPage: function (page) {
|
||||
getPage(page);
|
||||
}
|
||||
},
|
||||
getPage: function (page_index) {
|
||||
getPageData(page_index);
|
||||
showProblemListPage: function () {
|
||||
avalon.vmodels.admin.template_url = "template/problem/problem_list.html";
|
||||
},
|
||||
showSubmissionDetailPage: function (submissionId) {
|
||||
|
||||
},
|
||||
showProblemListPage: function(){
|
||||
vm.$fire("up!showProblemListPage");
|
||||
},
|
||||
rejudge: function(submission_id){
|
||||
rejudge: function (submission_id) {
|
||||
$.ajax({
|
||||
beforeSend: csrfTokenHeader,
|
||||
url: "/api/admin/rejudge/",
|
||||
method: "post",
|
||||
data: {"submission_id": submission_id},
|
||||
success: function(data){
|
||||
if(!data.code){
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
bsAlert("重判任务提交成功");
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getPageData(1);
|
||||
|
||||
function getPageData(page) {
|
||||
var url = "/api/admin/submission/?paging=true&page=" + page + "&page_size=10&problem_id=" + avalon.vmodels.admin.problemId;
|
||||
function getPage(page) {
|
||||
var url = "/api/admin/submission/?paging=true&page=" + page + "&page_size=20&problem_id=" + avalon.vmodels.admin.problemId;
|
||||
$.ajax({
|
||||
url: url,
|
||||
dataType: "json",
|
||||
@@ -84,6 +61,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
|
||||
vm.previousPage = data.data.previous_page;
|
||||
vm.nextPage = data.data.next_page;
|
||||
vm.page = page;
|
||||
avalon.vmodels.submissionsListPager.totalPage = data.data.total_page;
|
||||
}
|
||||
else {
|
||||
bsAlert(data.data);
|
||||
|
||||
@@ -1,133 +1,106 @@
|
||||
require(["jquery", "avalon", "csrfToken", "bsAlert", "validator"], function ($, avalon, csrfTokenHeader, bsAlert) {
|
||||
require(["jquery", "avalon", "csrfToken", "bsAlert", "pager", "validator"],
|
||||
function ($, avalon, csrfTokenHeader, bsAlert) {
|
||||
avalon.ready(function () {
|
||||
if (avalon.vmodels.userList) {
|
||||
var vm = avalon.vmodels.userList;
|
||||
}
|
||||
else {
|
||||
var vm = avalon.define({
|
||||
$id: "userList",
|
||||
|
||||
userList: [],
|
||||
userType: ["一般用户", "管理员", "超级管理员"],
|
||||
|
||||
// avalon:定义模式 userList
|
||||
avalon.ready(function () {
|
||||
keyword: "",
|
||||
showAdminOnly: false,
|
||||
isEditing: false,
|
||||
|
||||
if (avalon.vmodels.userList) {
|
||||
var vm = avalon.vmodels.userList;
|
||||
// initialize avalon object
|
||||
userList = []; //previousPage= 0; nextPage= 0; page = 1;
|
||||
//editingUserId= 0; totalPage = 1; keyword= ""; showAdminOnly= false;
|
||||
//user editor fields
|
||||
username= ""; realName= ""; email= ""; adminType= 0; id= 0;
|
||||
}
|
||||
else {
|
||||
var vm = avalon.define({
|
||||
$id: "userList",
|
||||
//通用变量
|
||||
userList: [],
|
||||
previousPage: 0,
|
||||
nextPage: 0,
|
||||
page: 1,
|
||||
editingUserId: 0,
|
||||
totalPage: 1,
|
||||
userType: ["一般用户", "管理员", "超级管理员"],
|
||||
keyword: "",
|
||||
showAdminOnly: false,
|
||||
//编辑区域同步变量
|
||||
username: "",
|
||||
realName: "",
|
||||
email: "",
|
||||
adminType: 0,
|
||||
id: 0,
|
||||
getNext: function () {
|
||||
if (!vm.nextPage)
|
||||
return;
|
||||
getPageData(vm.page + 1);
|
||||
},
|
||||
getPrevious: function () {
|
||||
if (!vm.previousPage)
|
||||
return;
|
||||
getPageData(vm.page - 1);
|
||||
},
|
||||
getBtnClass: function (btn) { //上一页/下一页按钮启用禁用逻辑
|
||||
if (btn == "next") {
|
||||
return vm.nextPage ? "btn btn-primary" : "btn btn-primary disabled";
|
||||
username: "",
|
||||
realName: "",
|
||||
email: "",
|
||||
adminType: 0,
|
||||
userId: -1,
|
||||
|
||||
pager: {
|
||||
getPage: function (page) {
|
||||
getPage(page);
|
||||
}
|
||||
},
|
||||
editUser: function (user) {
|
||||
vm.username = user.username;
|
||||
vm.realName = user.real_name;
|
||||
vm.adminType = user.admin_type;
|
||||
vm.email = user.email;
|
||||
vm.userId = user.id;
|
||||
|
||||
vm.isEditing = true;
|
||||
},
|
||||
search: function () {
|
||||
getPage(1);
|
||||
avalon.vmodels.userPager.currentPage = 1;
|
||||
}
|
||||
else {
|
||||
return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled";
|
||||
}
|
||||
},
|
||||
editUser: function (user) { //点击编辑按钮的事件,显示/隐藏编辑区
|
||||
vm.username = user.username;
|
||||
vm.realName = user.real_name;
|
||||
vm.adminType = user.admin_type;
|
||||
vm.email = user.email;
|
||||
vm.id = user.id;
|
||||
if (vm.editingUserId == user.id)
|
||||
vm.editingUserId = 0;
|
||||
else
|
||||
vm.editingUserId = user.id;
|
||||
},
|
||||
search: function () {
|
||||
getPageData(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
vm.$watch("showAdminOnly", function () {
|
||||
getPage(1);
|
||||
avalon.vmodels.userPager.currentPage = 1;
|
||||
});
|
||||
}
|
||||
vm.$watch("showAdminOnly", function () {
|
||||
getPageData(1);
|
||||
|
||||
function getPage(page) {
|
||||
var url = "/api/admin/user/?paging=true&page=" + page + "&page_size=10";
|
||||
if (vm.showAdminOnly == true)
|
||||
url += "&admin_type=1";
|
||||
if (vm.keyword != "")
|
||||
url += "&keyword=" + vm.keyword;
|
||||
$.ajax({
|
||||
beforeSend: csrfTokenHeader,
|
||||
url: url,
|
||||
dataType: "json",
|
||||
method: "get",
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
vm.userList = data.data.results;
|
||||
avalon.vmodels.userPager.totalPage = data.data.total_page;
|
||||
}
|
||||
else {
|
||||
bsAlert(data.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$("#edit-user-form").validator()
|
||||
.on('submit', function (e) {
|
||||
if (!e.isDefaultPrevented()) {
|
||||
var data = {
|
||||
username: vm.username,
|
||||
real_name: vm.realName,
|
||||
email: vm.email,
|
||||
id: vm.userId,
|
||||
admin_type: vm.adminType
|
||||
};
|
||||
if ($("#password").val() !== "")
|
||||
data.password = $("#password").val();
|
||||
$.ajax({
|
||||
url: "/api/admin/user/",
|
||||
data: data,
|
||||
dataType: "json",
|
||||
method: "put",
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
bsAlert("编辑成功!");
|
||||
getPage(1);
|
||||
$("#password").val("");
|
||||
vm.isEditing = false;
|
||||
} else {
|
||||
bsAlert(data.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
avalon.scan();
|
||||
getPageData(1); //用户列表初始化
|
||||
//Ajax get数据
|
||||
function getPageData(page) {
|
||||
var url = "/api/admin/user/?paging=true&page=" + page + "&page_size=10";
|
||||
if (vm.showAdminOnly == true)
|
||||
url += "&admin_type=1";
|
||||
if (vm.keyword != "")
|
||||
url += "&keyword=" + vm.keyword;
|
||||
$.ajax({
|
||||
beforeSend: csrfTokenHeader,
|
||||
url: url,
|
||||
dataType: "json",
|
||||
method: "get",
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
vm.userList = data.data.results;
|
||||
vm.totalPage = data.data.total_page;
|
||||
vm.previousPage = data.data.previous_page;
|
||||
vm.nextPage = data.data.next_page;
|
||||
vm.page = page;
|
||||
}
|
||||
else {
|
||||
bsAlert(data.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$("#edit_user-form").validator()
|
||||
.on('submit', function (e) {
|
||||
if (!e.isDefaultPrevented()) {
|
||||
var data = {
|
||||
username: vm.username,
|
||||
real_name: vm.realName,
|
||||
email: vm.email,
|
||||
id: vm.id,
|
||||
admin_type: vm.adminType
|
||||
};
|
||||
if ($("#password").val() !== "")
|
||||
data.password = $("#password").val();
|
||||
$.ajax({
|
||||
beforeSend: csrfTokenHeader,
|
||||
url: "/api/admin/user/",
|
||||
data: data,
|
||||
dataType: "json",
|
||||
method: "put",
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
bsAlert("编辑成功!");
|
||||
getPageData(1);
|
||||
$("#password").val("");
|
||||
} else {
|
||||
bsAlert(data.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
})
|
||||
@@ -27,14 +27,41 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"],
|
||||
return;
|
||||
}
|
||||
|
||||
function getLanguage(){
|
||||
return $("input[name='language'][checked]").val();
|
||||
}
|
||||
|
||||
var codeEditor = codeMirror(codeEditorSelector, "text/x-csrc");
|
||||
var language = $("input[name='language'][checked]").val();
|
||||
var language = getLanguage();
|
||||
var submissionId;
|
||||
|
||||
function setLanguage(language){
|
||||
var languageTypes = {"1": "text/x-csrc", "2": "text/x-c++src", "3": "text/x-java"};
|
||||
codeEditor.setOption("mode", languageTypes[language]);
|
||||
}
|
||||
|
||||
function saveCode(code){
|
||||
localStorage.setItem(location.href, JSON.stringify({code: code, language: language}))
|
||||
}
|
||||
|
||||
if(window.localStorage){
|
||||
var data = localStorage[location.href];
|
||||
if(data){
|
||||
data = JSON.parse(data);
|
||||
$("input[name='language'][value='" + data.language + "']").prop("checked", true);
|
||||
language = data.language;
|
||||
codeEditor.setValue(data.code);
|
||||
setLanguage(data.language);
|
||||
}
|
||||
|
||||
setInterval(function(){
|
||||
saveCode(codeEditor.getValue())
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
$("input[name='language']").change(function () {
|
||||
language = this.value;
|
||||
var languageTypes = {"1": "text/x-csrc", "2": "text/x-c++src", "3": "text/x-java"};
|
||||
codeEditor.setOption("mode", languageTypes[language]);
|
||||
setLanguage(language);
|
||||
});
|
||||
|
||||
$("#show-more-btn").click(function () {
|
||||
|
||||
@@ -23,12 +23,16 @@
|
||||
validator: "lib/validator/validator",
|
||||
ZeroClipboard: "lib/ZeroClipboard/ZeroClipboard",
|
||||
|
||||
// ------ admin web 组件 ----------
|
||||
pager: "components/pager",
|
||||
|
||||
// ------ 下面写的都不要直接用,而是使用上面的封装版本 ------
|
||||
//富文本编辑器simditor -> editor
|
||||
simditor: "lib/simditor/simditor",
|
||||
"simple-module": "lib/simditor/module",
|
||||
"simple-hotkeys": "lib/simditor/hotkeys",
|
||||
"simple-uploader": "lib/simditor/uploader",
|
||||
"simditor-autosave": "lib/simditor/simditor-autosave",
|
||||
|
||||
//code mirror 代码编辑器 ->codeMirror
|
||||
_codeMirror: "lib/codeMirror/codemirror",
|
||||
@@ -69,6 +73,11 @@
|
||||
group_20_pack: "app/admin/group/group",
|
||||
submissionList_21_pack: "app/admin/contest/submissionList"
|
||||
},
|
||||
shim: {
|
||||
avalon: {
|
||||
exports: "avalon"
|
||||
}
|
||||
},
|
||||
findNestedDependencies: true,
|
||||
appDir: "../",
|
||||
dir: "../../release/",
|
||||
@@ -76,6 +85,9 @@
|
||||
{
|
||||
name: "bootstrap",
|
||||
},
|
||||
{
|
||||
name: "codeMirror"
|
||||
},
|
||||
{
|
||||
name: "announcement_0_pack"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
define("editorComponent", ["jquery", "avalon", "editor"], function ($, avalon, editor) {
|
||||
avalon.component("ms:editor", {
|
||||
$template: "<textarea ms-attr-id='{{ editorId }}' ms-duplex='content' ms-attr-placeholder='placeholder'></textarea>",
|
||||
editorId: "editorId",
|
||||
content: "",
|
||||
placeholder: "",
|
||||
$editor: {},
|
||||
$ready: function (vm, el) {
|
||||
el.msRetain = true;
|
||||
vm.$editor = editor($("#" + vm.editorId));
|
||||
vm.$watch("content", function (oldValue, newValue) {
|
||||
vm.$editor.setValue(oldValue);
|
||||
})
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
define("pager", ["avalon"], function (avalon) {
|
||||
var _interface = function () {
|
||||
};
|
||||
avalon.component("ms:pager", {
|
||||
$template: "页数: {{ currentPage }}/{{ totalPage }} " +
|
||||
"<button ms-class=\"{{ currentPage==1?'btn btn-primary disabled':'btn btn-primary' }}\" ms-click=\"_getPrevPage\">上一页</button> " +
|
||||
" <button ms-class=\"{{ currentPage==totalPage?'btn btn-primary disabled':'btn btn-primary' }}\" ms-click=\"_getNextPage\">下一页</button>",
|
||||
currentPage: 1,
|
||||
totalPage: 1,
|
||||
_getPrevPage: _interface,
|
||||
_getNextPage: _interface,
|
||||
$init: function (vm, el) {
|
||||
vm._getPrevPage = function () {
|
||||
if (vm.currentPage > 1) {
|
||||
vm.currentPage--;
|
||||
vm.getPage(vm.currentPage);
|
||||
}
|
||||
};
|
||||
vm._getNextPage = function () {
|
||||
if (vm.currentPage < vm.totalPage) {
|
||||
vm.currentPage++;
|
||||
vm.getPage(vm.currentPage);
|
||||
}
|
||||
};
|
||||
},
|
||||
$ready: function(vm, el){
|
||||
el.msRetain = true;
|
||||
vm.getPage(1);
|
||||
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -6,6 +6,7 @@ var require = {
|
||||
jquery: "lib/jquery/jquery",
|
||||
jcountdown: "lib/jcountdown/jcountdown",
|
||||
avalon: "lib/avalon/avalon",
|
||||
//avalon15: "lib/avalon/avalon15",
|
||||
editor: "utils/editor",
|
||||
uploader: "utils/uploader",
|
||||
formValidation: "utils/formValidation",
|
||||
@@ -23,6 +24,10 @@ var require = {
|
||||
validator: "lib/validator/validator",
|
||||
ZeroClipboard: "lib/ZeroClipboard/ZeroClipboard",
|
||||
|
||||
// ------ admin web 组件 ----------
|
||||
pager: "components/pager",
|
||||
editorComponent: "components/editorComponent",
|
||||
|
||||
|
||||
// ------ 下面写的都不要直接用,而是使用上面的封装版本 ------
|
||||
//富文本编辑器simditor -> editor
|
||||
@@ -30,6 +35,7 @@ var require = {
|
||||
"simple-module": "lib/simditor/module",
|
||||
"simple-hotkeys": "lib/simditor/hotkeys",
|
||||
"simple-uploader": "lib/simditor/uploader",
|
||||
"simditor-autosave": "lib/simditor/simditor-autosave",
|
||||
|
||||
//code mirror 代码编辑器 ->codeMirror
|
||||
_codeMirror: "lib/codeMirror/codemirror",
|
||||
@@ -69,5 +75,10 @@ var require = {
|
||||
contestPassword_19_pack: "app/oj/contest/contestPassword",
|
||||
group_20_pack: "app/admin/group/group",
|
||||
submissionList_21_pack: "app/admin/contest/submissionList"
|
||||
},
|
||||
shim: {
|
||||
avalon: {
|
||||
exports: "avalon"
|
||||
}
|
||||
}
|
||||
};
|
||||
+5112
-4986
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,138 @@
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module unless amdModuleId is set
|
||||
define('simditor-autosave', ["jquery","simple-module","simditor"], function (a0,b1,c2) {
|
||||
return (root['SimditorAutosave'] = factory(a0,b1,c2));
|
||||
});
|
||||
} else if (typeof exports === 'object') {
|
||||
// Node. Does not work with strict CommonJS, but
|
||||
// only CommonJS-like environments that support module.exports,
|
||||
// like Node.
|
||||
module.exports = factory(require("jquery"),require("simple-module"),require("simditor"));
|
||||
} else {
|
||||
root['SimditorAutosave'] = factory(jQuery,SimpleModule,Simditor);
|
||||
}
|
||||
}(this, function ($, SimpleModule, Simditor) {
|
||||
|
||||
var SimditorAutosave,
|
||||
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
||||
hasProp = {}.hasOwnProperty;
|
||||
|
||||
SimditorAutosave = (function(superClass) {
|
||||
extend(SimditorAutosave, superClass);
|
||||
|
||||
function SimditorAutosave() {
|
||||
return SimditorAutosave.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
|
||||
SimditorAutosave.pluginName = 'Autosave';
|
||||
|
||||
SimditorAutosave.prototype.opts = {
|
||||
autosave: true,
|
||||
autosavePath: null
|
||||
};
|
||||
|
||||
SimditorAutosave.prototype._init = function() {
|
||||
var currentVal, link, name, val;
|
||||
this.editor = this._module;
|
||||
if (!this.opts.autosave) {
|
||||
return;
|
||||
}
|
||||
this.name = typeof this.opts.autosave === 'string' ? this.opts.autosave : 'simditor';
|
||||
if (this.opts.autosavePath) {
|
||||
this.path = this.opts.autosavePath;
|
||||
} else {
|
||||
link = $("<a/>", {
|
||||
href: location.href
|
||||
});
|
||||
name = this.editor.textarea.data('autosave') || this.name;
|
||||
this.path = "/" + (link[0].pathname.replace(/\/$/g, "").replace(/^\//g, "")) + "/autosave/" + name + "/";
|
||||
}
|
||||
if (!this.path) {
|
||||
return;
|
||||
}
|
||||
this.editor.on("valuechanged", (function(_this) {
|
||||
return function() {
|
||||
return _this.storage.set(_this.path, _this.editor.getValue());
|
||||
};
|
||||
})(this));
|
||||
this.editor.el.closest('form').on('ajax:success.simditor-' + this.editor.id, (function(_this) {
|
||||
return function(e) {
|
||||
return _this.storage.remove(_this.path);
|
||||
};
|
||||
})(this));
|
||||
val = this.storage.get(this.path);
|
||||
if (!val) {
|
||||
return;
|
||||
}
|
||||
currentVal = this.editor.textarea.val();
|
||||
if (val === currentVal) {
|
||||
return;
|
||||
}
|
||||
if (this.editor.textarea.is('[data-autosave-confirm]')) {
|
||||
if (confirm(this.editor.textarea.data('autosave-confirm') || 'Are you sure to restore unsaved changes?')) {
|
||||
return this.editor.setValue(val);
|
||||
} else {
|
||||
return this.storage.remove(this.path);
|
||||
}
|
||||
} else {
|
||||
return this.editor.setValue(val);
|
||||
}
|
||||
};
|
||||
|
||||
SimditorAutosave.prototype.storage = {
|
||||
supported: function() {
|
||||
var error;
|
||||
try {
|
||||
localStorage.setItem('_storageSupported', 'yes');
|
||||
localStorage.removeItem('_storageSupported');
|
||||
return true;
|
||||
} catch (_error) {
|
||||
error = _error;
|
||||
return false;
|
||||
}
|
||||
},
|
||||
set: function(key, val, session) {
|
||||
var storage;
|
||||
if (session == null) {
|
||||
session = false;
|
||||
}
|
||||
if (!this.supported()) {
|
||||
return;
|
||||
}
|
||||
storage = session ? sessionStorage : localStorage;
|
||||
return storage.setItem(key, val);
|
||||
},
|
||||
get: function(key, session) {
|
||||
var storage;
|
||||
if (session == null) {
|
||||
session = false;
|
||||
}
|
||||
if (!this.supported()) {
|
||||
return;
|
||||
}
|
||||
storage = session ? sessionStorage : localStorage;
|
||||
return storage[key];
|
||||
},
|
||||
remove: function(key, session) {
|
||||
var storage;
|
||||
if (session == null) {
|
||||
session = false;
|
||||
}
|
||||
if (!this.supported()) {
|
||||
return;
|
||||
}
|
||||
storage = session ? sessionStorage : localStorage;
|
||||
return storage.removeItem(key);
|
||||
}
|
||||
};
|
||||
|
||||
return SimditorAutosave;
|
||||
|
||||
})(SimpleModule);
|
||||
|
||||
Simditor.connect(SimditorAutosave);
|
||||
|
||||
return SimditorAutosave;
|
||||
|
||||
}));
|
||||
@@ -1,6 +1,6 @@
|
||||
define("uploader", ["webUploader", "csrfToken"], function(webuploader,csrfTokenHeader){
|
||||
function uploader(selector, server, onSuccess, beforeUpload) {
|
||||
var Webuploader= webuploader.create({
|
||||
define("uploader", ["webUploader", "csrfToken"], function (webuploader, csrfTokenHeader) {
|
||||
function uploader(selector, server, onSuccess, uploadProgress) {
|
||||
var Webuploader = webuploader.create({
|
||||
auto: true,
|
||||
// swf文件路径
|
||||
swf: "/static/img/Uploader.swf",
|
||||
@@ -11,11 +11,16 @@ define("uploader", ["webUploader", "csrfToken"], function(webuploader,csrfTokenH
|
||||
pick: selector,
|
||||
// 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传!
|
||||
resize: false,
|
||||
uploadBeforeSend : csrfTokenHeader
|
||||
uploadBeforeSend: csrfTokenHeader,
|
||||
accept: {
|
||||
title: 'testcase zip',
|
||||
extensions: 'zip',
|
||||
mimeTypes: 'application/zip'
|
||||
}
|
||||
});
|
||||
Webuploader.on("uploadBeforeSend",csrfTokenHeader);
|
||||
Webuploader.on("uploadBeforeSend", csrfTokenHeader);
|
||||
Webuploader.on("uploadSuccess", onSuccess);
|
||||
Webuploader.on("beforeFileQueued", beforeUpload);
|
||||
Webuploader.on("uploadProgress", uploadProgress);
|
||||
return Webuploader;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,3 +30,10 @@ class SubmissionRejudgeSerializer(serializers.Serializer):
|
||||
submission_id = serializers.CharField(max_length=40)
|
||||
|
||||
|
||||
class CreateContestSubmissionSerializer(serializers.Serializer):
|
||||
contest_id = serializers.IntegerField()
|
||||
problem_id = serializers.IntegerField()
|
||||
language = serializers.IntegerField()
|
||||
code = serializers.CharField(max_length=3000)
|
||||
|
||||
|
||||
|
||||
+129
-3
@@ -4,8 +4,8 @@ from django.test import TestCase, Client
|
||||
from django.core.urlresolvers import reverse
|
||||
from account.models import User, REGULAR_USER, ADMIN, SUPER_ADMIN
|
||||
from problem.models import Problem
|
||||
from contest.models import Contest
|
||||
from contest.models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PUBLIC_CONTEST
|
||||
from contest.models import Contest, ContestProblem
|
||||
from contest.models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST
|
||||
from submission.models import Submission
|
||||
from rest_framework.test import APITestCase, APIClient
|
||||
|
||||
@@ -193,7 +193,7 @@ class SubmissionPageTest(TestCase):
|
||||
hint="hint1",
|
||||
created_by=User.objects.get(username="test1"))
|
||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
||||
contest_type=PASSWORD_PUBLIC_CONTEST, show_rank=True,
|
||||
contest_type=PUBLIC_CONTEST, show_rank=True,
|
||||
show_user_submission=True,
|
||||
start_time="2015-08-15T10:00:00.000Z",
|
||||
end_time="2015-08-15T12:00:00.000Z",
|
||||
@@ -203,3 +203,129 @@ class SubmissionPageTest(TestCase):
|
||||
language=1,
|
||||
code='#include "stdio.h"\nint main(){\n\treturn 0;\n}',
|
||||
problem_id=self.problem.id)
|
||||
|
||||
|
||||
class ContestSubmissionAPITest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.url = reverse('contest_submission_api')
|
||||
self.user1 = User.objects.create(username="test1", admin_type=SUPER_ADMIN)
|
||||
self.user1.set_password("testaa")
|
||||
self.user1.save()
|
||||
self.user2 = User.objects.create(username="test2", admin_type=REGULAR_USER)
|
||||
self.user2.set_password("testbb")
|
||||
self.user2.save()
|
||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
||||
contest_type=PUBLIC_CONTEST, show_rank=True,
|
||||
show_user_submission=True,
|
||||
start_time="2015-08-15T10:00:00.000Z",
|
||||
end_time="2015-08-30T12:00:00.000Z",
|
||||
created_by=User.objects.get(username="test1"))
|
||||
self.contest_problem = ContestProblem.objects.create(title="titlex",
|
||||
description="descriptionx",
|
||||
input_description="input1_description",
|
||||
output_description="output1_description",
|
||||
test_case_id="1",
|
||||
samples=json.dumps([{"input": "1 1", "output": "2"}]),
|
||||
time_limit=100,
|
||||
memory_limit=1000,
|
||||
hint="hint1",
|
||||
created_by=User.objects.get(username="test1"),
|
||||
contest=Contest.objects.get(title="titlex"),
|
||||
sort_index="a")
|
||||
|
||||
# 以下是创建比赛的提交
|
||||
def test_invalid_format(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
data = {"contest_id": self.global_contest.id, "language": 1}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_contest_submission_successfully(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
data = {"contest_id": self.global_contest.id, "problem_id": self.contest_problem.id,
|
||||
"language": 1, "code": '#include "stdio.h"\nint main(){\n\treturn 0;\n}'}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data["code"], 0)
|
||||
|
||||
def test_contest_problem_does_not_exist(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
data = {"contest_id": self.global_contest.id, "problem_id": self.contest_problem.id + 10,
|
||||
"language": 1, "code": '#include "stdio.h"\nint main(){\n\treturn 0;\n}'}
|
||||
response = self.client.post(self.url, data=data)
|
||||
self.assertEqual(response.data, {"code": 1, "data": u"题目不存在"})
|
||||
|
||||
|
||||
class ContestSubmissionAdminAPITest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.url = reverse('contest_submission_admin_api_view')
|
||||
self.userA = User.objects.create(username="test1", admin_type=ADMIN)
|
||||
self.userA.set_password("testaa")
|
||||
self.userA.save()
|
||||
self.userS = User.objects.create(username="test2", admin_type=SUPER_ADMIN)
|
||||
self.userS.set_password("testbb")
|
||||
self.userS.save()
|
||||
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
|
||||
contest_type=PASSWORD_PROTECTED_CONTEST, show_rank=True,
|
||||
show_user_submission=True,
|
||||
start_time="2015-08-15T10:00:00.000Z",
|
||||
end_time="2015-08-15T12:00:00.000Z",
|
||||
password="aacc", created_by=self.userS
|
||||
)
|
||||
self.problem = ContestProblem.objects.create(title="title1",
|
||||
description="description1",
|
||||
input_description="input1_description",
|
||||
output_description="output1_description",
|
||||
test_case_id="1",
|
||||
sort_index="1",
|
||||
samples=json.dumps([{"input": "1 1", "output": "2"}]),
|
||||
time_limit=100,
|
||||
memory_limit=1000,
|
||||
hint="hint1",
|
||||
contest=self.global_contest,
|
||||
created_by=self.userS)
|
||||
self.submission = Submission.objects.create(user_id=self.userA.id,
|
||||
language=1,
|
||||
code='#include "stdio.h"\nint main(){\n\treturn 0;\n}',
|
||||
problem_id=self.problem.id)
|
||||
self.submissionS = Submission.objects.create(user_id=self.userS.id,
|
||||
language=2,
|
||||
code='#include "stdio.h"\nint main(){\n\treturn 0;\n}',
|
||||
problem_id=self.problem.id)
|
||||
|
||||
def test_submission_contest_does_not_exist(self):
|
||||
self.client.login(username="test2", password="testbb")
|
||||
response = self.client.get(self.url + "?contest_id=99")
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_submission_contest_parameter_error(self):
|
||||
self.client.login(username="test2", password="testbb")
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_submission_access_denied(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
response = self.client.get(self.url + "?problem_id=" + str(self.problem.id))
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_submission_access_denied_with_contest_id(self):
|
||||
self.client.login(username="test1", password="testaa")
|
||||
response = self.client.get(self.url + "?contest_id=" + str(self.global_contest.id))
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
|
||||
def test_get_submission_successfully(self):
|
||||
self.client.login(username="test2", password="testbb")
|
||||
response = self.client.get(
|
||||
self.url + "?contest_id=" + str(self.global_contest.id) + "&problem_id=" + str(self.problem.id))
|
||||
self.assertEqual(response.data["code"], 0)
|
||||
|
||||
def test_get_submission_successfully_problem(self):
|
||||
self.client.login(username="test2", password="testbb")
|
||||
response = self.client.get(self.url + "?problem_id=" + str(self.problem.id))
|
||||
self.assertEqual(response.data["code"], 0)
|
||||
|
||||
def test_get_submission_problem_do_not_exist(self):
|
||||
self.client.login(username="test2", password="testbb")
|
||||
response = self.client.get(self.url + "?problem_id=9999")
|
||||
self.assertEqual(response.data["code"], 1)
|
||||
+84
-36
@@ -5,27 +5,29 @@ import logging
|
||||
import redis
|
||||
from django.shortcuts import render
|
||||
from django.core.paginator import Paginator
|
||||
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from judge.judger_controller.tasks import judge
|
||||
from judge.judger_controller.settings import redis_config
|
||||
from account.decorators import login_required, super_admin_required
|
||||
from account.models import SUPER_ADMIN, User, REGULAR_USER
|
||||
|
||||
from account.models import SUPER_ADMIN, User
|
||||
from problem.models import Problem
|
||||
from contest.models import ContestProblem, Contest
|
||||
from announcement.models import Announcement
|
||||
from contest.decorators import check_user_contest_permission
|
||||
from utils.shortcuts import serializer_invalid_response, error_response, success_response, error_page, paginate
|
||||
|
||||
from utils.cache import get_cache_redis
|
||||
from .models import Submission
|
||||
from .serializers import (CreateSubmissionSerializer, SubmissionSerializer,
|
||||
SubmissionhareSerializer, SubmissionRejudgeSerializer)
|
||||
|
||||
SubmissionhareSerializer, SubmissionRejudgeSerializer,
|
||||
CreateContestSubmissionSerializer)
|
||||
|
||||
logger = logging.getLogger("app_info")
|
||||
|
||||
|
||||
def _judge(submission_id, time_limit, memory_limit, test_case_id):
|
||||
judge.delay(submission_id, time_limit, memory_limit, test_case_id)
|
||||
get_cache_redis().incr("judge_queue_length")
|
||||
|
||||
|
||||
class SubmissionAPIView(APIView):
|
||||
@login_required
|
||||
def post(self, request):
|
||||
@@ -41,17 +43,16 @@ class SubmissionAPIView(APIView):
|
||||
problem = Problem.objects.get(id=data["problem_id"])
|
||||
except Problem.DoesNotExist:
|
||||
return error_response(u"题目不存在")
|
||||
submission = Submission.objects.create(user_id=request.user.id, language=int(data["language"]),
|
||||
code=data["code"], problem_id=problem.id)
|
||||
submission = Submission.objects.create(user_id=request.user.id,
|
||||
language=int(data["language"]),
|
||||
code=data["code"],
|
||||
problem_id=problem.id)
|
||||
|
||||
try:
|
||||
judge.delay(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
||||
_judge(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return error_response(u"提交判题任务失败")
|
||||
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
|
||||
r.incr("judge_queue_length")
|
||||
|
||||
return success_response({"submission_id": submission.id})
|
||||
else:
|
||||
return serializer_invalid_response(serializer)
|
||||
@@ -71,6 +72,37 @@ class SubmissionAPIView(APIView):
|
||||
return success_response(response_data)
|
||||
|
||||
|
||||
class ContestSubmissionAPIView(APIView):
|
||||
@check_user_contest_permission
|
||||
def post(self, request):
|
||||
"""
|
||||
创建比赛的提交
|
||||
---
|
||||
request_serializer: CreateContestSubmissionSerializer
|
||||
"""
|
||||
serializer = CreateContestSubmissionSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
data = serializer.data
|
||||
contest = Contest.objects.get(id=data["contest_id"])
|
||||
try:
|
||||
problem = ContestProblem.objects.get(contest=contest, id=data["problem_id"])
|
||||
except ContestProblem.DoesNotExist:
|
||||
return error_response(u"题目不存在")
|
||||
submission = Submission.objects.create(user_id=request.user.id,
|
||||
language=int(data["language"]),
|
||||
contest_id=contest.id,
|
||||
code=data["code"],
|
||||
problem_id=problem.id)
|
||||
try:
|
||||
_judge(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return error_response(u"提交判题任务失败")
|
||||
return success_response({"submission_id": submission.id})
|
||||
else:
|
||||
return serializer_invalid_response(serializer)
|
||||
|
||||
|
||||
@login_required
|
||||
def problem_my_submissions_list_page(request, problem_id):
|
||||
"""
|
||||
@@ -81,11 +113,11 @@ def problem_my_submissions_list_page(request, problem_id):
|
||||
except Problem.DoesNotExist:
|
||||
return error_page(request, u"问题不存在")
|
||||
|
||||
submissions = Submission.objects.filter(user_id=request.user.id, problem_id=problem.id,
|
||||
contest_id__isnull=True).order_by("-create_time"). \
|
||||
submissions = Submission.objects.filter(user_id=request.user.id, problem_id=problem.id,contest_id__isnull=True).\
|
||||
order_by("-create_time"). \
|
||||
values("id", "result", "create_time", "accepted_answer_time", "language")
|
||||
|
||||
return render(request, "oj/problem/my_submissions_list.html",
|
||||
return render(request, "oj/submission/problem_my_submissions_list.html",
|
||||
{"submissions": submissions, "problem": problem})
|
||||
|
||||
|
||||
@@ -119,17 +151,13 @@ def my_submission(request, submission_id):
|
||||
except Submission.DoesNotExist:
|
||||
return error_page(request, u"提交不存在")
|
||||
|
||||
if submission.contest_id:
|
||||
try:
|
||||
problem = ContestProblem.objects.get(id=submission.problem_id,
|
||||
visible=True)
|
||||
except ContestProblem.DoesNotExist:
|
||||
return error_page(request, u"提交不存在")
|
||||
else:
|
||||
try:
|
||||
try:
|
||||
if submission.contest_id:
|
||||
problem = ContestProblem.objects.get(id=submission.problem_id, visible=True)
|
||||
else:
|
||||
problem = Problem.objects.get(id=submission.problem_id, visible=True)
|
||||
except Problem.DoesNotExist:
|
||||
return error_page(request, u"提交不存在")
|
||||
except Exception:
|
||||
return error_page(request, u"提交不存在")
|
||||
|
||||
if submission.info:
|
||||
try:
|
||||
@@ -139,12 +167,13 @@ def my_submission(request, submission_id):
|
||||
else:
|
||||
info = None
|
||||
user = User.objects.get(id=submission.user_id)
|
||||
return render(request, "oj/problem/my_submission.html",
|
||||
return render(request, "oj/submission/my_submission.html",
|
||||
{"submission": submission, "problem": problem, "info": info,
|
||||
"user": user, "can_share": result["can_share"]})
|
||||
|
||||
|
||||
class SubmissionAdminAPIView(APIView):
|
||||
@super_admin_required
|
||||
def get(self, request):
|
||||
problem_id = request.GET.get("problem_id", None)
|
||||
if not problem_id:
|
||||
@@ -164,7 +193,8 @@ def my_submission_list_page(request, page=1):
|
||||
submissions = Submission.objects.filter(contest_id__isnull=True)
|
||||
else:
|
||||
submissions = Submission.objects.filter(user_id=request.user.id, contest_id__isnull=True)
|
||||
submissions = submissions.values("id", "user_id", "problem_id", "result", "create_time", "accepted_answer_time", "language").order_by("-create_time")
|
||||
submissions = submissions.values("id", "user_id", "problem_id", "result", "create_time", "accepted_answer_time",
|
||||
"language").order_by("-create_time")
|
||||
|
||||
language = request.GET.get("language", None)
|
||||
filter = None
|
||||
@@ -238,24 +268,42 @@ class SubmissionRejudgeAdminAPIView(APIView):
|
||||
serializer = SubmissionRejudgeSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
submission_id = serializer.data["submission_id"]
|
||||
# 目前只考虑前台公开题目的重新判题
|
||||
try:
|
||||
submission = Submission.objects.get(id=submission_id)
|
||||
submission = Submission.objects.get(id=submission_id, contest_id__isnull=True)
|
||||
except Submission.DoesNotExist:
|
||||
return error_response(u"提交不存在")
|
||||
# 目前只考虑前台公开题目的重新判题
|
||||
|
||||
try:
|
||||
problem = Problem.objects.get(id=submission.problem_id)
|
||||
except Problem.DoesNotExist:
|
||||
return error_response(u"题目不存在")
|
||||
try:
|
||||
judge.delay(submission_id, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
||||
_judge(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return error_response(u"提交判题任务失败")
|
||||
|
||||
# 增加redis 中判题队列长度的计数器
|
||||
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
|
||||
r.incr("judge_queue_length")
|
||||
return success_response(u"任务提交成功")
|
||||
else:
|
||||
return serializer_invalid_response(serializer)
|
||||
|
||||
|
||||
class ContestSubmissionAdminAPIView(APIView):
|
||||
@check_user_contest_permission
|
||||
def get(self, request):
|
||||
"""
|
||||
查询比赛提交,单个比赛题目提交的adminAPI
|
||||
---
|
||||
response_serializer: SubmissionSerializer
|
||||
"""
|
||||
problem_id = request.GET.get("problem_id", None)
|
||||
contest_id = request.GET.get("contest_id", None)
|
||||
|
||||
# 需要 problem_id 和 contest_id 两个参数 否则会在check_user_contest_permission 的时候被拦截
|
||||
if problem_id:
|
||||
submissions = Submission.objects.filter(contest_id=contest_id, problem_id=problem_id).order_by("-create_time")
|
||||
# 需要 contest_id 参数
|
||||
else:
|
||||
submissions = Submission.objects.filter(contest_id=contest_id).order_by("-create_time")
|
||||
|
||||
return paginate(request, submissions, SubmissionSerializer)
|
||||
|
||||
@@ -69,8 +69,8 @@
|
||||
<ul class="list-group">
|
||||
<div ms-repeat="adminNavList">
|
||||
<li class="list-group-header">{{ el.name }}</li>
|
||||
<li class="list-group-item" ms-attr-id="getLiId(item.hash)" ms-repeat-item="el.children">
|
||||
<a ms-attr-href="el.hash">{{ item.name }}</a>
|
||||
<li class="list-group-item" ms-repeat-item="el.children" ms-attr-id="getLiId(item.hash)" >
|
||||
<a ms-attr-href="item.hash">{{ item.name }}</a>
|
||||
</li>
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
<th>创建时间</th>
|
||||
<th>更新时间</th>
|
||||
<th>创建者</th>
|
||||
<th>类型</th>
|
||||
<th>可见</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@@ -17,7 +16,6 @@
|
||||
<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 ms-text="el.is_global?'全局可见':'组内可见'"></td>
|
||||
<td ms-text="el.visible?'可见':'不可见'"></td>
|
||||
<td>
|
||||
<button class="btn-sm btn-info" ms-click="editAnnouncement(el)">编辑</button>
|
||||
@@ -28,12 +26,10 @@
|
||||
<label>仅显示可见 <input ms-duplex-checked="showVisibleOnly" type="checkbox"/></label>
|
||||
</div>
|
||||
<div class="right">
|
||||
页数:{{ page }}/{{ totalPage }}
|
||||
<button ms-attr-class="getBtnClass('pre')" ms-click="getPrevious">上一页</button>
|
||||
<button ms-attr-class="getBtnClass('next')" ms-click="getNext">下一页</button>
|
||||
<ms:pager $id="announcementPager" config="pager"></ms:pager>
|
||||
</div>
|
||||
|
||||
<div ms-visible="editingAnnouncementId">
|
||||
<div ms-visible="isEditing">
|
||||
<h3>编辑公告</h3>
|
||||
|
||||
<div class="form-group">
|
||||
@@ -42,37 +38,19 @@
|
||||
ms-duplex="newTitle"></div>
|
||||
<div class="form-group">
|
||||
<label>内容</label>
|
||||
<textarea id="edit-announcement-editor"></textarea>
|
||||
<ms:editor $id="editAnnouncementEditor" config="editAnnouncementEditor"></ms:editor>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>可见 <input ms-duplex-checked="announcementVisible" type="checkbox"/></label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>可见范围</label>
|
||||
|
||||
<div>
|
||||
<span ms-if="showGlobalViewRadio">
|
||||
<input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">全局可见
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组内可见
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="form-group col-md-12" ms-if="!isGlobal">
|
||||
<!-- radio 的value 没有用 但是没有的话,表单验证会出错-->
|
||||
<div ms-repeat="allGroups" class="col-md-4">
|
||||
<input type="checkbox" value="group_id" ms-duplex-checked="el.isSelected"> {{ el.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button ms-click="submitChange()" class="btn btn-success">保存修改</button>
|
||||
|
||||
<button ms-click="cancelEdit()" class="btn btn-danger">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
<h3>添加公告</h3>
|
||||
<h3>发布公告</h3>
|
||||
|
||||
<form id="announcement-form">
|
||||
<div class="form-group">
|
||||
@@ -84,36 +62,12 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>内容</label>
|
||||
<textarea id="create-announcement-editor" placeholder="公告内容" maxlength="10000" required>
|
||||
</textarea>
|
||||
|
||||
<ms:editor $id="createAnnouncementEditor" config="createAnnouncementEditor"></ms:editor>
|
||||
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>可见范围</label>
|
||||
|
||||
<div>
|
||||
<span ms-if="showGlobalViewRadio">
|
||||
<label>
|
||||
<small><input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">全局可见
|
||||
</small>
|
||||
</label>
|
||||
</span>
|
||||
<span>
|
||||
<label>
|
||||
<small><input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组内可见
|
||||
</small>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="form-group col-md-12" ms-if="!isGlobal">
|
||||
<!-- radio 的value 没有用 但是没有的话,表单验证会出错-->
|
||||
<div ms-repeat="allGroups" class="col-md-4">
|
||||
<input type="checkbox" value="group_id" ms-duplex-checked="el.isSelected"> {{ el.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-success">发布公告</button>
|
||||
</div>
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
|
||||
<div class="form-group col-md-12">
|
||||
<label>题目描述</label>
|
||||
<textarea id="problemDescription" placeholder="这里输入内容(此内容不能为空)" ms-duplex="description"></textarea>
|
||||
<p class="error-info" ms-visible="description==''">请填写题目描述</p>
|
||||
<ms:editor $id="problemDescriptionEditor" config="problemDescriptionEditor"></ms:editor>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -105,6 +104,7 @@
|
||||
<small class="text-info">请将所有测试用例打包在一个文件中上传,所有文件要在压缩包的根目录,且输入输出文件名要以从1开始连续数字标识要对应例如:<br>
|
||||
1.in 1.out 2.in 2.out
|
||||
</small>
|
||||
<p>上传进度<span ms-text="uploadProgress * 100"></span>%</p>
|
||||
<table class="table table-striped" ms-visible="uploadSuccess">
|
||||
<tr>
|
||||
<td>编号</td>
|
||||
@@ -125,7 +125,7 @@
|
||||
</div>
|
||||
<div class="form-group col-md-12">
|
||||
<label>提示</label>
|
||||
<textarea id="hint" placeholder="这里输入内容" ms-duplex="hint"></textarea>
|
||||
<ms:editor $id="problemHintEditor" config="problemHintEditor"></ms:editor>
|
||||
</div>
|
||||
<div class="form-group col-md-12">
|
||||
<label>来源</label>
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
|
||||
<div class="form-group col-md-12">
|
||||
<label>题目描述</label>
|
||||
<textarea id="problemDescription" placeholder="这里输入内容(此内容不能为空)" ms-duplex="description"></textarea>
|
||||
<p class="error-info" ms-visible="description==''">请填写题目描述</p>
|
||||
<ms:editor $id="problemDescriptionEditor" config="problemDescriptionEditor"></ms:editor>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -111,6 +110,7 @@
|
||||
<small class="text-info">请将所有测试用例打包在一个文件中上传,所有文件要在压缩包的根目录,且输入输出文件名要以从1开始连续数字标识要对应例如:<br>
|
||||
1.in 1.out 2.in 2.out
|
||||
</small>
|
||||
<p>上传进度<span ms-text="uploadProgress * 100"></span>%</p>
|
||||
<table class="table table-striped" ms-visible="uploadSuccess">
|
||||
<tr>
|
||||
<td>编号</td>
|
||||
@@ -131,7 +131,7 @@
|
||||
</div>
|
||||
<div class="form-group col-md-12">
|
||||
<label>提示</label>
|
||||
<textarea id="hint" placeholder="这里输入内容" ms-duplex="hint"></textarea>
|
||||
<ms:editor $id="problemHintEditor" config="problemHintEditor"></ms:editor>
|
||||
</div>
|
||||
<div class="form-group col-md-12">
|
||||
<label>来源</label>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="form-group-sm">
|
||||
<label>搜索</label>
|
||||
<input name="keyWord" class="form-control" placeholder="请输入关键词" ms-duplex="keyword">
|
||||
<input type="submit" value="搜索" class="btn btn-primary" ms-click="getPage(1)">
|
||||
<input type="submit" value="搜索" class="btn btn-primary" ms-click="search()">
|
||||
</div>
|
||||
</form>
|
||||
<br>
|
||||
@@ -38,9 +38,7 @@
|
||||
<label>仅显示可见 <input ms-duplex-checked="showVisibleOnly" type="checkbox"/></label>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
页数:{{ page }}/{{ totalPage }}
|
||||
<button ms-attr-class="getBtnClass('pre')" ms-click="getPrevious">上一页</button>
|
||||
<button ms-attr-class="getBtnClass('next')" ms-click="getNext">下一页</button>
|
||||
<ms:pager $id="problemPager" config="pager"></ms:pager>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/js/app/admin/problem/problem.js"></script>
|
||||
@@ -27,9 +27,7 @@
|
||||
</tr>
|
||||
</table>
|
||||
<div class="text-right">
|
||||
页数:{{ page }}/{{ totalPage }}
|
||||
<button ms-attr-class="getBtnClass('pre')" ms-click="getPrevious">上一页</button>
|
||||
<button ms-attr-class="getBtnClass('next')" ms-click="getNext">下一页</button>
|
||||
<ms:pager $id="submissionsListPager" config="pager"></ms:pager>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/js/app/admin/problem/submissionList.js"></script>
|
||||
@@ -16,7 +16,6 @@
|
||||
<th>ID</th>
|
||||
<th>用户名</th>
|
||||
<th>注册时间</th>
|
||||
<th>最近登陆</th>
|
||||
<th>真实姓名</th>
|
||||
<th>电子邮箱</th>
|
||||
<th>用户类型</th>
|
||||
@@ -26,7 +25,6 @@
|
||||
<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>{{ userType[el.admin_type]}}</td>
|
||||
@@ -39,17 +37,15 @@
|
||||
<label>仅显示管理员 <input ms-duplex-checked="showAdminOnly" type="checkbox"/></label>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
页数:{{ page }}/{{ totalPage }}
|
||||
<button ms-attr-class="getBtnClass('pre')" ms-click="getPrevious">上一页</button>
|
||||
<button ms-attr-class="getBtnClass('next')" ms-click="getNext">下一页</button>
|
||||
<ms:pager $id="userPager" config="pager"></ms:pager>
|
||||
</div>
|
||||
<div ms-visible="editingUserId">
|
||||
<div ms-visible="isEditing">
|
||||
<h3>修改用户信息</h3>
|
||||
|
||||
<form id="edit_user-form">
|
||||
<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">
|
||||
<input name="id" type="number" class="form-control" readonly ms-duplex="userId">
|
||||
</div>
|
||||
<div class="form-group col-md-4"><label>用户名</label>
|
||||
<input name="username" type="text" class="form-control" ms-duplex="username"
|
||||
@@ -64,7 +60,7 @@
|
||||
</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"
|
||||
<input name="password" type="password" class="form-control" id="password" autocomplete="off"
|
||||
placeholder="此项留空则保留原密码" data-minlength="6" data-minlength-error="密码不得少于6位">
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
@@ -80,7 +76,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">提交</button>
|
||||
<button type="submit" class="btn btn-success">保存修改</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{% extends "oj_base.html" %}
|
||||
{% block title %}
|
||||
授权登录
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<div class="container main">
|
||||
<div class="text-center">
|
||||
{% if request.user.is_authenticated %}
|
||||
<p>3秒钟后将跳转到<span id="link">{{ callback }}</span></p>
|
||||
<script>setTimeout(function(){
|
||||
window.location.href = "{{ callback }}?token={{ token }}"},
|
||||
3000);
|
||||
</script>
|
||||
{% else %}
|
||||
<script>window.location.href = "/login/";</script>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block js_block %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,103 +0,0 @@
|
||||
{% extends 'oj_base.html' %}
|
||||
{% block title %}
|
||||
{{ contest_problem.title }}
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<div class="container main">
|
||||
<ul class="nav nav-tabs nav-tabs-google">
|
||||
<li role="presentation">
|
||||
<a href="/contest/{{ contest_problem.contest.id }}/problems/">题目列表</a>
|
||||
</li>
|
||||
<li role="presentation" class="active">
|
||||
<a href="/contest/{{ contest_problem.contest.id }}/problem/{{ contest_problem.id }}/">题目
|
||||
{{ contest_problem.sort_index }}</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="/contest/{{ contest_problem.contest.id }}/problem/{{ contest_problem.id }}/submissions/">我的提交</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h2 class="text-center">{{ contest_problem.title }}</h2>
|
||||
|
||||
<p class="text-muted text-center">发布时间 : {{ contest_problem.create_time }}
|
||||
时间限制 : {{ contest_problem.time_limit }}ms
|
||||
内存限制 : {{ contest_problem.memory_limit }}M
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">描述</label>
|
||||
|
||||
<div class="problem-detail">{{ contest_problem.description|safe }}</div>
|
||||
</div>
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">输入</label>
|
||||
|
||||
<p class="problem-detail">{{ contest_problem.input_description }}</p>
|
||||
|
||||
<div>
|
||||
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">输出</label>
|
||||
|
||||
<p class="problem-detail">{{ contest_problem.output_description }}</p>
|
||||
</div>
|
||||
{% for item in samples %}
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">样例输入{{ forloop.counter }}</label>
|
||||
<a href="javascript:void(0)" class="copy-sample"
|
||||
data-clipboard-text="{{ item.input }}">复制</a>
|
||||
<pre>{{ item.input }}</pre>
|
||||
</div>
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">样例输出{{ forloop.counter }}</label>
|
||||
<pre>{{ item.output }}</pre>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if contest_problem.hint %}
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">提示</label>
|
||||
<div class="problem-detail">{{ contest_problem.hint|safe }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if show_submit_code_area %}
|
||||
<div>
|
||||
<label>选择语言</label>
|
||||
<div>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="language" value="1" checked> C (GCC 4.8)
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="language" value="2"> C++ (G++ 4.3)
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="language" value="3"> Java (Oracle JDK 1.7)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="code-field">
|
||||
<label class="problem-label">提交代码</label>
|
||||
<textarea id="code-editor"></textarea>
|
||||
</div>
|
||||
<hr>
|
||||
<div id="submit-code">
|
||||
<button type="button" class="btn btn-primary" id="submit-code-button">
|
||||
提交代码
|
||||
</button>
|
||||
<img src="/static/img/loading.gif" id="loading-gif">
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if show_warning %}
|
||||
<div class="alert alert-success" role="alert">{{ warning }}</div>
|
||||
{% endif %}
|
||||
<div id="result">
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block js_block %}
|
||||
<script src="/static/js/app/oj/problem/problem.js"></script>
|
||||
{% endblock %}
|
||||
@@ -3,66 +3,66 @@
|
||||
比赛题目列表
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
{% load problem %}
|
||||
<div class="container main">
|
||||
{% load problem %}
|
||||
<div class="container main">
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 contest-tab">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 contest-tab">
|
||||
|
||||
<ul class="nav nav-tabs nav-tabs-google">
|
||||
<li role="presentation">
|
||||
<a href="/contest/{{ contest.id }}/">比赛详情</a>
|
||||
</li>
|
||||
<li role="presentation" class="active">
|
||||
<a href="/contest/{{ contest.id }}/problems/">题目列表</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="/contest/{{ contest.id }}/submissions/">提交</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="/contest/{{ contest.id }}/rank/?paging=true&page=1&page_size=40">排名</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav nav-tabs nav-tabs-google">
|
||||
<li role="presentation">
|
||||
<a href="/contest/{{ contest.id }}/">比赛详情</a>
|
||||
</li>
|
||||
<li role="presentation" class="active">
|
||||
<a href="/contest/{{ contest.id }}/problems/">题目列表</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="/contest/{{ contest.id }}/submissions/">提交</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="/contest/{{ contest.id }}/rank/?paging=true&page=1&page_size=40">排名</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-12 col-lg-12">
|
||||
<div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>#</th>
|
||||
<th>题目</th>
|
||||
<th>通过率</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in contest_problems %}
|
||||
<tr>
|
||||
<th>
|
||||
<span class="{% get_problem_status request.user.problems_status.contest_problems item.id %}"></span>
|
||||
</th>
|
||||
<th scope="row">
|
||||
<a href="/contest/{{ item.contest.id }}/problem/{{ item.id }}/">{{ item.sort_index }}</a>
|
||||
</th>
|
||||
<td>
|
||||
<a href="/contest/{{ item.contest.id }}/problem/{{ item.id }}/">{{ item.title }}</a>
|
||||
</td>
|
||||
<td>{{ item|accepted_radio }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-12 col-lg-12">
|
||||
<div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>#</th>
|
||||
<th>题目</th>
|
||||
<th>通过率</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in contest_problems %}
|
||||
<tr>
|
||||
<th>
|
||||
<span class="{% get_problem_status request.user.problems_status.contest_problems item.id %}"></span>
|
||||
</th>
|
||||
<th scope="row">
|
||||
<a href="/contest/{{ item.contest.id }}/problem/{{ item.id }}/">{{ item.sort_index }}</a>
|
||||
</th>
|
||||
<td>
|
||||
<a href="/contest/{{ item.contest.id }}/problem/{{ item.id }}/">{{ item.title }}</a>
|
||||
</td>
|
||||
<td>{{ item|accepted_radio }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js_block %}
|
||||
|
||||
@@ -75,14 +75,14 @@
|
||||
<ul class="pager">
|
||||
{% if paging_info.previous_page %}
|
||||
<li class="previous">
|
||||
<a href="/contest/{{ contest.id }}/rank/?paging=true&page={{ paging_info.previous_page }}&page_size={{ paging_info.page_size }}&auto_refresh={% if auto_refresh %}true{% else %}false{% endif %}">
|
||||
<a href="/contest/{{ contest.id }}/rank/?paging=true&page={{ paging_info.previous_page }}&page_size={{ paging_info.page_size }}{% if auto_refresh %}&auto_refresh=true{% endif %}">
|
||||
|
||||
<span aria-hidden="true">←</span> 上一页
|
||||
</a></li>
|
||||
{% endif %}
|
||||
{% if paging_info.next_page %}
|
||||
<li class="next">
|
||||
<a href="/contest/{{ contest.id }}/rank/?paging=true&page={{ paging_info.next_page }}&page_size={{ paging_info.page_size }}&auto_refresh={% if auto_refresh %}true{% else %}false{% endif %}">
|
||||
<a href="/contest/{{ contest.id }}/rank/?paging=true&page={{ paging_info.next_page }}&page_size={{ paging_info.page_size }}{% if auto_refresh %}&auto_refresh=true{% endif %}">
|
||||
下一页 <span aria-hidden="true">→</span>
|
||||
</a></li>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
{% extends 'oj_base.html' %}
|
||||
{% block title %}
|
||||
我的提交详情
|
||||
{% endblock %}
|
||||
{% block css_block %}
|
||||
<style>
|
||||
.CodeMirror {
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
{% load submission %}
|
||||
<div class="container main">
|
||||
<ul class="nav nav-tabs nav-tabs-google">
|
||||
<li role="presentation">
|
||||
<a href="/contest/{{ problem.contest.id }}/problem/{{ problem.id }}/">题目</a></li>
|
||||
<li role="presentation" class="active">
|
||||
<a href="/contest/{{ problem.contest.id }}/problem/{{ problem.id }}/submissions/">
|
||||
我的提交</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% include "oj/problem/_problem_header.html" %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<h4>运行结果 : <span class="text-{{ submission.result|translate_result_class }}">
|
||||
{{ submission.result|translate_result }}
|
||||
</span>
|
||||
</h4>
|
||||
{% ifequal submission.result 0 %}
|
||||
<p>时间 : {{ submission.accepted_answer_time }}ms 语言 :
|
||||
{{ submission.language|translate_language }}
|
||||
</p>
|
||||
{% endifequal %}
|
||||
{% ifequal submission.result 4 %}
|
||||
<p>{{ submission.info }}</p>
|
||||
{% endifequal %}
|
||||
<p>提交时间 : {{ submission.create_time }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% ifequal request.user.admin_type 2 %}
|
||||
<p>本调试信息仅超级管理员可见</p>
|
||||
{% ifequal submission.result 7 %}
|
||||
<pre>System Error: {{ submission.info }}</pre>
|
||||
{% else %}
|
||||
<pre>{{ info }}</pre>
|
||||
{% endifequal %}
|
||||
|
||||
{% endifequal %}
|
||||
|
||||
|
||||
<div id="code-field">
|
||||
<textarea id="code-editor">{{ submission.code }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block js_block %}
|
||||
<script>
|
||||
require(["jquery", "codeMirror"], function ($, codeMirror) {
|
||||
{% ifequal submission.language 1 %}
|
||||
var language = "text/x-csrc";
|
||||
{% else %}
|
||||
{% ifequal submission.language 2 %}
|
||||
var language = "text/x-c++src";
|
||||
{% else %}
|
||||
var language = "text/x-java";
|
||||
{% endifequal %}
|
||||
{% endifequal %}
|
||||
var codeEditor = codeMirror($("#code-editor")[0], language);
|
||||
codeEditor.setOption("readOnly", true);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,52 +0,0 @@
|
||||
{% extends 'oj_base.html' %}
|
||||
{% block title %}
|
||||
我的提交列表
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
|
||||
{% load submission %}
|
||||
<div class="container main">
|
||||
<ul class="nav nav-tabs nav-tabs-google">
|
||||
<li role="presentation">
|
||||
<a href="/contest/{{ problem.contest.id }}/problem/{{ problem.id }}/">题目</a></li>
|
||||
<li role="presentation" class="active">
|
||||
<a href="/contest/{{ problem.contest.id }}/problem/{{ problem.id }}/submissions/">
|
||||
我的提交</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% include "oj/problem/_problem_header.html" %}
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>提交时间</th>
|
||||
<th>语言</th>
|
||||
<th>运行时间</th>
|
||||
<th>结果</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in submissions %}
|
||||
<tr>
|
||||
<th scope="row"><a href="/submission/{{ item.id }}/">{{ forloop.counter }}</a></th>
|
||||
<td>{{ item.create_time }}</td>
|
||||
<td>
|
||||
{{ item.language|translate_language }}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.accepted_answer_time %}
|
||||
{{ item.accepted_answer_time }}ms
|
||||
{% else %}
|
||||
--
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="alert-{{ item.result|translate_result_class }}">
|
||||
<strong>{{ item.result|translate_result }}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,6 +1,8 @@
|
||||
<h2 class="text-center">{{ problem.title }}</h2>
|
||||
|
||||
<p class="text-muted text-center">发布时间 : {{ problem.create_time }}
|
||||
时间限制 : {{ problem.time_limit }}ms
|
||||
内存限制 : {{ problem.memory_limit }}M
|
||||
<p class="text-muted text-center">
|
||||
发布时间: {{ problem.create_time }}
|
||||
{% if problem.last_update_time %}最后更新: {{ problem.last_update_time }} {% endif %}
|
||||
时间限制: {{ problem.time_limit }}ms
|
||||
内存限制: {{ problem.memory_limit }}M
|
||||
</p>
|
||||
@@ -0,0 +1,29 @@
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">描述</label>
|
||||
|
||||
<div class="problem-detail">{{ problem.description|safe }}</div>
|
||||
</div>
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">输入</label>
|
||||
|
||||
<p class="problem-detail">{{ problem.input_description }}</p>
|
||||
</div>
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">输出</label>
|
||||
|
||||
<p class="problem-detail">{{ problem.output_description }}</p>
|
||||
</div>
|
||||
{% for item in samples %}
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">样例输入{{ forloop.counter }}</label>
|
||||
<a href="javascript:void(0)" class="copy-sample" data-clipboard-text="{{ item.input }}">复制</a>
|
||||
<pre>
|
||||
{{ item.input }}</pre>
|
||||
|
||||
</div>
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">样例输出{{ forloop.counter }}</label>
|
||||
<pre>
|
||||
{{ item.output }}</pre>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,25 @@
|
||||
<div>
|
||||
<label>选择语言</label>
|
||||
<div>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="language" value="1" checked> C (GCC 4.8)
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="language" value="2"> C++ (G++ 4.3)
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="language" value="3"> Java (Oracle JDK 1.7)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="code-field">
|
||||
<label class="problem-label">提交代码</label>
|
||||
<textarea id="code-editor"></textarea>
|
||||
</div>
|
||||
<hr>
|
||||
<div id="submit-code">
|
||||
<button type="button" class="btn btn-primary" id="submit-code-button">
|
||||
提交代码
|
||||
</button>
|
||||
<img src="/static/img/loading.gif" id="loading-gif">
|
||||
</div>
|
||||
@@ -0,0 +1,42 @@
|
||||
{% extends 'oj_base.html' %}
|
||||
{% block title %}
|
||||
{{ problem.title }}
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<div class="container main">
|
||||
<ul class="nav nav-tabs nav-tabs-google">
|
||||
<li role="presentation">
|
||||
<a href="/contest/{{ problem.contest.id }}/problems/">题目列表</a>
|
||||
</li>
|
||||
<li role="presentation" class="active">
|
||||
<a href="/contest/{{ problem.contest.id }}/problem/{{ problem.id }}/">题目{{ problem.sort_index }}</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="/contest/{{ problem.contest.id }}/problem/{{ problem.id }}/submissions/">我的提交</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% include "oj/problem/_problem_header.html" %}
|
||||
<div>
|
||||
{% include "oj/problem/_problem_main_info.html" %}
|
||||
{% if problem.hint %}
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">提示</label>
|
||||
|
||||
<div class="problem-detail">{{ problem.hint|safe }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if show_submit_code_area %}
|
||||
{% include "oj/problem/_submit_problem.html" %}
|
||||
{% endif %}
|
||||
{% if show_warning %}
|
||||
<div class="alert alert-success" role="alert">{{ warning }}</div>
|
||||
{% endif %}
|
||||
<div id="result">
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block js_block %}
|
||||
<script src="/static/js/app/oj/problem/problem.js"></script>
|
||||
{% endblock %}
|
||||
@@ -15,35 +15,7 @@
|
||||
{% include "oj/problem/_problem_header.html" %}
|
||||
|
||||
<div>
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">描述</label>
|
||||
|
||||
<div class="problem-detail">{{ problem.description|safe }}</div>
|
||||
</div>
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">输入</label>
|
||||
|
||||
<p class="problem-detail">{{ problem.input_description }}</p>
|
||||
</div>
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">输出</label>
|
||||
|
||||
<p class="problem-detail">{{ problem.output_description }}</p>
|
||||
</div>
|
||||
{% for item in samples %}
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">样例输入{{ forloop.counter }}</label>
|
||||
<a href="javascript:void(0)" class="copy-sample" data-clipboard-text="{{ item.input }}">复制</a>
|
||||
<pre>
|
||||
{{ item.input }}</pre>
|
||||
|
||||
</div>
|
||||
<div class="problem-section">
|
||||
<label class="problem-label">样例输出{{ forloop.counter }}</label>
|
||||
<pre>
|
||||
{{ item.output }}</pre>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% include "oj/problem/_problem_main_info.html" %}
|
||||
<div>
|
||||
<button type="button" id="show-more-btn" class="btn btn-default btn-sm">查看隐藏信息</button>
|
||||
</div>
|
||||
@@ -67,31 +39,7 @@
|
||||
<div class="problem-detail">{{ problem.source }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<label>选择语言</label>
|
||||
<div>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="language" value="1" checked> C (GCC 4.8)
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="language" value="2"> C++ (G++ 4.3)
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="language" value="3"> Java (Oracle JDK 1.7)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="code-field">
|
||||
<label class="problem-label">提交代码</label>
|
||||
<textarea id="code-editor"></textarea>
|
||||
</div>
|
||||
<hr>
|
||||
<div id="submit-code">
|
||||
<button type="button" class="btn btn-primary" id="submit-code-button">
|
||||
提交代码
|
||||
</button>
|
||||
<img src="/static/img/loading.gif" id="loading-gif">
|
||||
</div>
|
||||
{% include "oj/problem/_submit_problem.html" %}
|
||||
<div id="result">
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
+3
-3
@@ -36,7 +36,7 @@
|
||||
{% include "oj/problem/_problem_header.html" %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<h4>运行结果 : <span class="text-{{ submission.result|translate_result_class }}">
|
||||
<h4>运行结果: <span class="text-{{ submission.result|translate_result_class }}">
|
||||
{{ submission.result|translate_result }}
|
||||
</span>
|
||||
</h4>
|
||||
@@ -44,14 +44,14 @@
|
||||
<p>作者:{{ user.username }}</p>
|
||||
{% endifnotequal %}
|
||||
{% ifequal submission.result 0 %}
|
||||
<p>时间 : {{ submission.accepted_answer_time }}ms 语言 :
|
||||
<p>时间: {{ submission.accepted_answer_time }}ms 语言 :
|
||||
{{ submission.language|translate_language }}
|
||||
</p>
|
||||
{% endifequal %}
|
||||
{% ifequal submission.result 4 %}
|
||||
<pre>{{ submission.info }}</pre>
|
||||
{% endifequal %}
|
||||
<p>提交时间 : {{ submission.create_time }}</p>
|
||||
<p>提交时间: {{ submission.create_time }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% ifequal request.user.admin_type 2 %}
|
||||
+13
-4
@@ -7,10 +7,19 @@
|
||||
{% load submission %}
|
||||
<div class="container main">
|
||||
<ul class="nav nav-tabs nav-tabs-google">
|
||||
<li role="presentation">
|
||||
<a href="/problem/{{ problem.id }}/">题目</a></li>
|
||||
<li role="presentation" class="active">
|
||||
<a href="/problem/{{ problem.id }}/submissions/">我的提交</a></li>
|
||||
{% if problem.contest_id %}
|
||||
<li role="presentation">
|
||||
<a href="/contest/{{ problem.contest.id }}/problem/{{ problem.id }}/">题目</a></li>
|
||||
<li role="presentation" class="active">
|
||||
<a href="/contest/{{ problem.contest.id }}/problem/{{ problem.id }}/submissions/">我的提交</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li role="presentation">
|
||||
<a href="/problem/{{ problem.id }}/">题目</a></li>
|
||||
<li role="presentation" class="active">
|
||||
<a href="/problem/{{ problem.id }}/submissions/">我的提交</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% include "oj/problem/_problem_header.html" %}
|
||||
{% if submissions %}
|
||||
@@ -8,7 +8,7 @@
|
||||
<tbody>
|
||||
<tr height="39" style="background-color:#50a5e6;">
|
||||
<td style="padding-left:15px;font-family:'微软雅黑','黑体',arial;">
|
||||
Online Judge
|
||||
{{ website_name }} 密码找回邮件
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -32,7 +32,7 @@
|
||||
</tr>
|
||||
<tr height="30">
|
||||
<td style="padding-left:55px;padding-right:55px;font-family:'微软雅黑','黑体',arial;font-size:14px;">
|
||||
您刚刚在 青岛大学在线评测系统 使用了找回密码功能。
|
||||
您刚刚在 {{ website_name }} 使用了找回密码功能。
|
||||
</td>
|
||||
</tr>
|
||||
<tr height="30">
|
||||
|
||||
@@ -5,7 +5,7 @@ from announcement.models import Announcement
|
||||
|
||||
|
||||
def public_announcement_list():
|
||||
return Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
|
||||
return Announcement.objects.filter(visible=True).order_by("-create_time")
|
||||
|
||||
register = template.Library()
|
||||
register.assignment_tag(public_announcement_list, name="public_announcement_list")
|
||||
Reference in New Issue
Block a user