mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2024-09-21 16:33:22 +00:00
Accept Merge Request #279 virusdefender-dev -> dev : (virusdefender-dev -> dev)
Merge Request: virusdefender-dev -> dev Created By: @virusdefender Accepted By: @virusdefender URL: https://coding.net/u/virusdefender/p/qduoj/git/merge/279
This commit is contained in:
commit
190565da88
@ -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"),
|
||||
]
|
||||
|
289
account/tests.py
289
account/tests.py
@ -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")
|
||||
|
@ -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
|
22
announcement/migrations/0004_auto_20151015_1555.py
Normal file
22
announcement/migrations/0004_auto_20151015_1555.py
Normal file
@ -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=[])
|
||||
|
@ -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")
|
||||
|
@ -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"))
|
||||
|
@ -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:
|
||||
# 没有输入过密码
|
||||
|
64
contest/migrations/0013_auto_20151017_1511.py
Normal file
64
contest/migrations/0013_auto_20151017_1511.py
Normal file
@ -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',
|
||||
),
|
||||
]
|
@ -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)
|
||||
|
@ -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")
|
||||
|
||||
|
||||
|
||||
|
118
contest/views.py
118
contest/views.py
@ -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)
|
@ -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"])
|
||||
|
@ -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',
|
||||
)
|
||||
|
||||
|
14
oj/urls.py
14
oj/urls.py
@ -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
|
||||
|
24
problem/migrations/0010_auto_20151017_1226.py
Normal file
24
problem/migrations/0010_auto_20151017_1226.py
Normal file
@ -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),
|
||||
),
|
||||
]
|
20
problem/migrations/0011_auto_20151017_1227.py
Normal file
20
problem/migrations/0011_auto_20151017_1227.py
Normal file
@ -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),
|
||||
),
|
||||
]
|
@ -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)
|
||||
|
@ -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/')
|
||||
|
@ -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),
|
||||
|
BIN
static/src/css/fonts/source_code_pro/EOT/SourceCodePro-Regular.eot
Executable file
BIN
static/src/css/fonts/source_code_pro/EOT/SourceCodePro-Regular.eot
Executable file
Binary file not shown.
BIN
static/src/css/fonts/source_code_pro/OTF/SourceCodePro-Regular.otf
Executable file
BIN
static/src/css/fonts/source_code_pro/OTF/SourceCodePro-Regular.otf
Executable file
Binary file not shown.
BIN
static/src/css/fonts/source_code_pro/TTF/SourceCodePro-Regular.ttf
Executable file
BIN
static/src/css/fonts/source_code_pro/TTF/SourceCodePro-Regular.ttf
Executable file
Binary file not shown.
BIN
static/src/css/fonts/source_code_pro/WOFF/OTF/SourceCodePro-Regular.otf.woff
Executable file
BIN
static/src/css/fonts/source_code_pro/WOFF/OTF/SourceCodePro-Regular.otf.woff
Executable file
Binary file not shown.
BIN
static/src/css/fonts/source_code_pro/WOFF/TTF/SourceCodePro-Regular.ttf.woff
Executable file
BIN
static/src/css/fonts/source_code_pro/WOFF/TTF/SourceCodePro-Regular.ttf.woff
Executable file
Binary file not shown.
BIN
static/src/css/fonts/source_code_pro/WOFF2/OTF/SourceCodePro-Regular.otf.woff2
Executable file
BIN
static/src/css/fonts/source_code_pro/WOFF2/OTF/SourceCodePro-Regular.otf.woff2
Executable file
Binary file not shown.
BIN
static/src/css/fonts/source_code_pro/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2
Executable file
BIN
static/src/css/fonts/source_code_pro/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2
Executable file
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"
|
||||
},
|
||||
|
16
static/src/js/components/editorComponent.js
Normal file
16
static/src/js/components/editorComponent.js
Normal file
@ -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);
|
||||
})
|
||||
}
|
||||
})
|
||||
});
|
32
static/src/js/components/pager.js
Normal file
32
static/src/js/components/pager.js
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
};
|
File diff suppressed because it is too large
Load Diff
5718
static/src/js/lib/avalon/avalon14.js
Normal file
5718
static/src/js/lib/avalon/avalon14.js
Normal file
File diff suppressed because it is too large
Load Diff
138
static/src/js/lib/simditor/simditor-autosave.js
Normal file
138
static/src/js/lib/simditor/simditor-autosave.js
Normal file
@ -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)
|
||||
|
||||
|
||||
|
@ -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)
|
@ -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>
|
||||
|
24
template/src/oj/account/oauth.html
Normal file
24
template/src/oj/account/oauth.html
Normal file
@ -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>
|
29
template/src/oj/problem/_problem_main_info.html
Normal file
29
template/src/oj/problem/_problem_main_info.html
Normal file
@ -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 %}
|
25
template/src/oj/problem/_submit_problem.html
Normal file
25
template/src/oj/problem/_submit_problem.html
Normal file
@ -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>
|
42
template/src/oj/problem/contest_problem.html
Normal file
42
template/src/oj/problem/contest_problem.html
Normal file
@ -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>
|
||||
|
@ -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 %}
|
@ -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")
|
Loading…
Reference in New Issue
Block a user