Accept Merge Request #281 后台部分重构,还未完成;部分代码修改为 web 组件;修复部分小 bug;更新个人主页样式 : (virusdefender-dev -> dev)

Merge Request: 后台部分重构,还未完成;部分代码修改为 web 组件;修复部分小 bug;更新个人主页样式
Created By: @virusdefender
Accepted By: @virusdefender
URL: https://coding.net/u/virusdefender/p/qduoj/git/merge/281
This commit is contained in:
virusdefender 2015-10-26 10:47:24 +08:00
commit a176999750
25 changed files with 610 additions and 505 deletions

View File

@ -1,4 +1,5 @@
# coding=utf-8
import urllib
import functools
from functools import wraps
@ -27,7 +28,7 @@ class BasePermissionDecorator(object):
if self.request.is_ajax():
return error_response(u"请先登录")
else:
return HttpResponseRedirect("/login/")
return HttpResponseRedirect("/login/?__from=" + urllib.quote(self.request.build_absolute_uri()))
def check_permission(self):
raise NotImplementedError()

View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import jsonfield.fields
import account.models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('account', '0012_auto_20151012_1546'),
]
operations = [
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('avatar', models.CharField(default=account.models._random_avatar, max_length=50)),
('blog', models.URLField(null=True, blank=True)),
('mood', models.CharField(max_length=200, null=True, blank=True)),
('hduoj_username', models.CharField(max_length=30, null=True, blank=True)),
('bestcoder_username', models.CharField(max_length=30, null=True, blank=True)),
('codeforces_username', models.CharField(max_length=30, null=True, blank=True)),
('rank', models.IntegerField(default=65535)),
('accepted_number', models.IntegerField(default=0)),
('submissions_number', models.IntegerField(default=0)),
('problems_status', jsonfield.fields.JSONField(default={})),
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'user_profile',
},
),
]

View File

@ -48,3 +48,26 @@ class User(AbstractBaseUser):
class Meta:
db_table = "user"
def _random_avatar():
import random
return "/static/img/avatar/avatar-" + str(random.randint(1, 20)) + ".png"
class UserProfile(models.Model):
user = models.OneToOneField(User)
avatar = models.CharField(max_length=50, default=_random_avatar)
blog = models.URLField(blank=True, null=True)
mood = models.CharField(max_length=200, blank=True, null=True)
hduoj_username = models.CharField(max_length=30, blank=True, null=True)
bestcoder_username = models.CharField(max_length=30, blank=True, null=True)
codeforces_username = models.CharField(max_length=30, blank=True, null=True)
rank = models.IntegerField(default=65535)
accepted_number = models.IntegerField(default=0)
submissions_number = models.IntegerField(default=0)
# JSON字典用来表示该用户的问题的解决状态 1为ac2为正在进行
problems_status = JSONField(default={})
class Meta:
db_table = "user_profile"

View File

@ -58,3 +58,7 @@ 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)
class SSOSerializer(serializers.Serializer):
token = serializers.CharField(max_length=40)

View File

