Merge branch 'dev' into virusdefender-dev

* dev: (34 commits)
  [Bugs]修复后台管理比赛,题目列表可见不可的bug
  去掉多余注释[CI SKIP]
  添加题目列表检索包含描述字段
  [前台]添加了小组列表页面,带测试,不全覆盖
  [后端]添加比赛,题目列表页对描述的检索(原来仅对标题检索关键字)
  [后台]我的提交列表不再返回比赛提交
  修改前台提交列表的显示样式,使之与其他列表形式更加统一,修改显示的颜色有原来的class=success等等改为alert-success,并增加strong标签[CI SKIP]
  [前端]修改统一首页和oj_base中导航栏项目,添加首页登录,及登录状态区(这个使用css的float:right方式固定在右侧,可以动态响应窗口大小变化,只是与导航栏位置少不对称了.由于没有引入bootstraps所以没有使用与oj_base里相同的下拉菜单来显示用户登录后的选项
  [后端]修改我的提交列表样式,方便筛选
  [后端]添加昨天修改的我的提交列表页面的测试,全覆盖了
  [后端]添加提交列表筛选功能,按语言,题目,测试还没写
  [前台]添加比赛模块里rank,submission页面的列表是空的情况的提示(修改模板)
  [前台]添加提交列表的公告栏
  [后端]修改注销views的函数名称
  [后端-前台]修复比赛列表仅显示可参加的比赛的问题,小bug,否则选择查看可参加的比赛有可能显示不可见的比赛
  [前端]修改前台页面用户登录状态下拉菜单,完成注销功能,在登录页面添加注册链接
  [前端]添加比赛列表为空的判断
  [后端-前台]增加用户注销功能,这个并没有做成api,感觉这个做成api可能没什么必要...所以干脆写了地址重定向到首页了,测试通过account.tests.py
  [前端-后台]增强radio的易用性,使用label包裹,便于用户点到[CI SKIP]
  [前端-后台]再次修改部分措辞,修改boolen值的显示方法(再次改进)[CI SKIP]
  ...
This commit is contained in:
virusdefender 2015-08-30 11:55:07 +08:00
commit 48df067491
43 changed files with 1080 additions and 576 deletions

View File

@ -369,3 +369,18 @@ class AdminRequiredDecoratorTest(TestCase):
self.client.login(username="test", password="test")
response = self.client.get("/admin_required_test/cbv/1024/")
self.assertEqual(response.content, "1024")
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()
def logout_success(self):
self.client = Client()
self.client.login(username="test", password="1")
response = self.client.get("/logout/")
self.assertEqual(response.status_code, 302)

View File

@ -1,4 +1,5 @@
# coding=utf-8
from django import http
from django.contrib import auth
from django.shortcuts import render
from django.db.models import Q
@ -35,6 +36,10 @@ class UserLoginAPIView(APIView):
return serializer_invalid_response(serializer)
def logout(request):
auth.logout(request)
return http.HttpResponseRedirect("/")
class UserRegisterAPIView(APIView):
def post(self, request):
"""

View File

@ -343,12 +343,12 @@ def contest_list_page(request, page=1):
# 搜索的情况
keyword = request.GET.get("keyword", None)
if keyword:
contests = contests.filter(title__contains=keyword)
contests = contests.filter(Q(title__contains=keyword) | Q(description__contains=keyword))
# 筛选我能参加的比赛
join = request.GET.get("join", None)
if join:
contests = Contest.objects.filter(Q(contest_type__in=[1, 2]) | Q(groups__in=request.user.group_set.all())).\
contests = contests.filter(Q(contest_type__in=[1, 2]) | Q(groups__in=request.user.group_set.all())).\
filter(end_time__gt=datetime.datetime.now(), start_time__lt=datetime.datetime.now())
paginator = Paginator(contests, 20)

View File

@ -1,3 +1,84 @@
from django.test import TestCase
import json
from django.core.urlresolvers import reverse
from account.models import User, ADMIN, SUPER_ADMIN
from contest.models import Contest, ContestProblem
from submission.models import Submission
from rest_framework.test import APITestCase, APIClient
# Create your tests here.
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=2, 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

@ -21,6 +21,7 @@ from utils.shortcuts import serializer_invalid_response, error_response, success
from submission.models import Submission
from .serializers import CreateContestSubmissionSerializer
from submission.serializers import SubmissionSerializer
class ContestSubmissionAPIView(APIView):
@ -76,7 +77,8 @@ def contest_problem_my_submissions_list_page(request, contest_id, contest_proble
contest_problem = ContestProblem.objects.get(id=contest_problem_id, visible=True)
except Problem.DoesNotExist:
return error_page(request, u"比赛问题不存在")
submissions = Submission.objects.filter(user_id=request.user.id, problem_id=contest_problem.id).order_by("-create_time"). \
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})
@ -112,4 +114,38 @@ def contest_problem_submissions_list_page(request, contest_id, page=1):
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})
"contest": contest})
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

@ -8,6 +8,8 @@ from rest_framework.test import APITestCase, APIClient
from account.models import User, REGULAR_USER, ADMIN, SUPER_ADMIN
from group.models import Group, UserGroupRelation, JoinGroupRequest
from django.test import TestCase, Client
class GroupAPITest(APITestCase):
pass
@ -254,3 +256,28 @@ class JoinGroupRequestAdminAPITest(APITestCase):
response = self.client.put(self.url, data=data)
self.assertEqual(response.data, {"code": 1, "data": u"加入失败,已经在本小组内"})
class ProblemListPageTest(TestCase):
def setUp(self):
self.client = Client()
self.url = reverse('group_list_page')
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.group = Group.objects.create(name="group1",
description="description1",
# 0是公开 1是需要申请后加入 2是不允许任何人加入
join_group_setting = 1,
admin=User.objects.get(username="test"))
def get_group_list_page_successful(self):
self.client.login(username="test", password="testaa")
response = self.client.get(self.url)
self.assertEqual(response.status_coed, 200)
def get_group_list_page_successful_with_keyword(self):
self.client.login(username="test", password="testaa")
response = self.client.get(self.url+"?keyword=gro")
self.assertEqual(response.status_coed, 200)

View File

@ -4,7 +4,7 @@ from django.db import IntegrityError
from rest_framework.views import APIView
from utils.shortcuts import error_response, serializer_invalid_response, success_response, paginate
from utils.shortcuts import error_response, serializer_invalid_response, success_response, paginate, error_page
from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN
from account.decorators import login_required
@ -13,6 +13,9 @@ from .serializers import (CreateGroupSerializer, EditGroupSerializer,
CreateJoinGroupRequestSerializer, GroupSerializer,
GroupMemberSerializer, EditGroupMemberSerializer,
JoinGroupRequestSerializer, PutJoinGroupRequestSerializer)
from announcement.models import Announcement
from django.core.paginator import Paginator
from django.db.models import Q
class GroupAPIViewBase(object):
@ -26,7 +29,7 @@ class GroupAPIViewBase(object):
else:
group = Group.objects.get(id=group_id, visible=True, admin=request.user)
return group
def get_groups(self, request):
"""
如果是超级管理员就返回全部的小组
@ -113,8 +116,8 @@ class GroupAdminAPIView(APIView, GroupAPIViewBase):
elif request.GET.get("admin_id", None):
groups = groups.filter(admin__id=request.GET["admin_id"])
return paginate(request, groups, GroupSerializer)
class GroupMemberAdminAPIView(APIView, GroupAPIViewBase):
def get(self, request):
"""
@ -129,9 +132,9 @@ class GroupMemberAdminAPIView(APIView, GroupAPIViewBase):
group = self.get_group(request, group_id)
except Group.DoesNotExist:
return error_response(u"小组不存在")
return paginate(request, UserGroupRelation.objects.filter(group=group), GroupMemberSerializer)
def put(self, request):
"""
删除小组成员的api接口
@ -190,7 +193,7 @@ class JoinGroupAPIView(APIView):
return error_response(u"该小组不允许任何人加入")
else:
return serializer_invalid_response(serializer)
def get(self, request):
"""
搜索小组的api需要传递keyword参数
@ -244,3 +247,39 @@ class JoinGroupRequestAdminAPIView(APIView, GroupAPIViewBase):
else:
return serializer_invalid_response(serializer)
@login_required
def group_list_page(request, page=1):
# 右侧的公告列表
announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
groups = Group.objects.filter(visible=True, join_group_setting__lte=2)
# 搜索的情况
keyword = request.GET.get("keyword", None)
if keyword:
groups = groups.filter(Q(name__contains=keyword) | Q(description__contains=keyword))
paginator = Paginator(groups, 20)
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
next_page = None
try:
next_page = current_page.next_page_number()
except Exception:
pass
return render(request, "oj/group/group_list.html", {
"groups": groups, "announcements": announcements,
"contests": current_page, "page": int(page),
"previous_page": previous_page, "next_page": next_page,
"keyword": keyword, "announcements": announcements,
})

View File

@ -1,11 +1,11 @@
# coding=utf-8
from django.conf.urls import include, url
from django.contrib import admin
from django.views.generic import TemplateView
from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView,
UserChangePasswordAPIView, EmailCheckAPIView,
UserAdminAPIView, UserInfoAPIView)
from announcement.views import AnnouncementAdminAPIView
from contest.views import ContestAdminAPIView, ContestProblemAdminAPIView, ContestPasswordVerifyAPIView
@ -17,7 +17,7 @@ from admin.views import AdminTemplateView
from problem.views import TestCaseUploadAPIView, ProblemTagAdminAPIView, ProblemAdminAPIView
from submission.views import SubmissionAPIView, SubmissionAdminAPIView
from contest_submission.views import ContestSubmissionAPIView
from contest_submission.views import ContestSubmissionAPIView, ContestSubmissionAdminAPIView
from monitor.views import QueueLengthMonitorAPIView
from contest_submission.views import contest_problem_my_submissions_list_page
@ -34,6 +34,7 @@ urlpatterns = [
name="admin_template"),
url(r'^login/$', TemplateView.as_view(template_name="oj/account/login.html"), name="user_login_page"),
url(r'^logout/$', "account.views.logout", name="user_logout_api"),
url(r'^register/$', TemplateView.as_view(template_name="oj/account/register.html"),
name="user_register_page"),
url(r'^change_password/$', TemplateView.as_view(template_name="oj/account/change_password.html"),
@ -65,7 +66,7 @@ urlpatterns = [
name="join_group_request_admin_api"),
url(r'^api/admin/submission/$', SubmissionAdminAPIView.as_view(), name="submission_admin_api_view"),
url(r'^api/admin/monitor/$', QueueLengthMonitorAPIView.as_view(), name="queue_length_monitor_api"),
url(r'^api/admin/contest_submission/$', ContestSubmissionAdminAPIView.as_view(), name="contest_submission_admin_api_view"),
url(r'^contest/(?P<contest_id>\d+)/problem/(?P<contest_problem_id>\d+)/$', "contest.views.contest_problem_page",
@ -99,6 +100,9 @@ urlpatterns = [
url(r'^submissions/$', "submission.views.my_submission_list_page", name="my_submission_list_page"),
url(r'^submissions/(?P<page>\d+)/$', "submission.views.my_submission_list_page", name="my_submission_list_page"),
url(r'^contest/(?P<contest_id>\d+)/rank/$', "contest.views.contest_rank_page", name="contest_rank_page")
url(r'^contest/(?P<contest_id>\d+)/rank/$', "contest.views.contest_rank_page", name="contest_rank_page"),
url(r'^groups/$', "group.views.group_list_page", name="group_list_page"),
url(r'^groups/(?P<page>\d+)/$', "group.views.group_list_page", name="group_list_page")
]

View File

@ -222,7 +222,7 @@ def problem_list_page(request, page=1):
# 搜索的情况
keyword = request.GET.get("keyword", None)
if keyword:
problems = problems.filter(title__contains=keyword)
problems = problems.filter(Q(title__contains=keyword) | Q(description__contains=keyword))
# 按照标签筛选
tag_text = request.GET.get("tag", None)

View File

@ -55,3 +55,12 @@
list-style-type: none;
margin: 5px;
}
.error-info {
color: #dd4b39;
font-family:
Arial,Helvetica,sans-serif;
font-size: 13px;
line-height: 1.4;
font-weight: 600;
}

View File

@ -133,11 +133,17 @@ define("admin", ["jquery", "avalon"], function ($, avalon) {
vm.template_url = "template/contest/edit_problem.html";
});
vm.$watch("showContestListPage", function (problemId) {
vm.problemId = problemId;
vm.$watch("showContestListPage", function () {
vm.template_url = "template/contest/contest_list.html";
});
vm.$watch("showContestSubmissionPage", function (problemId, contestId, contestMode) {
vm.$problemId = problemId;
vm.$contestId = contestId;
vm.$contestMode = contestMode
vm.template_url = "template/contest/submission_list.html";
});
avalon.scan();
window.onhashchange = function () {

View File

@ -1,163 +1,163 @@
require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
function ($, avalon, csrfTokenHeader, bsAlert, editor) {
avalon.ready(function () {
avalon.vmodels.announcement = null;
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, //仅显示可见公告
// 编辑
newTitle: "",
announcementVisible: 0,
showGlobalViewRadio: true,
isGlobal: true,
allGroups: [],
getState: function (el) { //获取公告当前状态,显示
if (el.visible)
return "可见";
else
return "隐藏";
},
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";
}
},
editAnnouncement: function (announcement) {
vm.newTitle = announcement.title;
editAnnouncementEditor.setValue(announcement.content);
vm.announcementVisible = announcement.visible;
if (vm.editingAnnouncementId == announcement.id)
var vm = avalon.define({
$id: "announcement",
//通用变量
announcementList: [], // 公告列表数据项
previousPage: 0, // 之前的页数
nextPage: 0, // 之后的页数
page: 1, // 当前页数
editingAnnouncementId: 0, // 正在编辑的公告的ID 为零说明未在编辑
totalPage: 1, // 总页数
showVisibleOnly: false, //仅显示可见公告
// 编辑
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";
}
},
editAnnouncement: function (announcement) {
vm.newTitle = announcement.title;
editAnnouncementEditor.setValue(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();
},
cancelEdit: function () {
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;
},
submitChange: function () {
var title = vm.newTitle;
var content = editAnnouncementEditor.getValue();
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);
}
}
}
}
editAnnouncementEditor.focus();
},
cancelEdit: function () {
vm.editingAnnouncementId = 0;
},
submitChange: function () {
var title = vm.newTitle;
var content = editAnnouncementEditor.getValue();
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;
}
}
if (!vm.isGlobal && !selectedGroups.length) {
bsAlert("请至少选择一个小组");
return false;
}
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/admin/announcement/",
contentType: "application/json",
dataType: "json",
method: "put",
data: JSON.stringify({
id: vm.editingAnnouncementId,
title: title,
content: content,
visible: vm.announcementVisible,
is_global: vm.isGlobal,
groups: selectedGroups
}),
success: function (data) {
if (!data.code) {
bsAlert("修改成功");
vm.editingAnnouncementId = 0;
getPageData(1);
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/admin/announcement/",
contentType: "application/json",
dataType: "json",
method: "put",
data: JSON.stringify({
id: vm.editingAnnouncementId,
title: title,
content: content,
visible: vm.announcementVisible,
is_global: vm.isGlobal,
groups: selectedGroups
}),
success: function (data) {
if (!data.code) {
bsAlert("修改成功");
vm.editingAnnouncementId = 0;
getPageData(1);
}
else {
bsAlert(data.data);
}
}
else {
bsAlert(data.data);
}
}
});
});
}
});
vm.$watch("showVisibleOnly", function () {
getPageData(1);
});
}
});
vm.$watch("showVisibleOnly", function () {
getPageData(1);
});
}
getPageData(1);
$.ajax({
url: "/api/admin/group/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
if (!data.data.length) {
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);
}
}
});
$.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);
}
}
});
}
});

View File

@ -131,7 +131,6 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
success: function (data) {
if (!data.code) {
if (!data.data.length) {
bsAlert("您的用户权限只能创建组内比赛,但是您还没有创建过小组");
return;
}
for (var i = 0; i < data.data.length; i++) {

View File

@ -234,15 +234,17 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
vm.groupList[vm.choseGroupList[groupIndex].index].chose = false;
vm.choseGroupList.remove(vm.choseGroupList[groupIndex]);
},
add_problem: function () {
addProblem: function () {
vm.$fire("up!showContestProblemPage", 0, vm.contestList[vm.editingProblemContestIndex-1].id, vm.editMode);
},
showProblemEditor: function(el) {
showProblemEditPage: function(el) {
vm.$fire("up!showContestProblemPage", el.id, vm.contestList[vm.editingProblemContestIndex-1].id, vm.editMode);
},
getYesOrNo: function(yORn) {
if (yORn) return "是";
return "否";
showSubmissionPage: function(el) {
var problemId = 0
if (el)
problemId = el.id;
vm.$fire("up!showContestSubmissionPage", problemId, vm.contestList[vm.editingProblemContestIndex-1].id, vm.editMode);
}
});
vm.$watch("showVisibleOnly", function() {

View File

@ -0,0 +1,88 @@
require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfTokenHeader, bsAlert) {
avalon.ready(function () {
if (avalon.vmodels.contestSubmissionList){
var vm = avalon.vmodels.contestSubmissionList;
}
else {
var vm = avalon.define({
$id: "contestSubmissionList",
submissionList: [],
previousPage: 0,
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);
},
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";
}
},
getPage: function (page_index) {
if (!page_index)
var page_index = vm.page;
getPageData(page_index);
},
showSubmissionDetailPage: function (submissionId) {
},
goBack: function(check){
vm.$fire("up!showContestListPage");
}
});
}
getPageData(1);
function getPageData(page) {
var url = "/api/admin/contest_submission/?paging=true&page=" + page + "&page_size=10&contest_id=" + avalon.vmodels.admin.$contestId;
if (avalon.vmodels.admin.$problemId)
url += "&problem_id=" + avalon.vmodels.admin.$problemId
$.ajax({
url: url,
dataType: "json",
method: "get",
success: function (data) {
if (!data.code) {
vm.submissionList = 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);
}
}
});
}
});
avalon.scan();
});

View File

@ -1,51 +1,57 @@
require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfTokenHeader, bsAlert) {
avalon.ready(function () {
avalon.vmodels.group = null;
var vm = avalon.define({
$id: "group",
//通用变量
groupList: [], // 用户列表数据项
previousPage: 0, // 之前的页数
nextPage: 0, // 之后的页数
page: 1, // 当前页数
totalPage: 1, // 总页数
keyword: "",
//avalon.vmodels.group = null;
if (avalon.vmodels.group) {
var vm = avalon.vmodels.group;
}
else {
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";
}
var vm = avalon.define({
$id: "group",
//通用变量
groupList: [], // 用户列表数据项
previousPage: 0, // 之前的页数
nextPage: 0, // 之后的页数
page: 1, // 当前页数
totalPage: 1, // 总页数
keyword: "",
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";
}
},
search: function(){
getPageData(1);
},
getGroupSettingString: function (setting) {
return {0: "允许任何人加入", 1: "提交请求后管理员审核", 2: "不允许任何人加入"}[setting]
},
showGroupDetailPage: function (groupId) {
vm.$fire("up!showGroupDetailPage", groupId);
}
});
}
},
search: function(){
getPageData(1);
},
getGroupSettingString: function (setting) {
return {0: "允许任何人加入", 1: "提交请求后管理员审核", 2: "不允许任何人加入"}[setting]
},
showGroupDetailPage: function (groupId) {
vm.$fire("up!showGroupDetailPage", groupId);
}
});
getPageData(1);
function getPageData(page) {
var url = "/api/admin/group/?paging=true&page=" + page + "&page_size=2";
var url = "/api/admin/group/?paging=true&page=" + page + "&page_size=10";
if (vm.keyword)
url += "&keyword=" + vm.keyword;
$.ajax({

View File

@ -3,55 +3,60 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "validator"], function ($,
// avalon:定义模式 group_list
avalon.ready(function () {
avalon.vmodels.groupDetail = null;
var vm = avalon.define({
$id: "groupDetail",
//通用变量
memberList: [],
previousPage: 0,
nextPage: 0,
page: 1,
totalPage: 1,
name: "",
description: "",
checkedSetting: "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";
}
else {
return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled";
}
},
if (avalon.vmodels.groupDetail) {
var vm = avalon.vmodels.groupDetail;
}
else {
var vm = avalon.define({
$id: "groupDetail",
//通用变量
memberList: [],
previousPage: 0,
nextPage: 0,
page: 1,
totalPage: 1,
name: "",
description: "",
checkedSetting: "0",
removeMember: function (relation) {
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/admin/group_member/",
method: "put",
data: JSON.stringify({group_id: relation.group, members: [relation.user.id]}),
contentType: "application/json",
success: function (data) {
vm.memberList.remove(relation);
bsAlert(data.data);
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";
}
})
},
showGroupListPage: function () {
vm.$fire("up!showGroupListPage");
}
});
else {
return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled";
}
},
removeMember: function (relation) {
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/admin/group_member/",
method: "put",
data: JSON.stringify({group_id: relation.group, members: [relation.user.id]}),
contentType: "application/json",
success: function (data) {
vm.memberList.remove(relation);
bsAlert(data.data);
}
})
},
showGroupListPage: function () {
vm.$fire("up!showGroupListPage");
}
});
}
avalon.scan();
getPageData(1);

View File

@ -1,51 +1,57 @@
require(["jquery", "avalon", "csrfToken", "bsAlert", "formValidation"], function ($, avalon, csrfTokenHeader, bsAlert) {
require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfTokenHeader, bsAlert) {
// avalon:定义模式 group_list
avalon.ready(function () {
avalon.vmodels.requestList = null;
var vm = avalon.define({
$id: "requestList",
//通用变量
requestList: [], // 列表数据项
previousPage: 0, // 之前的页数
nextPage: 0, // 之后的页数
page: 1, // 当前页数
totalPage: 1, // 总页数
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";
}
},
getPage: function (page_index) {
getPageData(page_index);
},
processRequest: function(request, status){
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/admin/join_group_request/",
method: "put",
data: {request_id: request.id, status: status},
success: function(data){
vm.requestList.remove(request);
bsAlert(data.data);
if (avalon.vmodels.requestList) {
var vm = avalon.vmodels.requestList;
}
else {
var vm = avalon.define({
$id: "requestList",
//通用变量
requestList: [], // 列表数据项
previousPage: 0, // 之前的页数
nextPage: 0, // 之后的页数
page: 1, // 当前页数
totalPage: 1, // 总页数
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";
}
},
getPage: function (page_index) {
getPageData(page_index);
},
processRequest: function(request, status){
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/admin/join_group_request/",
method: "put",
data: {request_id: request.id, status: status},
success: function(data){
vm.requestList.remove(request);
bsAlert(data.data);
}
})
}
});
}
avalon.scan();
getPageData(1);
@ -72,78 +78,6 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "formValidation"], function
});
}
/*$("#edit_user-form")
.formValidation({
framework: "bootstrap",
fields: {
username: {
validators: {
notEmpty: {
message: "请填写用户名"
},
stringLength: {
min: 3,
max: 30,
message: '用户名长度必须在3到30位之间'
}
}
},
real_name: {
validators: {
notEmpty: {
message: "请填写真实姓名"
}
}
},
email: {
validators: {
notEmpty: {
message: "请填写电子邮箱邮箱地址"
},
emailAddress: {
message: "请填写有效的邮箱地址"
}
}
},
password: {
validators: {
stringLength: {
min: 6,
max: 30,
message: '密码长度必须在6到30位之间'
}
}
}
}
}
).on('success.form.fv', function (e) {
e.preventDefault();
var data = {
username: vm.username,
real_name: vm.real_name,
email: vm.email,
id: vm.id,
admin_type: vm.admin_type
};
if ($("#password").val() !== "")
data.password = $("#password").val();
$.ajax({
beforeSend: csrfHeader,
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);
}
}
})
});*/
});
});

View File

@ -3,6 +3,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
avalon.ready(function () {
if(avalon.vmodels.problemList){
vm = avalon.vmodels.problemList;
problemList = [];
}
else {
var vm = avalon.define({
@ -13,6 +14,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
page: 1,
totalPage: 1,
keyword: "",
showVisibleOnly: false,
getNext: function () {
if (!vm.nextPage)
return;
@ -41,12 +43,17 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
vm.$fire("up!showProblemSubmissionPage", problemId);
}
});
vm.$watch("showVisibleOnly", function () {
getPageData(1);
});
}
getPageData(1);
function getPageData(page) {
var url = "/api/admin/problem/?paging=true&page=" + page + "&page_size=10";
if (vm.keyword != "")
url += "&keyword=" + vm.keyword;
if (vm.showVisibleOnly)
url += "&visible=true";
$.ajax({
url: url,
dataType: "json",

View File

@ -1,53 +1,59 @@
require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfTokenHeader, bsAlert) {
avalon.ready(function () {
avalon.vmodels.submissionList = null;
var vm = avalon.define({
$id: "submissionList",
submissionList: [],
previousPage: 0,
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);
},
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";
}
},
getPage: function (page_index) {
getPageData(page_index);
},
showSubmissionDetailPage: function (submissionId) {
},
showProblemListPage: function(){
vm.$fire("up!showProblemListPage");
}
});
if (avalon.vmodels.submissionList){
var vm = avalon.vmodels.submissionList;
}
else {
var vm = avalon.define({
$id: "submissionList",
submissionList: [],
previousPage: 0,
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);
},
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";
}
},
getPage: function (page_index) {
getPageData(page_index);
},
showSubmissionDetailPage: function (submissionId) {
},
showProblemListPage: function(){
vm.$fire("up!showProblemListPage");
}
});
}
getPageData(1);

View File

@ -3,12 +3,12 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "validator"], function ($,
// avalon:定义模式 userList
avalon.ready(function () {
//avalon.vmodels.userList = null;
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;
userList = []; //previousPage= 0; nextPage= 0; page = 1;
//editingUserId= 0; totalPage = 1; keyword= ""; showAdminOnly= false;
//user editor fields
username= ""; realName= ""; email= ""; adminType= 0; id= 0;
}

View File

@ -18,7 +18,6 @@ class SubmissionsListPageTest(TestCase):
self.user.set_password("666666")
self.user.save()
self.user2.save()
# self.client.login(username="gogoing", password="666666")
self.submission = Submission.objects.create(user_id=self.user.id,
language=1,
code='#include "stdio.h"\nint main(){\n\treturn 0;\n}',
@ -29,6 +28,16 @@ class SubmissionsListPageTest(TestCase):
response = self.client.get('/submissions/1/')
self.assertEqual(response.status_code, 200)
def test_visit_submissionsListPage_successfully_language_filter(self):
self.client.login(username="gogoing", password="666666")
response = self.client.get('/submissions/?language=1')
self.assertEqual(response.status_code, 200)
def test_visit_submissionsListPage_successfully_result_filter(self):
self.client.login(username="gogoing", password="666666")
response = self.client.get('/submissions/?result=1')
self.assertEqual(response.status_code, 200)
def test_visit_submissionsListPage_without_page_successfully(self):
self.client.login(username="gogoing", password="666666")
response = self.client.get('/submissions/')
@ -41,7 +50,7 @@ class SubmissionsListPageTest(TestCase):
def test_submissionsListPage_page_not_exist(self):
self.client.login(username="gogoing", password="666666")
response = self.client.get('/submissions/5/')
response = self.client.get('/submissions/999/')
self.assertTemplateUsed(response, "utils/error.html")
def test_submissionsListPage_have_no_submission(self):
@ -137,10 +146,3 @@ class ContestSubmissionAPITest(APITestCase):
data = {"language": 1}
response = self.client.post(self.url, data=data)
pass

View File

@ -12,15 +12,14 @@ from judge.judger_controller.settings import redis_config
from account.decorators import login_required
from account.models import SUPER_ADMIN
from contest.decorators import check_user_contest_permission
from problem.models import Problem
from contest.models import Contest, ContestProblem
from contest.models import ContestProblem
from utils.shortcuts import serializer_invalid_response, error_response, success_response, error_page, paginate
from .models import Submission
from .serializers import CreateSubmissionSerializer, SubmissionSerializer
from announcement.models import Announcement
class SubmissionAPIView(APIView):
@login_required
@ -80,8 +79,10 @@ def problem_my_submissions_list_page(request, problem_id):
problem = Problem.objects.get(id=problem_id, visible=True)
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"). \
values("id", "result", "create_time", "accepted_answer_time", "language")
return render(request, "oj/problem/my_submissions_list.html",
{"submissions": submissions, "problem": problem})
@ -137,8 +138,17 @@ def my_submission_list_page(request, page=1):
"""
我的所有提交的列表页
"""
submissions = Submission.objects.filter(user_id=request.user.id). \
submissions = Submission.objects.filter(user_id=request.user.id, contest_id__isnull=True). \
values("id", "result", "create_time", "accepted_answer_time", "language").order_by("-create_time")
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)
try:
current_page = paginator.page(int(page))
@ -154,6 +164,10 @@ def my_submission_list_page(request, page=1):
except Exception:
pass
# 右侧的公告列表
announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
return render(request, "oj/submission/my_submissions_list.html",
{"submissions": current_page, "page": int(page),
"previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20})
"previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20,
"announcements": announcements, "filter":filter})

View File

@ -7,8 +7,8 @@
<th>创建时间</th>
<th>更新时间</th>
<th>创建者</th>
<td>可见范围</td>
<th>状态</th>
<th>类型</th>
<th>可见</th>
<th></th>
</tr>
<tr ms-repeat="announcementList">
@ -18,7 +18,7 @@
<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>{{ getState(el)}}</td>
<td ms-text="el.visible?'可见':'不可见'"></td>
<td>
<button class="btn-sm btn-info" ms-click="editAnnouncement(el)">编辑</button>
</td>
@ -38,7 +38,8 @@
<div class="form-group">
<label>标题</label>
<input name="title" type="text" class="form-control" id="newTitle" placeholder="公告标题" value="" ms-duplex="newTitle"></div>
<input name="title" type="text" class="form-control" id="newTitle" placeholder="公告标题" value=""
ms-duplex="newTitle"></div>
<div class="form-group">
<label>内容</label>
<textarea id="edit-announcement-editor"></textarea>
@ -66,7 +67,7 @@
</div>
</div>
<div class="form-group">
<button ms-click="submitChange()" class="btn btn-primary">提交</button>
<button ms-click="submitChange()" class="btn btn-success">保存修改</button>
&nbsp;&nbsp;
<button ms-click="cancelEdit()" class="btn btn-danger">取消</button>
</div>
@ -78,12 +79,14 @@
<label>标题</label>
<input name="title" type="text" class="form-control" id="title" placeholder="公告标题"
data-error="请填写公告标题(标题不得超过50字)" maxlength="50" required>
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
<label>内容</label>
<textarea id="create-announcement-editor" placeholder="公告内容" maxlength="10000" required>
</textarea>
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
@ -91,10 +94,16 @@
<div>
<span ms-if="showGlobalViewRadio">
<input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">全局可见
<label>
<small><input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">全局可见
</small>
</label>
</span>
<span>
<input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组内可见
<label>
<small><input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组内可见
</small>
</label>
</span>
</div>
@ -106,7 +115,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

@ -18,9 +18,7 @@
<div class="col-md-12">
<div class="form-group">
<textarea id="editor" placeholder="这里输入内容" autofocus ms-duplex="description"></textarea>
<div class="help-block with-errors"></div>
<small ms-visible="description==''" style="color:red">请填写比赛描述</small>
<p class="error-info" ms-visible="description==''">请填写比赛描述</p>
</div>
</div>
<div class="col-md-6">

View File

@ -15,22 +15,22 @@
<tr>
<th>ID</th>
<th>比赛</th>
<th>公开排名</th>
<th>可见</th>
<th>排名</th>
<th>创建时间</th>
<th>创建者</th>
<th>可见</th>
<th></th>
</tr>
<tr ms-repeat="contestList">
<td>{{ el.id }}</td>
<td>{{ el.title }}</td>
<td>{{ getYesOrNo(el.show_rank) }}</td>
<td>{{ getYesOrNo(el.visible) }}</td>
<td ms-text="el.show_rank?'公开':'不公开'"></td>
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{ el.created_by.username }}</td>
<td ms-text="el.visible?'可见':'不可见'"></td>
<td>
<a class="btn btn-info" href="javascript:void(0)" ms-click="showEditContestArea($index+1)">编辑</a>
<a class="btn btn-primary" href="javascript:void(0)" ms-click="showEditProblemArea($index+1)">编辑问</a>
<a class="btn btn-info btn-sm" href="javascript:void(0)" ms-click="showEditContestArea($index+1)">编辑</a>
<a class="btn btn-info btn-sm" href="javascript:void(0)" ms-click="showEditProblemArea($index+1)"></a>
</td>
</tr>
</table>
@ -63,7 +63,7 @@
<textarea id="editor" placeholder="这里输入内容" autofocus ms-duplex="editDescription"></textarea>
<div class="help-block with-errors"></div>
<small ms-visible="editDescription==''" style="color:red">请填写比赛描述</small>
<p class="error-info" ms-visible="editDescription==''" >请填写比赛描述</p>
</div>
</div>
<div class="col-md-6">
@ -152,13 +152,14 @@
</div>
</div>
<div class="col-md-12">
<button class="btn btn-primary" type="submit">保存修改</button>
<button class="btn btn-success" type="submit">保存修改</button>
</div>
</form>
</div>
<div class="col-md-12" ms-visible="editingProblemContestIndex">
<label>题目列表</label>
<a href="javascript:void(0)" class="btn btn-success btn-sm" ms-click="add_problem()">添加</a>
<a href="javascript:void(0)" class="btn btn-primary btn-sm" ms-click="addProblem()">添加题目</a>
<a href="javascript:void(0)" class="btn btn-info btn-sm" ms-click="showSubmissionPage()">查看提交</a>
<table class="table table-striped">
<tr>
<th>编号</th>
@ -172,11 +173,13 @@
<td>{{ el.sort_index }}</td>
<td>{{ el.title }}</td>
<td ms-visible="editMode=='2'">{{ el.score}}</td>
<td>{{ getYesOrNo(el.visible) }}</td>
<td ms-text="el.visible?'可见':'不可见'"></td>
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss") }}</td>
<td>
<a href="javascript:void(0)" class="btn-sm btn-info"
ms-click="showProblemEditor(el)">编辑</a>
ms-click="showProblemEditPage(el)">编辑</a>
<a href="javascript:void(0)" class="btn-sm btn-info"
ms-click="showSubmissionPage(el)">提交</a>
</td>
</tr>
</table>

View File

@ -23,7 +23,7 @@
<div class="form-group col-md-12">
<label>题目描述</label>
<textarea id="problemDescription" placeholder="这里输入内容(此内容不能为空)" ms-duplex="description"></textarea>
<small ms-visible="description==''" style="color:red">请填写题目描述</small>
<p class="error-info" ms-visible="description==''">请填写题目描述</p>
</div>
@ -130,7 +130,7 @@
<textarea id="hint" placeholder="这里输入内容" ms-duplex="hint"></textarea>
</div>
<div class="col-md-12">
<input type="submit" class="btn btn-success btn-lg" value="发布题目" id="submitBtn">
<button type="submit" class="btn btn-success btn-lg">发布题目</button>
</div>
</form>
</div>

View File

@ -0,0 +1,37 @@
<div ms-controller="contestSubmissionList" class="col-md-9">
<nav>
<ul class="pager">
<li class="previous" ms-click="goBack()"><a href="javascript:void(0)"><span
aria-hidden="true">&larr;</span> 返回</a></li>
</ul>
</nav>
<h1>提交列表</h1>
<a href="javascript:void(0)" class="btn btn-sm btn-primary" ms-click="getPage(1)">
<span class="glyphicon glyphicon-refresh"></span> 刷新
</a>
<table class="table table-striped">
<tr>
<th>ID</th>
<th>创建时间</th>
<th>作者</th>
<td>结果</td>
<td></td>
</tr>
<tr ms-repeat="submissionList">
<td>{{ el.id }}</td>
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{ el.user }}</td>
<td>{{ results[el.result] }}</td>
<td>
<a class="btn btn-info" ms-attr-href="'/submission/' + el.id + '/'" target="_blank">详情</a>
</td>
</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>
</div>
</div>
<script src="/static/js/app/admin/contest/submissionList.js"></script>

View File

@ -11,7 +11,7 @@
<div class="form-group col-md-12">
<label>题目描述</label>
<textarea id="problemDescription" placeholder="这里输入内容(此内容不能为空)" ms-duplex="description"></textarea>
<small ms-visible="description==''" style="color:red">请填写题目描述</small>
<p class="error-info" ms-visible="description==''">请填写题目描述</p>
</div>

View File

@ -17,7 +17,7 @@
<div class="form-group col-md-12">
<label>题目描述</label>
<textarea id="problemDescription" placeholder="这里输入内容(此内容不能为空)" ms-duplex="description"></textarea>
<small ms-visible="description==''" style="color:red">请填写题目描述</small>
<p class="error-info" ms-visible="description==''">请填写题目描述</p>
</div>

View File

@ -17,7 +17,8 @@
<th>题目</th>
<th>创建时间</th>
<th>作者</th>
<td>通过次数/提交总数</td>
<th>可见</th>
<th>通过次数/提交总数</th>
<td></td>
</tr>
<tr ms-repeat="problemList">
@ -25,6 +26,7 @@
<td>{{ el.title }}</td>
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{ el.created_by.username }}</td>
<td ms-text="el.visible?'可见':'不可见'"></td>
<td>{{ el.total_accepted_number }}/{{ el.total_submit_number }}</td>
<td>
<button class="btn-sm btn-info" ms-click="showEditProblemPage(el.id)">编辑</button>
@ -32,6 +34,9 @@
</td>
</tr>
</table>
<div class="form-group">
<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>

View File

@ -21,7 +21,7 @@
<td>{{ el.user }}</td>
<td>{{ results[el.result] }}</td>
<td>
<a class="btn btn-info" ms-attr-href="'/my_submission/' + el.id + '/'" target="_blank">详情</a>
<a class="btn btn-info" ms-attr-href="'/submission/' + el.id + '/'" target="_blank">详情</a>
</td>
</tr>
</table>

View File

@ -1,27 +1,33 @@
{% extends "oj_base.html" %}
{% block body %}
<div class="container main">
<div class="col-md-6 col-md-offset-3">
<h2 class="text-center">用户登录</h2>
<div class="container main">
<div class="col-md-6 col-md-offset-3">
<h2 class="text-center">用户登录</h2>
<form id="login-form">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" class="form-control input-lg" id="username" name="username" maxlength="30" data-error="请填写用户名" placeholder="用户名" autofocus required>
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control input-lg" id="password" name="password" maxlength="30" data-error="请填写密码" placeholder="密码" required>
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
</div>
<form id="login-form">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" class="form-control input-lg" id="username" name="username" maxlength="30"
data-error="请填写用户名" placeholder="用户名" autofocus required>
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control input-lg" id="password" name="password" maxlength="30"
data-error="请填写密码" placeholder="密码" required>
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">提交</button>
</div>
<a href="/register/">还没有帐号?点击注册</a>
</form>
</div>
</div>
{% endblock %}
{% block js_block %}
<script src="/static/js/app/oj/account/login.js"></script>
<script src="/static/js/app/oj/account/login.js"></script>
{% endblock %}

View File

@ -5,8 +5,13 @@
公告
</h3></div>
<div class="panel-body">
{% for item in announcements %}
<p>{{ forloop.counter }}.&nbsp;&nbsp;<a href="/announcement/{{ item.id }}/" target="_blank">{{ item.title }}</a></p>
{% endfor %}
{% if announcements %}
{% for item in announcements %}
<p>{{ forloop.counter }}.&nbsp;&nbsp;<a href="/announcement/{{ item.id }}/" target="_blank">{{ item.title }}</a>
</p>
{% endfor %}
{% else %}
<p>暂无可显示的公告</p>
{% endif %}
</div>
</div>

View File

@ -3,7 +3,7 @@
{% load contest %}
<div class="container main">
<div class="row">
<div class="col-lg-9">
<div class="col-md-9 col-lg-9">
<div class="row">
<div class="right">
<form class="form-inline" method="get">
@ -15,6 +15,7 @@
</div>
</div>
<div>
{% if contests %}
<table class="table table-striped">
<thead>
<tr>
@ -47,6 +48,7 @@
{% endfor %}
</tbody>
</table>
<div class="form-group">
<label>仅显示当前可参加的比赛
<input id="join" type="checkbox" {% if join %}checked{% endif %} onchange="if(this.checked){location.href='/contests/?join=True'}else{location.href='/contests/'}">
@ -66,10 +68,13 @@
{% endif %}
</ul>
</nav>
{% else %}
<p>当前没有合适的比赛</p>
{% endif %}
</div>
</div>
<div class="col-lg-3">
<div class="col-md-3 col-lg-3">
{% include "oj/announcement/_announcement_panel.html" %}
</div>
</div>

View File

@ -26,7 +26,7 @@
</div>
<div class="col-lg-9">
<div class="col-md-9 col-lg-9">
<div>
<table class="table table-striped">
<thead>
@ -55,7 +55,7 @@
</div>
</div>
<div class="col-lg-3">
<div class="col-md-3 col-lg-3">
{% include "oj/announcement/_announcement_panel.html" %}
</div>
</div>

View File

@ -20,6 +20,7 @@
</div>
<div class="row">
<div class="col-lg-12">
{% if result %}
<table class="table table-bordered">
<thead>
<tr>
@ -49,6 +50,9 @@
{% endfor %}
</tbody>
</table>
{% else %}
<p>还没有结果</p>
{% endif %}
</div>
</div>
</div>

View File

@ -0,0 +1,71 @@
{% extends "oj_base.html" %}
{% block body %}
{% load problem %}
<div class="container main">
<div class="row">
<div class="col-md-9 col-lg-9">
<div class="row">
<div class="right">
<form class="form-inline" method="get">
<div class="form-group-sm">
<input name="keyword" class="form-control" placeholder="请输入关键词">
<input type="submit" value="搜索" class="btn btn-primary">
</div>
</form>
</div>
</div>
<div>
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>名称</th>
<th>加入方式</th>
<th>创建者</th>
<th>创建时间</th>
</tr>
</thead>
<tbody>
{% for item in groups %}
<tr>
<th scope="row"><a href="/group/{{ item.id }}/" target="_blank">{{ item.id }}</a></th>
<td><a href="/group/{{ item.id }}/" target="_blank">{{ item.name }}</a></td>
<td>
{% if item.join_group_setting %}
需要申请
{% else %}
无需申请
{% endif %}
</td>
<td>{{ item.admin }}</td>
<td>{{ item.create_time }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<nav>
<ul class="pager">
{% if previous_page %}
<li class="previous"><a
href="/groups/{{ previous_page }}/{% if keyword %}?keyword={{ keyword }}{% endif %}">
<span aria-hidden="true">&larr;</span> 上一页</a>
</li>
{% endif %}
{% if next_page %}
<li class="next">
<a href="/groups/{{ next_page }}/{% if keyword %}?keyword={{ keyword }}{% endif %}">
下一页 <span aria-hidden="true">&rarr;</span>
</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
<div class="col-md-3 col-lg-3">
{% include "oj/announcement/_announcement_panel.html" %}
</div>
</div>
</div>
{% endblock %}

View File

@ -8,64 +8,71 @@
<link rel="stylesheet" type="text/css" href="/static/css/fullpage/jquery.fullPage.css">
<style>
html, textarea, input, option, select, button {
font: 1em "Helvetica Neue", Helvetica, "Lantinghei SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", "STHeiti", "WenQuanYi Micro Hei", SimSun, sans-serif;
color: #FFF;
font: 1em "Helvetica Neue", Helvetica, "Lantinghei SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑",
"STHeiti", "WenQuanYi Micro Hei", SimSun, sans-serif;
color: #FFF;
}
a {
text-decoration: none;
color: #FFF;
text-decoration: none;
color: #FFF;
}
#header {
position: fixed;
height: 30px;
display: block;
width: 100%;
background: transparent;
z-index: 10;
padding: 20px 20px 0 20px;
position: fixed;
height: 30px;
display: block;
width: 100%;
background: transparent;
z-index: 10;
padding: 20px 20px 0 20px;
}
#name {
font-size: 45px;
margin-right: 20px;
font-size: 45px;
margin-right: 20px;
}
#nav-left{
display: inline;
position:relative;
float: right;
margin-right: 28px;
}
.section {
position: relative;
position: relative;
}
.section h1 {
font-size: 60px;
font-size: 60px;
}
.section h3 {
font-size: 30px;
font-weight: normal;
font-size: 30px;
font-weight: normal;
}
.bottom-pointer {
top: auto;
bottom: 20px;
position: absolute;
left: 48%;
top: auto;
bottom: 20px;
position: absolute;
left: 48%;
}
.index-section-text {
position: absolute;
top: 30%;
width: 100%;
text-align: center;
position: absolute;
top: 30%;
width: 100%;
text-align: center;
}
.section-text {
text-align: center;
text-align: center;
}
.icon {
max-height: 300px;
max-width: 300px;
max-height: 300px;
max-width: 300px;
}
</style>
@ -84,13 +91,34 @@
loopBottom: true
});
});
</script>
</head>
<body>
<div id="header">
<span id="name">qduoj</span>
<a href="/problems/">题目</a>&nbsp;&nbsp;<a href="/contests/">比赛</a>&nbsp;&nbsp;<a href="#">小组</a>
<a href="/problems/">题目</a>&nbsp;&nbsp;
<a href="/submissions/">提交</a>&nbsp;&nbsp;
<a href="/contests/">比赛</a>&nbsp;&nbsp;
<a href="/groups/">小组</a>&nbsp;&nbsp;
<a href="/about/">关于</a>&nbsp;&nbsp;
<div id="nav-left">
{% if request.user.is_authenticated %}
<a href="#">{{ request.user.username }}</a>
<a href="/logout/">退出</a>
{% else %}
<a href="/login/">
登录
</a>
<a href="/register/">
注册
</a>
{% endif %}
</div>
</div>
<div id="fullpage">

View File

@ -11,22 +11,25 @@
<a href="/problem/{{ problem.id }}/submissions/">我的提交</a></li>
</ul>
{% include "oj/problem/_problem_header.html" %}
{% if submissions %}
<table class="table table-bordered">
<thead>
<tr class="" success>
<th>#</th>
<th>提交时间</th>
<th>结果</th>
<th>运行时间</th>
<th>语言</th>
<th>运行时间</th>
<th>结果</th>
</tr>
</thead>
<tbody>
{% for item in submissions %}
<tr class="{{ item.result|translate_result_class }}">
<tr>
<th scope="row"><a href="/submission/{{ item.id }}/">{{ forloop.counter }}</a></th>
<td>{{ item.create_time }}</td>
<td>{{ item.result|translate_result }}</td>
<td>
{{ item.language|translate_language }}
</td>
<td>
{% if item.accepted_answer_time %}
{{ item.accepted_answer_time }}ms
@ -34,13 +37,16 @@
--
{% endif %}
</td>
<td>
{{ item.language|translate_language }}
<td class="alert-{{ item.result|translate_result_class }}">
<strong>{{ item.result|translate_result }}</strong>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>你还没有提交该题目</p>
{% endif %}
</div>
{% endblock %}

View File

@ -3,7 +3,7 @@
{% load problem %}
<div class="container main">
<div class="row">
<div class="col-lg-9">
<div class="col-md-9 col-lg-9">
<div class="row">
<div class="right">
<form class="form-inline" method="get">
@ -54,7 +54,7 @@
</div>
</div>
<div class="col-lg-3">
<div class="col-md-3 col-lg-3">
{% include "oj/announcement/_announcement_panel.html" %}
<div class="panel panel-info">
<div class="panel-heading">

View File

@ -2,57 +2,94 @@
{% block body %}
{% load submission %}
<div class="container main">
{% if submissions %}
<table class="table table-bordered">
<thead>
<tr class="" success>
<th>#</th>
<th>提交时间</th>
<th>结果</th>
<th>运行时间</th>
<th>语言</th>
</tr>
</thead>
<tbody>
{% for item in submissions %}
<tr class="{{ item.result|translate_result_class }}">
<th scope="row"><a href="/my_submission/{{ item.id }}/" id="id_{{ forloop.counter }}">
{{ forloop.counter |add:start_id }}</a></th>
<td>{{ item.create_time }}</td>
<td>{{ item.result|translate_result }}</td>
<td>
{% if item.accepted_answer_time %}
{{ item.accepted_answer_time }}ms
{% else %}
--
{% endif %}
</td>
<td>
{{ item.language|translate_language }}
</td>
</tr>
{% endfor %}
{% load submission %}
<div class="container main">
<div class="col-md-9 col-lg-9">
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>提交时间</th>
<th>
<div class="dropdown">
<a href="#" class="dropdown-toggle" id="languageFilter" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true">
语言<span class="caret"></span>
</a>
<ul class="dropdown-menu" aria-labelledby="languageFilter">
<li><a href="/submissions/?language=1">C</a></li>
<li><a href="/submissions/?language=2">C++</a></li>
<li><a href="/submissions/?language=3">Java</a></li>
<li><a href="/submissions/">取消筛选</a></li>
</ul>
</div>
</th>
<th>运行时间</th>
<th>
<div class="dropdown">
<a href="#" class="dropdown-toggle" id="resultFilter" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true">
结果<span class="caret"></span>
</a>
<ul class="dropdown-menu" aria-labelledby="resultFilter">
<li><a href="/submissions/?result=0">Accepted</a></li>
<li><a href="/submissions/?result=6">Wrong Answer</a></li>
<li><a href="/submissions/?result=1">Runtime Error</a></li>
<li><a href="/submissions/?result=2">Time Limit Exceeded</a></li>
<li><a href="/submissions/?result=3">Memory Limit Exceeded</a></li>
<li><a href="/submissions/?result=4">Compile Error</a></li>
<li><a href="/submissions/?result=5">Format Error</a></li>
<li><a href="/submissions/">取消筛选</a></li>
</ul>
</div>
</th>
</tr>
</thead>
<tbody>
{% for item in submissions %}
<tr>
<th scope="row"><a href="/my_submission/{{ item.id }}/" id="id_{{ forloop.counter }}">
{{ forloop.counter |add:start_id }}</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>
</tbody>
</table>
{% else %}
<p>你还没有提交记录!</p>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
<nav>
<ul class="pager">
{% if previous_page %}
<li class="previous"><a
href="/submissions/{{ previous_page }}/{% if keyword %}?keyword={{ keyword }}{% endif %}{% if tag %}?tag={{ tag }}{% endif %}">
<span aria-hidden="true">&larr;</span> 上一页</a></li>
<li class="previous"><a
href="/submissions/{{ previous_page }}/{% if filter %}?{{ filter.name }}={{ filter.content }}{% endif %}">
<span aria-hidden="true">&larr;</span> 上一页</a></li>
{% endif %}
{% if next_page %}
<li class="next"><a
href="/submissions/{{ next_page }}/{% if keyword %}?keyword={{ keyword }}{% endif %}{% if tag %}?tag={{ tag }}{% endif %}">下一页 <span
aria-hidden="true">&rarr;</span></a></li>
<li class="next"><a
href="/submissions/{{ next_page }}/{% if filter %}?{{ filter.name }}={{ filter.content }}{% endif %}">下一页 <span
aria-hidden="true">&rarr;</span></a></li>
{% endif %}
</ul>
</nav>
{% if not submissions %}
<p>你还没有提交记录!</p>
{% endif %}
</div>
<div class="col-md-3 col-lg-3">
{% include "oj/announcement/_announcement_panel.html" %}
</div>
</div>
{% endblock %}

View File

@ -45,31 +45,32 @@
<li><a href="/problems/">题目</a></li>
<li><a href="/submissions/">提交</a></li>
<li><a href="/contests/">比赛</a></li>
<li><a href="/groups/">小组</a></li>
<li><a href="/about/">关于</a></li>
</ul>
{% if request.user.is_authenticated %}
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">
李扬
<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">我的提交</a></li>
<li><a href="#">我的资料</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">退出</a></li>
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">
{{ request.user.username }}
<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/submission/">我的提交</a></li>
<li><a href="#">我的资料</a></li>
<li role="separator" class="divider"></li>
<li><a href="/logout/">退出</a></li>
</ul>
</li>
</ul>
{% else %}
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="/login/" class="dropdown-toggle">
登录
</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="/login/">
登录
</a>
</li>
</ul>
{% endif %}
</div>
</div>
@ -84,6 +85,10 @@
<script src="/static/js/require.js"></script>
<script>
require(["bootstrap"]);
</script>
{% block js_block %}{% endblock %}
<!-- footer begin -->