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:
virusdefender 2015-10-18 11:46:18 +08:00
commit 190565da88
83 changed files with 12582 additions and 6869 deletions

View File

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

View File

@ -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):
@ -51,4 +51,10 @@ class EditUserSerializer(serializers.Serializer):
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)

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@ -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):
@ -29,6 +27,4 @@ class EditAnnouncementSerializer(serializers.Serializer):
id = serializers.IntegerField()
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=[])
visible = serializers.BooleanField()

View File

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

View File

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

View File

@ -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:
# 没有输入过密码

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

View File

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

View File

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

View File

@ -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})
@ -441,4 +445,94 @@ class ContestTimeAPIView(APIView):
return error_response(u"比赛不存在")
return success_response({"start": int((contest.start_time - now()).total_seconds() * 1000),
"end": int((contest.end_time - now()).total_seconds() * 1000),
"status": contest.status})
"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})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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),
),
]

View 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),
),
]

View File

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

View File

@ -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,15 +61,11 @@ 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()
tags = serializers.ListField(child=serializers.CharField(max_length=20))
samples = ProblemSampleSerializer()
hint = serializers.CharField(max_length=3000, allow_blank=True)
visible = serializers.BooleanField()
class CreateProblemTagSerializer(serializers.Serializer):
name = serializers.CharField(max_length=10)
visible = serializers.BooleanField()

View File

@ -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/')

View File

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

View File

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

View File

@ -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("请求失败");
}
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"
},

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

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

View File

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

File diff suppressed because it is too large Load Diff

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 }}&nbsp;&nbsp;
<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">&nbsp;&nbsp;{{ el.name }}
</div>
</div>
<div class="form-group">
<button ms-click="submitChange()" class="btn btn-success">保存修改</button>
&nbsp;&nbsp;
<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">&nbsp;&nbsp;{{ el.name }}
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success">发布公告</button>
</div>

View File

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

View File

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

View File

@ -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 }}&nbsp;&nbsp;
<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>

View File

@ -27,9 +27,7 @@
</tr>
</table>
<div class="text-right">
页数:{{ page }}/{{ totalPage }}&nbsp;&nbsp;
<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>

View File

@ -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 }}&nbsp;&nbsp;
<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>

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

View File

@ -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 }}&nbsp;&nbsp;
时间限制 : {{ contest_problem.time_limit }}ms&nbsp;&nbsp;
内存限制 : {{ 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 %}

View File

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

View File

@ -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">&larr;</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">&rarr;</span>
</a></li>
{% endif %}

View File

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

View File

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

View File

@ -1,6 +1,8 @@
<h2 class="text-center">{{ problem.title }}</h2>
<p class="text-muted text-center">发布时间 : {{ problem.create_time }}&nbsp;&nbsp;
时间限制 : {{ problem.time_limit }}ms&nbsp;&nbsp;
内存限制 : {{ problem.memory_limit }}M
<p class="text-muted text-center">
发布时间: {{ problem.create_time }}&nbsp;&nbsp;
{% if problem.last_update_time %}最后更新: {{ problem.last_update_time }}&nbsp;&nbsp;{% endif %}
时间限制: {{ problem.time_limit }}ms&nbsp;&nbsp;
内存限制: {{ problem.memory_limit }}M
</p>

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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