@ -5,6 +5,7 @@ from django.contrib import auth
from django.shortcuts import render
from django.db.models import Q
from django.conf import settings
from django.http import HttpResponseRedirect
from django.core.exceptions import MultipleObjectsReturned
from django.utils.timezone import now
@ -20,7 +21,8 @@ from .models import User
from .serializers import (UserLoginSerializer, UsernameCheckSerializer,
UserRegisterSerializer, UserChangePasswordSerializer,
EmailCheckSerializer, UserSerializer, EditUserSerializer,
ApplyResetPasswordSerializer, ResetPasswordSerializer)
ApplyResetPasswordSerializer, ResetPasswordSerializer,
SSOSerializer)
from .decorators import super_admin_required
@ -284,15 +286,37 @@ class ResetPasswordAPIView(APIView):
def user_index_page(request, username):
return render(request, "oj/account/user_index.html")
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
return error_page(request, u"用户不存在")
blog_link = ""
if user.userprofile.blog:
blog_link = user.userprofile.blog.replace("http://", "").replace("https://", "")
return render(request, "oj/account/user_index.html", {"user": user, "blog_link": blog_link})
def auth_page(request):
if not request.user.is_authenticated():
return render(request, "oj/account/oauth.html")
class SSOAPIView(APIView):
def post(self, request):
serializer = SSOSerializer(data=request.data)
if serializer.is_valid():
try:
user = User.objects.get(auth_token=serializer.data["token"])
return success_response({"username": user.username})
except User.DoesNotExist:
return error_response(u"用户不存在")
else:
return serializer_invalid_response(serializer)
@login_required
def get(self, request):
callback = request.GET.get("callback", None)
if not callback:
if not callback or callback != settings.SSO["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})
request.user.save()
return render(request, "oj/account/sso.html", {"redirect_url": callback + "?token=" + token, "callback": callback})

View File

@ -1,4 +1,5 @@
# coding=utf-8
import urllib
from functools import wraps
from django.http import HttpResponse, HttpResponseRedirect
@ -30,7 +31,7 @@ def check_user_contest_permission(func):
if request.is_ajax():
return error_response(u"请先登录")
else:
return HttpResponseRedirect("/login/")
return HttpResponseRedirect("/login/?__from=" + urllib.quote(request.build_absolute_uri()))
# kwargs 就包含了 url 里面的参数
if "contest_id" in kwargs:

View File

@ -11,10 +11,8 @@ from .models import Contest, ContestProblem
class CreateContestSerializer(serializers.Serializer):
title = serializers.CharField(max_length=40)
description = serializers.CharField(max_length=5000)
mode = serializers.IntegerField()
contest_type = serializers.IntegerField()
real_time_rank = serializers.BooleanField()
show_user_submission = serializers.BooleanField()
password = serializers.CharField(max_length=30, required=False, default=None)
start_time = serializers.DateTimeField()
end_time = serializers.DateTimeField()
@ -45,10 +43,8 @@ class EditContestSerializer(serializers.Serializer):
id = serializers.IntegerField()
title = serializers.CharField(max_length=40)
description = serializers.CharField(max_length=10000)
mode = serializers.IntegerField()
contest_type = serializers.IntegerField()
real_time_rank = serializers.BooleanField()
show_user_submission = serializers.BooleanField()
password = serializers.CharField(max_length=30, required=False, default=None)
start_time = serializers.DateTimeField()
end_time = serializers.DateTimeField()

View File

@ -1,9 +0,0 @@
# coding=utf-8
from django.conf.urls import include, url
from django.views.generic import TemplateView
urlpatterns = [
url(r'^login/$', TemplateView.as_view(template_name="oj/account/login.html"), name="user_login_page"),
]

View File

@ -64,9 +64,8 @@ class ContestAdminAPIView(APIView):
return error_response(u"比赛的开始时间必须早于比赛结束的时间")
try:
contest = Contest.objects.create(title=data["title"], description=data["description"],
mode=data["mode"], contest_type=data["contest_type"],
contest_type=data["contest_type"],
real_time_rank=data["real_time_rank"], password=data["password"],
show_user_submission=data["show_user_submission"],
start_time=dateparse.parse_datetime(data["start_time"]),
end_time=dateparse.parse_datetime(data["end_time"]),
created_by=request.user, visible=data["visible"])
@ -125,10 +124,8 @@ class ContestAdminAPIView(APIView):
contest.title = data["title"]
contest.description = data["description"]
contest.mode = data["mode"]
contest.contest_type = data["contest_type"]
contest.real_time_rank = data["real_time_rank"]
contest.show_user_submission = data["show_user_submission"]
contest.start_time = dateparse.parse_datetime(data["start_time"])
contest.end_time = dateparse.parse_datetime(data["end_time"])
contest.visible = data["visible"]
@ -225,6 +222,7 @@ class ContestProblemAdminAPIView(APIView):
contest_problem.visible = data["visible"]
contest_problem.sort_index = data["sort_index"]
contest_problem.score = data["score"]
contest_problem.last_update_time = now()
contest_problem.save()
return success_response(ContestProblemSerializer(contest_problem).data)
else:
@ -486,6 +484,10 @@ def contest_problem_submissions_list_page(request, contest_id, page=1):
if user_id:
submissions = submissions.filter(user_id=request.GET.get("user_id"))
problem_id = request.GET.get("problem_id", None)
if problem_id:
submissions = submissions.filter(problem_id=problem_id)
# 封榜的时候只能看到自己的提交
if not contest.real_time_rank:
if not (request.user.admin_type == SUPER_ADMIN or request.user == contest.created_by):
@ -535,4 +537,4 @@ 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, "filter": filter, "user_id": user_id})
"contest": contest, "filter": filter, "user_id": user_id, "problem_id": problem_id})

View File

@ -11,18 +11,13 @@ DATABASES = {
},
# submission 的 name 和 engine 请勿修改,其他代码会用到
'submission': {
'NAME': 'oj_submission',
'ENGINE': 'django.db.backends.mysql',
'CONN_MAX_AGE': 0.1,
'HOST': "127.0.0.1",
'PORT': 3306,
'USER': 'root',
'PASSWORD': 'root',
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db1.sqlite3'),
}
}
REDIS_CACHE = {
"host": "121.42.32.129",
"host": "127.0.0.1",
"port": 6379,
"db": 1
}
@ -37,3 +32,5 @@ STATICFILES_DIRS = [os.path.join(BASE_DIR, "static/src/"), BASE_DIR]
# 模板文件夹
TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'template/src/')]
SSO = {"callback": "http://localhost:8765/login"}

View File

@ -43,3 +43,5 @@ STATICFILES_DIRS = [os.path.join(BASE_DIR, "static/release/"), os.path.join(BASE
TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'template/release/')]
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SSO = {"callback": "https://discuss.acmer.site/login"}

View File

@ -6,7 +6,7 @@ from django.views.generic import TemplateView
from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView,
UserChangePasswordAPIView, EmailCheckAPIView,
UserAdminAPIView, UserInfoAPIView,
ApplyResetPasswordAPIView)
ApplyResetPasswordAPIView, SSOAPIView)
from announcement.views import AnnouncementAdminAPIView
@ -121,13 +121,13 @@ urlpatterns = [
url(r'^api/contest/time/$', ContestTimeAPIView.as_view(), name="contest_time_api_view"),
url(r'^api/admin/rejudge/$', SubmissionRejudgeAdminAPIView.as_view(), name="submission_rejudge_api"),
url(r'^user/(?P<username>\w+)/$', "account.views.user_index_page"),
url(r'^user/(?P<username>.+)/$', "account.views.user_index_page"),
url(r'^api/reset_password/$', ApplyResetPasswordAPIView.as_view(), name="apply_reset_password_api"),
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"),
url(r'^account/sso/$', SSOAPIView.as_view(), name="sso_api"),
]

View File

@ -62,3 +62,32 @@ pre, code {
font-family: "source_code_pro";
background-color: white;
}
#index-avatar{
height: 200px;
width: 200px;
}
#user-mood{
max-width: 70%;
}
#oj-logo{
height: 20px;
}
.super-admin-star{
color: #ffd700;
}
.admin-star{
color: #c0c0c0;
}
#user-data-number{
font-size: 20px;
}
#user-data-text{
display: block;
}

View File

@ -1,5 +1,5 @@
require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "datetimePicker",
"validator"],
"validator", "editorComponent"],
function ($, avalon, editor, uploader, bsAlert, csrfTokenHeader) {
$("#add-contest-form").validator().on('submit', function (e) {
@ -7,11 +7,9 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
e.preventDefault();
var ajaxData = {
title: vm.title,
description: vm.description,
mode: vm.mode,
description: avalon.vmodels.contestDescriptionEditor.content,
contest_type: 0,
real_time_rank: vm.realTimeRank,
show_user_submission: vm.showSubmission,
start_time: vm.startTime,
end_time: vm.endTime,
visible: false
@ -38,12 +36,11 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
bsAlert("你没有选择参赛用户!");
return false;
}
if (vm.editDescription == "") {
if (ajaxData.description.trim() == "") {
bsAlert("比赛描述不能为空!");
return false;
}
$.ajax({ // Add contest
beforeSend: csrfTokenHeader,
$.ajax({
url: "/api/admin/contest/",
dataType: "json",
contentType: "application/json;charset=UTF-8",
@ -52,17 +49,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
success: function (data) {
if (!data.code) {
bsAlert("添加成功!将转到比赛列表页以便为比赛添加问题(注意比赛当前状态为:隐藏)");
vm.title = "";
vm.description = "";
vm.startTime = "";
vm.endTime = "";
vm.password = "";
vm.mode = "0";
vm.showSubmission = true;
location.hash = "#contest/contest_list";
vm.isGlobal = true;
vm.allGroups = [];
vm.showGlobalViewRadio = true;
}
else {
bsAlert(data.data);
@ -73,23 +60,25 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
return false;
});
editor("#editor");
//editor("#editor");
if (avalon.vmodels.add_contest)
var vm = avalon.vmodels.add_contest;
else
var vm = avalon.define({
$id: "add_contest",
title: "",
description: "",
startTime: "",
endTime: "",
password: "",
mode: "0",
showSubmission: true,
isGlobal: true,
allGroups: [],
showGlobalViewRadio: true,
realTimeRank: true
realTimeRank: true,
contestDescriptionEditor: {
editorId: "contest-description-editor",
placeholder: "比赛介绍内容"
}
});
$.ajax({
@ -102,7 +91,6 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
if (data.data.admin_type == 1) {
vm.isGlobal = false;
vm.showGlobalViewRadio = false;
}
}
$.ajax({

View File

@ -1,277 +1,38 @@
require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker", "validator"], function ($, avalon, csrfTokenHeader, bsAlert, editor) {
require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker", "validator", "pager"], function ($, avalon, csrfTokenHeader, bsAlert, editor) {
avalon.ready(function () {
$("#edit-contest-form").validator().on('submit', function (e) {
if (!e.isDefaultPrevented()) {
e.preventDefault();
var ajaxData = {
id: vm.contestList[vm.editingContestId - 1].id,
title: vm.editTitle,
description: vm.editDescription,
mode: vm.editMode,
contest_type: 0,
real_time_rank: vm.editRealTimeRank,
show_user_submission: vm.editShowSubmission,
start_time: vm.editStartTime,
end_time: vm.editEndTime,
visible: vm.editVisible
};
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);
}
}
ajaxData.groups = selectedGroups;
}
else {
if (vm.editPassword) {
ajaxData.password = vm.editPassword;
ajaxData.contest_type = 2;
}
else
ajaxData.contest_type = 1;
}
if (!vm.isGlobal && !selectedGroups.length) {
bsAlert("你没有选择参赛用户!");
return false;
}
if (vm.editDescription == "") {
bsAlert("比赛描述不能为空!");
return false;
}
$.ajax({ // modify contest info
beforeSend: csrfTokenHeader,
url: "/api/admin/contest/",
dataType: "json",
contentType: "application/json;charset=UTF-8",
data: JSON.stringify(ajaxData),
method: "put",
success: function (data) {
if (!data.code) {
bsAlert("修改成功!");
vm.editingContestId = 0; // Hide the editor
vm.getPage(1); // Refresh the contest list
}
else {
bsAlert(data.data);
}
}
});
}
return false;
});
if (avalon.vmodels.contestList) {
// this page has been loaded before, so set the default value
var vm = avalon.vmodels.contestList;
vm.contestList = [];
vm.previousPage = 0;
vm.nextPage = 0;
vm.page = 1;
vm.totalPage = 1;
vm.keyword = "";
vm.editingContestId = 0;
vm.editTitle = "";
vm.editDescription = "";
vm.editProblemList = [];
vm.editPassword = "";
vm.editStartTime = "";
vm.editEndTime = "";
vm.editMode = "";
vm.editShowSubmission = false;
vm.editVisible = false;
vm.editingProblemContestIndex = 0;
vm.editRealTimeRank = true;
}
else {
var vm = avalon.define({
$id: "contestList",
contestList: [],
previousPage: 0,
nextPage: 0,
page: 1,
totalPage: 1,
showVisibleOnly: false,
keyword: "",
editingContestId: 0,
editTitle: "",
editDescription: "",
editProblemList: [],
editPassword: "",
editStartTime: "",
editEndTime: "",
editMode: "",
editShowSubmission: false,
editVisible: false,
editRealTimeRank: true,
editingProblemContestIndex: 0,
isGlobal: true,
allGroups: [],
showGlobalViewRadio: true,
admin_type: 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";
showVisibleOnly: false,
pager: {
getPage: function(page){
getPage(page);
}
},
getPage: function (page_index) {
getPageData(page_index);
},
showEditContestArea: function (contestId) {
if (vm.editingContestId && !confirm("如果继续将丢失未保存的信息,是否继续?"))
return;
if (contestId == vm.editingContestId)
vm.editingContestId = 0;
else {
vm.editingContestId = contestId;
vm.editTitle = vm.contestList[contestId - 1].title;
vm.editPassword = vm.contestList[contestId - 1].password;
vm.editStartTime = vm.contestList[contestId - 1].start_time.substring(0, 16).replace("T", " ");
vm.editEndTime = vm.contestList[contestId - 1].end_time.substring(0, 16).replace("T", " ");
vm.editMode = vm.contestList[contestId - 1].mode;
vm.editVisible = vm.contestList[contestId - 1].visible;
vm.editRealTimeRank = vm.contestList[contestId - 1].real_time_rank;
if (vm.contestList[contestId - 1].contest_type == 0) { //contest type == 0, contest in group
vm.isGlobal = false;
for (var i = 0; i < vm.allGroups.length; i++) {
vm.allGroups[i].isSelected = false;
}
for (var i = 0; i < vm.contestList[contestId - 1].groups.length; i++) {
var id = parseInt(vm.contestList[contestId - 1].groups[i]);
for (var index = 0; vm.allGroups[index]; index++) {
if (vm.allGroups[index].id == id) {
vm.allGroups[index].isSelected = true;
break;
search: function () {
getPage(1);
avalon.vmodels.contestListPager.currentPage = 1;
}
}
}
}
else {
vm.isGlobal = true;
}
vm.editShowSubmission = vm.contestList[contestId - 1].show_user_submission;
editor("#editor").setValue(vm.contestList[contestId - 1].description);
vm.editingProblemContestIndex = 0;
}
},
showEditProblemArea: function (contestId) {
if (vm.editingProblemContestIndex == contestId) {
vm.editingProblemContestIndex = 0;
return;
}
if (vm.editingContestId && !confirm("如果继续将丢失未保存的信息,是否继续?")) {
return;
}
$.ajax({ // Get the problem list of current contest
beforeSend: csrfTokenHeader,
url: "/api/admin/contest_problem/?contest_id=" + vm.contestList[contestId - 1].id,
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
vm.editProblemList = data.data;
}
else {
bsAlert(data.data);
}
}
});
vm.editingContestId = 0;
vm.editingProblemContestIndex = contestId;
vm.editMode = vm.contestList[contestId - 1].mode;
},
addProblem: function () {
vm.$fire("up!showContestProblemPage", 0, vm.contestList[vm.editingProblemContestIndex - 1].id, vm.editMode);
},
showProblemEditPage: function (el) {
vm.$fire("up!showContestProblemPage", el.id, vm.contestList[vm.editingProblemContestIndex - 1].id, vm.editMode);
},
showSubmissionPage: function (el) {
var problemId = 0
if (el)
problemId = el.id;
vm.$fire("up!showContestSubmissionPage", problemId, vm.contestList[vm.editingProblemContestIndex - 1].id, vm.editMode);
},
addToProblemList: function (problem) {
var ajaxData = {
title: problem.title,
description: problem.description,
time_limit: problem.time_limit,
memory_limit: problem.memory_limit,
samples: [],
test_case_id: problem.test_case_id,
hint: problem.hint,
source: problem.contest.title,
visible: false,
tags: [],
input_description: problem.input_description,
output_description: problem.output_description,
difficulty: 0
};
for (var i = 0; i < problem.samples.length; i++) {
ajaxData.samples.push({input: problem.samples[i].input, output: problem.samples[i].output})
}
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/admin/problem/",
dataType: "json",
data: JSON.stringify(ajaxData),
method: "post",
contentType: "application/json;charset=UTF-8",
success: function (data) {
if (!data.code) {
bsAlert("题目添加成功!题目现在处于隐藏状态,请到题目列表手动修改,并添加分类和难度信息!");
}
else {
bsAlert(data.data);
}
}
});
}
});
vm.$watch("showVisibleOnly", function () {
getPageData(1);
})
}
getPageData(1);
//init time picker
$("#contest_start_time").datetimepicker({
format: "yyyy-mm-dd hh:ii",
minuteStep: 5,
weekStart: 1,
language: "zh-CN"
});
$("#contest_end_time").datetimepicker({
format: "yyyy-mm-dd hh:ii",
minuteStep: 5,
weekStart: 1,
language: "zh-CN"
vm.$watch("showVisibleOnly", function () {
getPage(1);
avalon.vmodels.contestListPager.currentPage = 1;
});
function getPageData(page) {
var url = "/api/admin/contest/?paging=true&page=" + page + "&page_size=10";
function getPage(page) {
var url = "/api/admin/contest/?paging=true&page=" + page + "&page_size=20";
if (vm.showVisibleOnly)
url += "&visible=true"
url += "&visible=true";
if (vm.keyword != "")
url += "&keyword=" + vm.keyword;
$.ajax({
@ -281,10 +42,8 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
success: function (data) {
if (!data.code) {
vm.contestList = data.data.results;
vm.totalPage = data.data.total_page;
vm.previousPage = data.data.previous_page;
vm.nextPage = data.data.next_page;
vm.page = page;
vm.announcementList = data.data.results;
avalon.vmodels.contestListPager.totalPage = data.data.total_page;
}
else {
bsAlert(data.data);
@ -293,46 +52,6 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
});
}
// Get group list
$.ajax({
url: "/api/user/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
var admin_type = data.data.admin_type;
vm.admin_type = 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);
}
}
});
}
});
});
avalon.scan();
});

View File

@ -0,0 +1,321 @@
require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker", "validator"], function ($, avalon, csrfTokenHeader, bsAlert, editor) {
avalon.ready(function () {
$("#edit-contest-form").validator().on('submit', function (e) {
if (!e.isDefaultPrevented()) {
e.preventDefault();
var ajaxData = {
id: vm.contestList[vm.editingContestId - 1].id,
title: vm.editTitle,
description: vm.editDescription,
mode: vm.editMode,
contest_type: 0,
real_time_rank: vm.editRealTimeRank,
show_user_submission: vm.editShowSubmission,
start_time: vm.editStartTime,
end_time: vm.editEndTime,
visible: vm.editVisible
};
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);
}
}
ajaxData.groups = selectedGroups;
}
else {
if (vm.editPassword) {
ajaxData.password = vm.editPassword;
ajaxData.contest_type = 2;
}
else
ajaxData.contest_type = 1;
}
if (!vm.isGlobal && !selectedGroups.length) {
bsAlert("你没有选择参赛用户!");
return false;
}
if (vm.editDescription == "") {
bsAlert("比赛描述不能为空!");
return false;
}
$.ajax({ // modify contest info
beforeSend: csrfTokenHeader,
url: "/api/admin/contest/",
dataType: "json",
contentType: "application/json;charset=UTF-8",
data: JSON.stringify(ajaxData),
method: "put",
success: function (data) {
if (!data.code) {
bsAlert("修改成功!");
vm.editingContestId = 0; // Hide the editor
vm.getPage(1); // Refresh the contest list
}
else {
bsAlert(data.data);
}
}
});
}
return false;
});
if (avalon.vmodels.contestList) {
// this page has been loaded before, so set the default value
var vm = avalon.vmodels.contestList;
vm.contestList = [];
}
else {
var vm = avalon.define({
$id: "contestList",
contestList: [],
previousPage: 0,
nextPage: 0,
page: 1,
totalPage: 1,
showVisibleOnly: false,
keyword: "",
editingContestId: 0,
editTitle: "",
editDescription: "",
editProblemList: [],
editPassword: "",
editStartTime: "",
editEndTime: "",
editMode: "",
editShowSubmission: false,
editVisible: false,
editRealTimeRank: true,
editingProblemContestIndex: 0,
isGlobal: true,
allGroups: [],
showGlobalViewRadio: true,
admin_type: 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);
},
showEditContestArea: function (contestId) {
if (vm.editingContestId && !confirm("如果继续将丢失未保存的信息,是否继续?"))
return;
if (contestId == vm.editingContestId)
vm.editingContestId = 0;
else {
vm.editingContestId = contestId;
vm.editTitle = vm.contestList[contestId - 1].title;
vm.editPassword = vm.contestList[contestId - 1].password;
vm.editStartTime = vm.contestList[contestId - 1].start_time.substring(0, 16).replace("T", " ");
vm.editEndTime = vm.contestList[contestId - 1].end_time.substring(0, 16).replace("T", " ");
vm.editMode = vm.contestList[contestId - 1].mode;
vm.editVisible = vm.contestList[contestId - 1].visible;
vm.editRealTimeRank = vm.contestList[contestId - 1].real_time_rank;
if (vm.contestList[contestId - 1].contest_type == 0) { //contest type == 0, contest in group
vm.isGlobal = false;
for (var i = 0; i < vm.allGroups.length; i++) {
vm.allGroups[i].isSelected = false;
}
for (var i = 0; i < vm.contestList[contestId - 1].groups.length; i++) {
var id = parseInt(vm.contestList[contestId - 1].groups[i]);
for (var index = 0; vm.allGroups[index]; index++) {
if (vm.allGroups[index].id == id) {
vm.allGroups[index].isSelected = true;
break;
}
}
}
}
else {
vm.isGlobal = true;
}
vm.editShowSubmission = vm.contestList[contestId - 1].show_user_submission;
editor("#editor").setValue(vm.contestList[contestId - 1].description);
vm.editingProblemContestIndex = 0;
}
},
showEditProblemArea: function (contestId) {
if (vm.editingProblemContestIndex == contestId) {
vm.editingProblemContestIndex = 0;
return;
}
if (vm.editingContestId && !confirm("如果继续将丢失未保存的信息,是否继续?")) {
return;
}
$.ajax({ // Get the problem list of current contest
beforeSend: csrfTokenHeader,
url: "/api/admin/contest_problem/?contest_id=" + vm.contestList[contestId - 1].id,
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
vm.editProblemList = data.data;
}
else {
bsAlert(data.data);
}
}
});
vm.editingContestId = 0;
vm.editingProblemContestIndex = contestId;
vm.editMode = vm.contestList[contestId - 1].mode;
},
addProblem: function () {
vm.$fire("up!showContestProblemPage", 0, vm.contestList[vm.editingProblemContestIndex - 1].id, vm.editMode);
},
showProblemEditPage: function (el) {
vm.$fire("up!showContestProblemPage", el.id, vm.contestList[vm.editingProblemContestIndex - 1].id, vm.editMode);
},
showSubmissionPage: function (el) {
var problemId = 0
if (el)
problemId = el.id;
vm.$fire("up!showContestSubmissionPage", problemId, vm.contestList[vm.editingProblemContestIndex - 1].id, vm.editMode);
},
addToProblemList: function (problem) {
var ajaxData = {
title: problem.title,
description: problem.description,
time_limit: problem.time_limit,
memory_limit: problem.memory_limit,
samples: [],
test_case_id: problem.test_case_id,
hint: problem.hint,
source: problem.contest.title,
visible: false,
tags: [],
input_description: problem.input_description,
output_description: problem.output_description,
difficulty: 0
};
for (var i = 0; i < problem.samples.length; i++) {
ajaxData.samples.push({input: problem.samples[i].input, output: problem.samples[i].output})
}
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/admin/problem/",
dataType: "json",
data: JSON.stringify(ajaxData),
method: "post",
contentType: "application/json;charset=UTF-8",
success: function (data) {
if (!data.code) {
bsAlert("题目添加成功!题目现在处于隐藏状态,请到题目列表手动修改,并添加分类和难度信息!");
}
else {
bsAlert(data.data);
}
}
});
}
});
vm.$watch("showVisibleOnly", function () {
getPageData(1);
})
}
getPageData(1);
//init time picker
$("#contest_start_time").datetimepicker({
format: "yyyy-mm-dd hh:ii",
minuteStep: 5,
weekStart: 1,
language: "zh-CN"
});
$("#contest_end_time").datetimepicker({
format: "yyyy-mm-dd hh:ii",
minuteStep: 5,
weekStart: 1,
language: "zh-CN"
});
function getPageData(page) {
var url = "/api/admin/contest/?paging=true&page=" + page + "&page_size=10";
if (vm.showVisibleOnly)
url += "&visible=true"
if (vm.keyword != "")
url += "&keyword=" + vm.keyword;
$.ajax({
url: url,
dataType: "json",
method: "get",
success: function (data) {
if (!data.code) {
vm.contestList = 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);
}
}
});
}
// Get group list
$.ajax({
url: "/api/user/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
var admin_type = data.data.admin_type;
vm.admin_type = 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);
}
}
});
}
});
});
avalon.scan();
});

View File

@ -14,21 +14,18 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
method: "post",
success: function (data) {
if (!data.code) {
//成功登陆
var ref = document.referrer;
if (ref) {
// 注册页和本页的来源的跳转回首页,防止死循环
if (ref.indexOf("register") > -1 || ref.indexOf("login") > -1) {
function getLocationVal(id){
var temp = unescape(location.search).split(id+"=")[1] || "";
return temp.indexOf("&")>=0 ? temp.split("&")[0] : temp;
}
var from = getLocationVal("__from");
if(from != ""){
console.log(from);
window.location.href = from;
}
else{
location.href = "/";
return;
}
// 判断来源,只有同域下才跳转
if (ref.split("/")[2].split(":")[0] == location.hostname) {
location.href = ref;
return;
}
}
location.href = "/";
}
else {
refresh_captcha();

View File

@ -25,6 +25,7 @@
// ------ admin web 组件 ----------
pager: "components/pager",
editorComponent: "components/editorComponent",
// ------ 下面写的都不要直接用,而是使用上面的封装版本 ------
//富文本编辑器simditor -> editor
@ -153,7 +154,7 @@
},
{
name: "submissionList_21_pack"
}
},
],
optimizeCss: "standard",
})

View File

@ -17,8 +17,7 @@
</div>
<div class="col-md-12">
<div class="form-group">
<textarea id="editor" placeholder="这里输入内容" autofocus ms-duplex="description"></textarea>
<p class="error-info" ms-visible="description==''">请填写比赛描述</p>
<ms:editor $id="contestDescriptionEditor" config="contestDescriptionEditor"></ms:editor>
</div>
</div>
<div class="col-md-6">
@ -39,7 +38,7 @@
</div>
<div class="col-md-6">
<div class="form-group">
<label>可见范围</label>
<label>比赛类型</label>
<div>
<span ms-if="showGlobalViewRadio">
@ -70,30 +69,12 @@
<input type="checkbox" value="group_id" ms-duplex-checked="el.isSelected">&nbsp;&nbsp;{{ el.name }}
</div>
</div>
<div class="col-md-3">
<label>排名方式</label>
<div class="form-group">
<label><input type="radio" name="mode" ms-duplex-string="mode" value="0">
<small>ACM</small>
</label>
<label><input type="radio" name="mode" ms-duplex-string="mode" value="1">
<small>分数</small>
</label>
</div>
</div>
<div class="col-md-3">
<label>公开提交记录</label>
<div class="form-group">
<label class="text"><input type="checkbox" ms-duplex-checked="showSubmission">
<small>公开</small>
</label>
</div>
</div>
<div class="col-md-3">
<div class="col-md-12">
<label>实时排名</label>
<div class="form-group">
<label class="text"><input type="checkbox" ms-duplex-checked="realTimeRank">
<small></small>
<small>如果不勾选,排名会被缓存,不会更新,而且只显示自己的提交。用于 acm 封榜。</small>
</label>
</div>
</div>

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,10 +38,10 @@
<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="contestListPager" config="pager"></ms:pager>
</div>
<!--
<div ms-visible="editingContestId">
<form id="edit-contest-form">
<div class="col-md-12">
@ -108,7 +108,7 @@
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="editPassword">
</div>
<div class="form-group col-md-12" ms-visible="!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>
@ -186,6 +186,8 @@
</div>
-->
</div>
<script src="/static/js/app/admin/contest/contestList.js"></script>

View File

@ -1,24 +0,0 @@
{% 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

@ -0,0 +1,20 @@
{% extends "oj_base.html" %}
{% block title %}
授权登录
{% endblock %}
{% block body %}
<div class="container main">
<div class="text-center">
<p>3秒钟后将使用账号{{ request.user.username }}登录<span id="link">{{ callback }}</span></p>
<button class="btn btn-warning" onclick="location.href='/'">取消登录</button>
<button class="btn btn-success" onclick="location.href='/login/'">更换账号</button>
<script>setTimeout(function(){
window.location.href = "{{ redirect_url }}"},
3000);
</script>
</div>
</div>
{% endblock %}
{% block js_block %}
{% endblock %}

View File

@ -1,90 +1,83 @@
{% extends "oj_base.html" %}
{% block title %}
{{ user.username }}的主页
{% endblock %}
{% block body %}
<div class="container main">
<div class="col-lg-4">
<div class="avatar">
<img src="https://coding.net/static/fruit_avatar/Fruit-1.png" class="img-responsive"
style="height: 200px;width: 200px;">
<img src="{{ user.userprofile.avatar }}" class="img-responsive" id="index-avatar">
</div>
<div>
<h2>virusdefender</h2>
<h2>
{{ user.username }}
{% ifequal user.admin_type 2 %}
<span class="glyphicon glyphicon-star super-admin-star" title="超级管理员"></span>
{% endifequal %}
{% ifequal user.admin_type 1 %}
<span class="glyphicon glyphicon-star admin-star"></span>
{% endifequal %}
</h2>
<p id="user-mood">{{ user.userprofile.mood }}</p>
</div>
<div class="list-group col-lg-10">
<div class="list-group col-lg-9">
{% if user.userprofile.blog %}
<p class="list-group-item"><span class="glyphicon glyphicon-link"></span>
<a href="https://virusdefender.net">https://virusdefender.net</a>
<a href="{{ user.userprofile.blog }}" target="_blank">{{ blog_link }}</a>
</p>
{% endif %}
{% if user.userprofile.hduoj_username %}
<p class="list-group-item">
<img src="/static/img/oj_logo/hdu_logo.png" id="oj-logo">
<a href="http://bestcoder.hdu.edu.cn/rating.php?user={{ user.userprofile.hduoj_username }}" target="_blank">
{{ user.userprofile.hduoj_username }}
</a>
</p>
{% endif %}
{% if user.userprofile.bestcoder_username %}
<p class="list-group-item">
<img src="/static/img/oj_logo/bestcoder_logo.png" id="oj-logo">
<a href="http://bestcoder.hdu.edu.cn/rating.php?user={{ user.userprofile.bestcoder_username }}" target="_blank">
{{ user.userprofile.bestcoder_username }}
</a>
</p>
{% endif %}
{% if user.userprofile.codeforces_username %}
<p class="list-group-item">
<img src="/static/img/oj_logo/codeforces_logo.png" id="oj-logo">
<a href="http://codeforces.com/profile/{{ user.userprofile.codeforces_username }}" target="_blank">
{{ user.userprofile.codeforces_username }}
</a>
</p>
{% endif %}
<p class="list-group-item">
<img src="/static/img/oj_logo/hdu_logo.png" style="height: 20px">
<a href="https://virusdefender.net">https://virusdefender.net</a>
<span class="glyphicon glyphicon-calendar"></span>
{{ user.create_time }}
</p>
<p class="list-group-item">
<img src="/static/img/oj_logo/bestcoder_logo.png" style="height: 20px">
<a href="https://virusdefender.net">https://virusdefender.net</a>
</p>
<p class="list-group-item">
<img src="/static/img/oj_logo/codeforces_logo.png" style="height: 20px">
<a href="https://virusdefender.net">https://virusdefender.net</a>
</p>
<p class="list-group-item"><span class="glyphicon glyphicon-calendar"></span> 2015-9-10</p>
<div class="rows">
<div class="col-lg-4 text-center">
<strong id="user-data-number">{{ user.userprofile.rank }}</strong>
<span id="user-data-text">Rank</span>
</div>
<div class="col-lg-4 text-center">
<strong id="user-data-number">{{ user.userprofile.accepted_number }}</strong>
<span id="user-data-text">AC</span>
</div>
<div class="col-lg-4 text-center">
<strong id="user-data-number">{{ user.userprofile.submissions_number }}</strong>
<span id="user-data-text">Submissions</span>
</div>
</div>
</div>
</div>
<div class="col-lg-8">
<ul class="nav nav-tabs" style="margin: 10px;;">
<li role="presentation" class="active"><a href="#">Home</a></li>
<li role="presentation"><a href="#123">全部分享</a></li>
</ul>
<div class="col-lg-6">
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">正在做的题</h3></div>
<div class="list-group">
<p class="list-group-item">
<a href="#" style="font-size: large;">problem title</a>
<span class="right">3 / 10</span>
<span style="display: block;">Accepted</span>
</p>
<p class="list-group-item">
<a href="#" style="font-size: large;">problem title</a>
<span class="right">3 / 10</span>
<span style="display: block;">Accepted</span>
</p>
<p class="list-group-item">
<a href="#" style="font-size: large;">problem title</a>
<span class="right">3 / 10</span>
<span style="display: block;">Accepted</span>
</p>
<p class="list-group-item">
<a href="#" style="font-size: large;">problem title</a>
<span class="right">3 / 10</span>
<span style="display: block;">Accepted</span>
</p>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">分享的代码</h3></div>
<div class="panel-body"> Panel content</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js_block %}
<script src="/static/js/app/oj/account/register.js"></script>
{% endblock %}

View File

@ -40,7 +40,7 @@
<th class="text-center">用时 + 罚时</th>
{% for item in contest_problems %}
<th class="text-center">
<a href="/contest/{{ contest.id }}/problem/{{ item.id }}/">{{ item.sort_index }}</a>
<a href="/contest/{{ contest.id }}/submissions/?problem_id={{ item.id }}">{{ item.sort_index }}</a>
</th>
{% endfor %}
</tr>
@ -48,7 +48,7 @@
<tbody class="rank">
{% for item in rank %}
<tr>
<th scope="row">{{ forloop.counter|add:paging_info.offset}}</th>
<th scope="row">{% if item.total_ac_number %}{{ forloop.counter|add:paging_info.offset}}{% else %}-{% endif %}</th>
<td>
<a href="/contest/{{ contest.id }}/submissions/?user_id={{ item.user__id }}">
{{ item.user__username }}

View File

@ -107,14 +107,14 @@
<ul class="pager">
{% if previous_page %}
<li class="previous">
<a href="/contest/{{ contest.id }}/submissions/{{ previous_page }}/{% if filter %}?{{ filter.name }}={{ filter.content }}{% if user_id %}&user_id={{ user_id }}{% endif %}{% else %}{% if user_id %}?user_id={{ user_id }}{% endif %}{% endif %}">
<a href="/contest/{{ contest.id }}/submissions/{{ previous_page }}/?{% if filter %}{{ filter.name }}={{ filter.content }}&{% endif %}{% if user_id %}user_id={{ user_id }}&{% endif %}{% if problem_id %}problem_id={{ problem_id }}&{% endif %}">
<span aria-hidden="true">&larr;</span> 上一页
</a>
</li>
{% endif %}
{% if next_page %}
<li class="next">
<a href="/contest/{{ contest.id }}/submissions/{{ next_page }}/{% if filter %}?{{ filter.name }}={{ filter.content }}{% if user_id %}&user_id={{ user_id }}{% endif %}{% else %}{% if user_id %}?user_id={{ user_id }}{% endif %}{% endif %}">
<a href="/contest/{{ contest.id }}/submissions/{{ next_page }}/?{% if filter %}{{ filter.name }}={{ filter.content }}&{% endif %}{% if user_id %}user_id={{ user_id }}&{% endif %}{% if problem_id %}problem_id={{ problem_id }}&{% endif %}">
下一页 <span aria-hidden="true">&rarr;</span>
</a>
</li>