Merge branch 'dev' into hohoTT-dev

This commit is contained in:
hohoTT 2015-09-13 19:24:41 +08:00
commit 2dbd9c24a5
50 changed files with 882 additions and 669 deletions

1
.gitignore vendored
View File

@ -58,6 +58,7 @@ log/
static/release/css
static/release/js
static/release/img
static/src/upload_image/*
build.txt
tmp/
test_case/

1
Accessories/__init__.py Normal file
View File

@ -0,0 +1 @@
__author__ = 'root'

74
Accessories/reJudge.py Normal file
View File

@ -0,0 +1,74 @@
import django
from contest.models import *
from problem.models import *
from submission.models import Submission
import redis
from judge.judger_controller.tasks import judge
from judge.judger_controller.settings import redis_config
django.setup()
def rejudge(submission):
# for submission in submission:
# submission_id = submission.id
# try:
# command = "%s run -t -i --privileged --rm=true " \
# "-v %s:/var/judger/test_case/ " \
# "-v %s:/var/judger/code/ " \
# "%s " \
# "python judge/judger/run.py " \
# "--solution_id %s --time_limit %s --memory_limit %s --test_case_id %s" % \
# (docker_config["docker_path"],
# test_case_dir,
# source_code_dir,
# docker_config["image_name"],
# submission_id, str(time_limit), str(memory_limit), test_case_id)
# subprocess.call(command, shell=docker_config["shell"])
# except Exception as e:
# print e
return
def easy_rejudge(submissions, map_table, user_id, contest_id=None):
try:
user = User.objects.get(pk=user_id)
except User.DoesNotExist:
print "User.DoesNotExist!"
return
problemDict = {}
for oldSubmission in submission:
problem_id = map_table[oldSubmission.problem_id]
if problem_id in problemDict:
problem = problemDict[problem_id]
else:
try:
p = Problem.objects.get(pk=problem_id)
except Problem.DoesNotExist:
print " Problem.DoesNotExist!" + str(problem_id)
continue
problem = p
problemDict[problem_id] = p
submission = Submission.objects.create(
user_id=user_id,
language=oldSubmission.language,
code=oldSubmission.code,
contest_id=contest_id,
problem_id=problem_id,
originResult=oldSubmission.result
)
try:
judge.delay(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
except Exception:
print "error!"
continue
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
r.incr("judge_queue_length")
return

61
Accessories/utils.py Executable file
View File

@ -0,0 +1,61 @@
import django
from contest.models import *
from problem.models import *
django.setup()
def add_exist_problem_to_contest(problems, contest_id):
try:
contest = Contest.objects.get(pk=contest_id)
except Contest.DoesNotExist:
print "Contest Doesn't Exist!"
return
i = 1
for problem in problems:
print "Add the problem:"
print problem.title
print "The sort Index is" + str(i) + " You Can modify it latter as you like~"
ContestProblem.objects.create(contest=contest, sort_index=str(i),
title=problem.title, description=problem.description,
input_description=problem.input_description,
output_description=problem.output_description,
samples=problem.samples,
test_case_id=problem.test_case_id,
hint=problem.hint,
created_by=problem.created_by,
time_limit=problem.time_limit,
memory_limit=problem.memory_limit)
i += 1
return
def add_contest_problem_to_problem(contest_id):
try:
contest = Contest.objects.get(pk=contest_id)
except Contest.DoesNotExist:
print "Contest Doesn't Exist!"
return
#Get all problems in this contest
problems = ContestProblem.objects.filter(contest=contest)
#get a tag
try:
tag = ProblemTag.objects.get(name=contest.title)
except ProblemTag.DoesNotExist:
tag = ProblemTag.objects.create(name=contest.title)
#for each problem
for problem in problems:
print "Add problem to problem list:"
print problem.title
p = Problem.objects.create(title=problem.title,
description=problem.description,
input_description=problem.input_description,
output_description=problem.output_description,
samples=problem.samples,
test_case_id=problem.test_case_id,
hint=problem.hint,
created_by=problem.created_by,
time_limit=problem.time_limit,
memory_limit=problem.memory_limit,
visible = False,
difficulty = 0,
source = contest.title)
p.tags.add(tag)
return

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('contest', '0007_contestsubmission_ac_time'),
]
operations = [
migrations.RenameField(
model_name='contest',
old_name='show_rank',
new_name='real_time_rank',
),
]

View File

@ -17,8 +17,8 @@ class Contest(models.Model):
description = models.TextField()
# 比赛模式0 即为是acm模式1 即为是按照总的 ac 题目数量排名模式
mode = models.IntegerField()
# 是否显示排名结果
show_rank = models.BooleanField()
# 是否显示实时排名结果
real_time_rank = models.BooleanField()
# 是否显示别人的提交记录
show_user_submission = models.BooleanField()
# 只能超级管理员创建公开赛,管理员只能创建小组内部的比赛

View File

@ -335,10 +335,7 @@ def contest_problems_list_page(request, contest_id):
item.state = 2
else:
item.state = 0
# 右侧的公告列表
announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
return render(request, "oj/contest/contest_problems_list.html", {"contest_problems": contest_problems,
"announcements": announcements,
"contest": {"id": contest_id}})
@ -356,8 +353,8 @@ def contest_list_page(request, page=1):
# 筛选我能参加的比赛
join = request.GET.get("join", None)
if join:
contests = contests.filter(Q(contest_type__in=[PUBLIC_CONTEST, PASSWORD_PUBLIC_CONTEST]) | Q(groups__in=request.user.group_set.all())). \
if request.user.is_authenticated and join:
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)
@ -378,14 +375,10 @@ def contest_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/contest/contest_list.html",
{"contests": current_page, "page": int(page),
"previous_page": previous_page, "next_page": next_page,
"keyword": keyword, "announcements": announcements,
"join": join})
"keyword": keyword, "join": join})
def _cmp(x, y):

View File

@ -119,6 +119,14 @@ def contest_problem_submissions_list_page(request, contest_id, page=1):
next_page = current_page.next_page_number()
except Exception:
pass
# 如果该用户是超级管理员那么他可以查看所有的提交记录详情
if request.user.admin_type > 1:
return render(request, "oj/contest/submissions_list_admin.html",
{"submissions": current_page, "page": int(page),
"previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20,
"contest": contest})
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,

View File

@ -254,9 +254,6 @@ class JoinGroupRequestAdminAPIView(APIView, GroupAPIViewBase):
@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)
@ -282,10 +279,10 @@ def group_list_page(request, page=1):
pass
return render(request, "oj/group/group_list.html", {
"groups": groups, "announcements": announcements,
"groups": groups,
"contests": current_page, "page": int(page),
"previous_page": previous_page, "next_page": next_page,
"keyword": keyword, "announcements": announcements,
"keyword": keyword
})

View File

@ -1,4 +1,5 @@
# coding=utf-8
import os
import json
import commands
import hashlib
@ -7,9 +8,9 @@ from multiprocessing import Pool
from settings import max_running_number, lrun_gid, lrun_uid, judger_workspace
from language import languages
from result import result
from compiler import compile_
from judge_exceptions import JudgeClientError, CompileError
from judge_exceptions import JudgeClientError
from utils import parse_lrun_output
from logger import logger
# 下面这个函数作为代理访问实例变量否则Python2会报错是Python2的已知问题
@ -82,6 +83,8 @@ class JudgeClient(object):
# 倒序找到MEMORY的位置
output_start = output.rfind("MEMORY")
if output_start == -1:
logger.error("Lrun result parse error")
logger.error(output)
raise JudgeClientError("Lrun result parse error")
# 如果不是0说明lrun输出前面有输出也就是程序的stderr有内容
if output_start != 0:
@ -92,7 +95,8 @@ class JudgeClient(object):
return error, parse_lrun_output(output)
def _compare_output(self, test_case_id):
test_case_md5 = self._test_case_info["test_cases"][str(test_case_id)]["output_md5"]
test_case_config = self._test_case_info["test_cases"][str(test_case_id)]
test_case_md5 = test_case_config["output_md5"]
output_path = judger_workspace + str(test_case_id) + ".out"
try:
@ -102,6 +106,7 @@ class JudgeClient(object):
return False
# 计算输出文件的md5 和之前测试用例文件的md5进行比较
# 现在比较的是完整的文件
md5 = hashlib.md5()
while True:
data = f.read(2 ** 8)
@ -109,9 +114,18 @@ class JudgeClient(object):
break
md5.update(data)
# 对比文件是否一致
# todo 去除最后的空行
return md5.hexdigest() == test_case_md5
if md5.hexdigest() == test_case_md5:
return True
else:
# 这时候需要去除用户输出最后的空格和换行 再去比较md5
# 兼容之前没有striped_output_md5的测试用例
if "striped_output_md5" not in test_case_config:
return False
f.seek(0)
striped_md5 = hashlib.md5()
# 比较和返回去除空格后的md5比较结果
striped_md5.update(f.read().rstrip())
return striped_md5.hexdigest() == test_case_config["striped_output_md5"]
def _judge_one(self, test_case_id):
# 运行lrun程序 接收返回值
@ -123,21 +137,23 @@ class JudgeClient(object):
run_result["test_case_id"] = test_case_id
# 如果返回值非0 或者信号量不是0 或者程序的stderr有输出 代表非正常结束
if run_result["exit_code"] or run_result["term_sig"] or run_result["siginaled"] or error:
run_result["result"] = result["runtime_error"]
return run_result
# 代表内存或者时间超过限制了
# 代表内存或者时间超过限制了 程序被终止掉 要在runtime error 之前判断
if run_result["exceed"]:
if run_result["exceed"] == "memory":
run_result["result"] = result["memory_limit_exceeded"]
elif run_result["exceed"] in ["cpu_time", "real_time"]:
run_result["result"] = result["time_limit_exceeded"]
else:
logger.error("Error exceeded type: " + run_result["exceed"])
logger.error(output)
raise JudgeClientError("Error exceeded type: " + run_result["exceed"])
return run_result
# 如果返回值非0 或者信号量不是0 或者程序的stderr有输出 代表非正常结束
if run_result["exit_code"] or run_result["term_sig"] or run_result["siginaled"] or error:
run_result["result"] = result["runtime_error"]
return run_result
# 下面就是代码正常运行了 需要判断代码的输出是否正确
if self._compare_output(test_case_id):
run_result["result"] = result["accepted"]
@ -160,8 +176,8 @@ class JudgeClient(object):
try:
results.append(item.get())
except Exception as e:
# todo logging
print e
logger.error("system error")
logger.error(e)
results.append({"result": result["system_error"]})
return results

View File

@ -4,6 +4,7 @@ import commands
from settings import lrun_uid, lrun_gid
from judge_exceptions import CompileError, JudgeClientError
from utils import parse_lrun_output
from logger import logger
def compile_(language_item, src_path, exe_path):
@ -22,14 +23,20 @@ def compile_(language_item, src_path, exe_path):
output_start = output.rfind("MEMORY")
if output_start == -1:
logger.error("Compiler error")
logger.error(output)
raise JudgeClientError("Error running compiler in lrun")
# 返回值不为0 或者 stderr中lrun的输出之前有东西
if status or output_start:
# 返回值不为 0 或者 stderr 中 lrun 的输出之前有 erro r字符串
# 判断 error 字符串的原因是链接的时候可能会有一些不推荐使用的函数的的警告,
# 但是 -w 参数并不能关闭链接时的警告
if status or "error" in output[0:output_start]:
raise CompileError(output[0:output_start])
parse_result = parse_lrun_output(output)
parse_result = parse_lrun_output(output[output_start:])
if parse_result["exit_code"] or parse_result["term_sig"] or parse_result["siginaled"] or parse_result["exceed"]:
logger.error("Compiler error")
logger.error(output)
raise CompileError("Compile error")
return exe_path

8
judge/judger/logger.py Normal file
View File

@ -0,0 +1,8 @@
# coding=utf-8
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s',
filename='log/judge.log')
logger = logging

View File

@ -1,163 +0,0 @@
# coding=utf-8
import json
import commands
import hashlib
from multiprocessing import Pool
from settings import max_running_number, lrun_gid, lrun_uid, judger_workspace
from language import languages
from result import result
from compiler import compile_
from judge_exceptions import JudgeClientError, CompileError
from utils import parse_lrun_output
# 下面这个函数作为代理访问实例变量否则Python2会报错是Python2的已知问题
# http://stackoverflow.com/questions/1816958/cant-pickle-type-instancemethod-when-using-pythons-multiprocessing-pool-ma/7309686
def _run(instance, test_case_id):
return instance._judge_one(test_case_id)
class JudgeClient(object):
def __init__(self, language_code, exe_path, max_cpu_time,
max_real_time, max_memory, test_case_dir):
"""
:param language_code: 语言编号
:param exe_path: 可执行文件路径
:param max_cpu_time: 最大cpu时间单位ms
:param max_real_time: 最大执行时间单位ms
:param max_memory: 最大内存单位MB
:param test_case_dir: 测试用例文件夹路径
:return:返回结果list
"""
self._language = languages[language_code]
self._exe_path = exe_path
self._max_cpu_time = max_cpu_time
self._max_real_time = max_real_time
self._max_memory = max_memory
self._test_case_dir = test_case_dir
# 进程池
self._pool = Pool(processes=max_running_number)
def _generate_command(self, test_case_id):
"""
设置相关运行限制 进制访问网络 如果启用tmpfs 就把代码输出写入tmpfs否则写入硬盘
"""
# todo 系统调用白名单 chroot等参数
command = "lrun" + \
" --max-cpu-time " + str(self._max_cpu_time / 1000.0) + \
" --max-real-time " + str(self._max_real_time / 1000.0 * 2) + \
" --max-memory " + str(self._max_memory * 1000 * 1000) + \
" --network false" + \
" --uid " + str(lrun_uid) + \
" --gid " + str(lrun_gid)
execute_command = self._language["execute_command"].format(exe_path=self._exe_path)
command += (" " +
execute_command +
# 0就是stdin
" 0<" + self._test_case_dir + str(test_case_id) + ".in" +
# 1就是stdout
" 1>" + judger_workspace + str(test_case_id) + ".out" +
# 3是stderr包含lrun的输出和程序的异常输出
" 3>&2")
return command
def _parse_lrun_output(self, output):
# 要注意的是 lrun把结果输出到了stderr所以有些情况下lrun的输出可能与程序的一些错误输出的混合的要先分离一下
error = None
# 倒序找到MEMORY的位置
output_start = output.rfind("MEMORY")
if output_start == -1:
raise JudgeClientError("Lrun result parse error")
# 如果不是0说明lrun输出前面有输出也就是程序的stderr有内容
if output_start != 0:
error = output[0:output_start]
# 分离出lrun的输出
output = output[output_start:]
return error, parse_lrun_output(output)
def _compare_output(self, test_case_id):
output_path = judger_workspace + str(test_case_id) + ".out"
try:
f = open(output_path, "r")
except IOError:
# 文件不存在等引发的异常 返回结果错误
return False
try:
std = open(self._test_case_dir+test_case_id+".out", "r")
except IOError:
# 文件不存在等引发的异常 返回结果错误
return False
lines=std.readline()
line_conut = len(lines)
for i in range(0, line_conut-2):
if lines[i]
def _judge_one(self, test_case_id):
# 运行lrun程序 接收返回值
command = self._generate_command(test_case_id)
status_code, output = commands.getstatusoutput(command)
if status_code:
raise JudgeClientError(output)
error, run_result = self._parse_lrun_output(output)
run_result["test_case_id"] = test_case_id
# 如果返回值非0 或者信号量不是0 或者程序的stderr有输出 代表非正常结束
if run_result["exit_code"] or run_result["term_sig"] or run_result["siginaled"] or error:
run_result["result"] = result["runtime_error"]
return run_result
# 代表内存或者时间超过限制了
if run_result["exceed"]:
if run_result["exceed"] == "memory":
run_result["result"] = result["memory_limit_exceeded"]
elif run_result["exceed"] in ["cpu_time", "real_time"]:
run_result["result"] = result["time_limit_exceeded"]
else:
raise JudgeClientError("Error exceeded type: " + run_result["exceed"])
return run_result
# 下面就是代码正常运行了 需要判断代码的输出是否正确
if self._compare_output(test_case_id):
run_result["result"] = result["accepted"]
else:
run_result["result"] = result["wrong_answer"]
return run_result
def run(self):
# 添加到任务队列
_results = []
results = []
for i in range(self._test_case_info["test_case_number"]):
_results.append(self._pool.apply_async(_run, (self, i + 1)))
self._pool.close()
self._pool.join()
for item in _results:
# 注意多进程中的异常只有在get()的时候才会被引发
# http://stackoverflow.com/questions/22094852/how-to-catch-exceptions-in-workers-in-multiprocessing
try:
results.append(item.get())
except Exception as e:
# todo logging
print e
results.append({"result": result["system_error"]})
return results
def __getstate__(self):
# 不同的pool之间进行pickle的时候要排除自己否则报错
# http://stackoverflow.com/questions/25382455/python-notimplementederror-pool-objects-cannot-be-passed-between-processes
self_dict = self.__dict__.copy()
del self_dict['_pool']
return self_dict

View File

@ -2,21 +2,13 @@
import sys
import json
import MySQLdb
import os
# 判断判题模式
judge_model = os.environ.get("judge_model", "default")
if judge_model == "default":
from client import JudgeClient
elif judge_model == "loose":
from loose_client import JudgeClient
from client import JudgeClient
from language import languages
from compiler import compile_
from result import result
from settings import judger_workspace
from settings import submission_db
from settings import judger_workspace, submission_db
from logger import logger
# 简单的解析命令行参数
@ -67,7 +59,6 @@ except Exception as e:
conn.commit()
exit()
print "Compile successfully"
# 运行
try:
client = JudgeClient(language_code=language_code,
@ -87,16 +78,13 @@ try:
judge_result["accepted_answer_time"] = l[-1]["cpu_time"]
except Exception as e:
print e
logger.error(e)
conn = db_conn()
cur = conn.cursor()
cur.execute("update submission set result=%s, info=%s where id=%s", (result["system_error"], str(e), submission_id))
conn.commit()
exit()
print "Run successfully"
print judge_result
conn = db_conn()
cur = conn.cursor()
cur.execute("update submission set result=%s, info=%s, accepted_answer_time=%s where id=%s",

View File

@ -19,6 +19,8 @@ docker_config = {
test_case_dir = "/var/mnt/source/test_case/"
# 源代码路径,也就是 manage.py 所在的实际路径
source_code_dir = "/var/mnt/source/OnlineJudge/"
# 日志文件夹路径
log_dir = "/var/log/"
# 存储提交信息的数据库,是 celery 使用的,与 oj.settings/local_settings 等区分,那是 web 服务器访问的地址

View File

@ -5,7 +5,7 @@ import MySQLdb
import subprocess
from ..judger.result import result
from ..judger_controller.celery import app
from settings import docker_config, source_code_dir, test_case_dir, submission_db, redis_config
from settings import docker_config, source_code_dir, test_case_dir, log_dir, submission_db, redis_config
@app.task
@ -14,17 +14,18 @@ def judge(submission_id, time_limit, memory_limit, test_case_id):
command = "%s run -t -i --privileged --rm=true " \
"-v %s:/var/judger/test_case/ " \
"-v %s:/var/judger/code/ " \
"-v %s:/var/judger/code/log/ " \
"%s " \
"python judge/judger/run.py " \
"--solution_id %s --time_limit %s --memory_limit %s --test_case_id %s" % \
(docker_config["docker_path"],
test_case_dir,
source_code_dir,
log_dir,
docker_config["image_name"],
submission_id, str(time_limit), str(memory_limit), test_case_id)
subprocess.call(command, shell=docker_config["shell"])
except Exception as e:
print e
conn = MySQLdb.connect(db=submission_db["db"],
user=submission_db["user"],
passwd=submission_db["password"],

View File

@ -1,12 +0,0 @@
#include <stdio.h>
int main()
{
int a = 0;
int i = 0;
for(i = 0; i < 9999999999;i++)
{
a += i;
}
printf("%d", a);
return 0;
}

View File

@ -1,6 +0,0 @@
#include <stdio.h>
#include <unistd.h>
int main()
{
}

View File

@ -1,8 +0,0 @@
# include <stdio.h>
int main()
{
int a, b;
scanf("%d %d", &a, &b);
printf("%d", a + b);
return 0;
}

View File

@ -25,9 +25,17 @@ DATABASES = {
}
}
REDIS_CACHE = {
"host": "121.42.32.129",
"port": 6379,
"db": 1
}
DEBUG = True
# 同理 这是 web 服务器的上传路径
TEST_CASE_DIR = os.path.join(BASE_DIR, 'test_case/')
TEST_CASE_DIR = os.path.join(BASE_DIR, 'test_case/')
ALLOWED_HOSTS = []
IMAGE_UPLOAD_DIR = os.path.join(BASE_DIR, 'static/src/upload_image/')

View File

@ -29,9 +29,17 @@ DATABASES = {
}
}
REDIS_CACHE = {
"host": "127.0.0.1",
"port": 6379,
"db": 1
}
DEBUG = True
# 同理 这是 web 服务器的上传路径
TEST_CASE_DIR = '/root/test_case/'
ALLOWED_HOSTS = ['*']
IMAGE_UPLOAD_DIR = '/var/mnt/source/OnlineJudge/static/src/upload_image/'

View File

@ -19,6 +19,7 @@ from problem.views import TestCaseUploadAPIView, ProblemTagAdminAPIView, Problem
from submission.views import SubmissionAPIView, SubmissionAdminAPIView, SubmissionShareAPIView
from contest_submission.views import ContestSubmissionAPIView, ContestSubmissionAdminAPIView
from monitor.views import QueueLengthMonitorAPIView
from utils.views import SimditorImageUploadAPIView
from contest_submission.views import contest_problem_my_submissions_list_page
@ -53,6 +54,7 @@ urlpatterns = [
url(r'^api/submission/$', SubmissionAPIView.as_view(), name="submission_api"),
url(r'^api/group_join/$', JoinGroupAPIView.as_view(), name="group_join_api"),
url(r'^api/admin/upload_image/$', SimditorImageUploadAPIView.as_view(), name="simditor_upload_image"),
url(r'^api/admin/announcement/$', AnnouncementAdminAPIView.as_view(), name="announcement_admin_api"),
url(r'^api/admin/contest/$', ContestAdminAPIView.as_view(), name="contest_admin_api"),
url(r'^api/admin/user/$', UserAdminAPIView.as_view(), name="user_admin_api"),
@ -112,5 +114,6 @@ urlpatterns = [
url(r'^help/$', TemplateView.as_view(template_name="utils/help.html"), name="help_page"),
url(r'^api/submission/share/$', SubmissionShareAPIView.as_view(), name="submission_share_api"),
url(r'^captcha/$', "utils.captcha.views.show_captcha", name="show_captcha"),
]

View File

@ -147,9 +147,12 @@ class TestCaseUploadAPIView(APIView):
f = request.FILES["file"]
tmp_zip = "/tmp/" + rand_str() + ".zip"
with open(tmp_zip, "wb") as test_case_zip:
for chunk in f:
test_case_zip.write(chunk)
try:
with open(tmp_zip, "wb") as test_case_zip:
for chunk in f:
test_case_zip.write(chunk)
except IOError:
return error_response(u"上传错误,写入临时目录失败")
test_case_file = zipfile.ZipFile(tmp_zip, 'r')
name_list = test_case_file.namelist()
@ -198,16 +201,24 @@ class TestCaseUploadAPIView(APIView):
# 计算输出文件的md5
for i in range(len(l) / 2):
md5 = hashlib.md5()
striped_md5 = hashlib.md5()
f = open(test_case_dir + str(i + 1) + ".out", "r")
# 完整文件的md5
while True:
data = f.read(2 ** 8)
if not data:
break
md5.update(data)
# 删除标准输出最后的空格和换行
# 这时只能一次全部读入了,分块读的话,没办法确定文件结尾
f.seek(0)
striped_md5.update(f.read().rstrip())
file_info["test_cases"][str(i + 1)] = {"input_name": str(i + 1) + ".in",
"output_name": str(i + 1) + ".out",
"output_md5": md5.hexdigest(),
"striped_output_md5": striped_md5.hexdigest(),
"output_size": os.path.getsize(test_case_dir + str(i + 1) + ".out")}
# 写入配置文件
open(test_case_dir + "info", "w").write(json.dumps(file_info))
@ -228,6 +239,17 @@ def problem_list_page(request, page=1):
if keyword:
problems = problems.filter(Q(title__contains=keyword) | Q(description__contains=keyword))
difficulty_order = request.GET.get("order_by", None)
if difficulty_order:
if difficulty_order[0] == "-":
problems = problems.order_by("-difficulty")
difficulty_order = "difficulty"
else:
problems = problems.order_by("difficulty")
difficulty_order = "-difficulty"
else:
difficulty_order = "difficulty"
# 按照标签筛选
tag_text = request.GET.get("tag", None)
if tag_text:
@ -235,7 +257,7 @@ def problem_list_page(request, page=1):
tag = ProblemTag.objects.get(name=tag_text)
except ProblemTag.DoesNotExist:
return error_page(request, u"标签不存在")
problems = tag.problem_set.all()
problems = tag.problem_set.all().filter(visible=True)
paginator = Paginator(problems, 20)
try:
@ -255,8 +277,6 @@ def problem_list_page(request, page=1):
except Exception:
pass
# 右侧的公告列表
announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
# 右侧标签列表 按照关联的题目的数量排序 排除题目数量为0的
tags = ProblemTag.objects.annotate(problem_number=Count("problem")).filter(problem_number__gt=0).order_by("-problem_number")
@ -264,4 +284,4 @@ def problem_list_page(request, page=1):
{"problems": current_page, "page": int(page),
"previous_page": previous_page, "next_page": next_page,
"keyword": keyword, "tag": tag_text,
"announcements": announcements, "tags": tags})
"tags": tags, "difficulty_order": difficulty_order})

View File

@ -1,3 +1,13 @@
/**
* Created by virusdefender on 8/25/15.
*/
fis.match('*.{js,css,png,gif}', {
useHash: true // 开启 md5 戳
});
fis.config.set(
'roadmap.path',
[{reg:'*.html',isHtmlLike : true}
])
;

View File

@ -104,3 +104,7 @@ li.list-group-item {
#share-code textarea {
margin-top: 15px;
}
#about-acm-logo{
width: 40%;
}

BIN
static/src/img/acm_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

View File

@ -2,40 +2,46 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
"validator"],
function ($, avalon, editor, uploader, bsAlert, csrfTokenHeader) {
//avalon.vmodels.add_contest = null;
$("#add-contest-form").validator().on('submit', function (e) {
if (!e.isDefaultPrevented()){
if (!e.isDefaultPrevented()) {
e.preventDefault();
var ajaxData = {
title: vm.title,
description: vm.description,
mode: vm.mode,
contest_type: 0,
show_rank: vm.showRank,
real_time_rank: vm.realTimeRank,
show_user_submission: vm.showSubmission,
start_time: vm.startTime,
end_time: vm.endTime,
visible: false
};
if (vm.choseGroupList.length == 0) {
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);
}
}
ajaxData.groups = selectedGroups;
}
if (vm.choseGroupList[0].id == 0) { //everyone | public contest
else {
if (vm.password) {
ajaxData.password = vm.password;
ajaxData.contest_type = 2;
}
else{
else
ajaxData.contest_type = 1;
}
}
else { // Add groups info
ajaxData.groups = [];
for (var i = 0; vm.choseGroupList[i]; i++)
ajaxData.groups.push(parseInt(vm.choseGroupList[i].id))
if (!vm.isGlobal && !selectedGroups.length) {
bsAlert("你没有选择参赛用户!");
return false;
}
if (vm.editDescription == "") {
bsAlert("比赛描述不能为空!");
return false;
}
$.ajax({ // Add contest
beforeSend: csrfTokenHeader,
url: "/api/admin/contest/",
@ -45,20 +51,18 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
method: "post",
success: function (data) {
if (!data.code) {
bsAlert("添加成功!将转到比赛列表页以便为比赛添加问题(注意比赛当前状态为:隐藏)");
vm.title = "";
vm.description = "";
vm.startTime = "";
vm.endTime = "";
vm.password = "";
vm.mode = "";
vm.showRank = false;
vm.showSubmission = false;
vm.group = "-1";
vm.groupList = [];
vm.choseGroupList = [];
vm.passwordUsable = false;
location.hash = "#contest/contest_list";
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);
@ -70,80 +74,59 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
});
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: "",
showRank: false,
showSubmission: false,
group: "-1",
groupList: [],
choseGroupList: [],
passwordUsable: false,
addGroup: function() {
if (vm.group == -1) return;
if (vm.groupList[vm.group].id == 0){
vm.passwordUsable = true;
vm.choseGroupList = [];
for (var key in vm.groupList){
vm.groupList[key].chose = true;
}
}
vm.groupList[vm.group]. chose = true;
vm.choseGroupList.push({name:vm.groupList[vm.group].name, index:vm.group, id:vm.groupList[vm.group].id});
vm.group = -1;
},
removeGroup: function(groupIndex){
if (vm.groupList[vm.choseGroupList[groupIndex].index].id == 0){
vm.passwordUsable = false;
for (key in vm.groupList){
vm.groupList[key].chose = false;
}
}
vm.groupList[vm.choseGroupList[groupIndex].index].chose = false;
vm.choseGroupList.remove(vm.choseGroupList[groupIndex]);
}
});
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
});
$.ajax({ // Get current user type
$.ajax({
url: "/api/user/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
if (data.data.admin_type == 2) { // Is super user
vm.isGlobal = true;
vm.groupList.push({id:0,name:"所有人",chose:false});
var admin_type = data.data.admin_type;
if (data.data.admin_type == 1) {
vm.isGlobal = false;
vm.showGlobalViewRadio = false;
}
$.ajax({ // Get the group list of current user
beforeSend: csrfTokenHeader,
url: "/api/admin/group/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
if (!data.data.length) {
return;
}
for (var i = 0; i < data.data.length; i++) {
var item = data.data[i];
item["chose"] = false;
vm.groupList.push(item);
}
}
$.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;
}
else {
bsAlert(data.data);
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

@ -3,21 +3,39 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
avalon.ready(function () {
$("#edit-contest-form").validator().on('submit', function (e) {
if (!e.isDefaultPrevented()){
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,
show_rank: vm.editShowRank,
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
start_time: vm.editStartTime,
end_time: vm.editEndTime,
visible: vm.editVisible
};
if (vm.choseGroupList.length == 0) {
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.password) {
ajaxData.password = vm.editPassword;
ajaxData.contest_type = 2;
}
else
ajaxData.contest_type = 1;
}
if (!vm.isGlobal && !selectedGroups.length) {
bsAlert("你没有选择参赛用户!");
return false;
}
@ -25,22 +43,8 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
bsAlert("比赛描述不能为空!");
return false;
}
if (vm.choseGroupList[0].id == 0) { //everyone | public contest
if (vm.editPassword) {
ajaxData.password = vm.editPassword;
ajaxData.contest_type = 2;
}
else{
ajaxData.contest_type = 1;
}
}
else { // Add groups info
ajaxData.groups = [];
for (var i = 0; vm.choseGroupList[i]; i++)
ajaxData.groups.push(parseInt(vm.choseGroupList[i].id))
}
$.ajax({ // Add contest
$.ajax({ // modify contest info
beforeSend: csrfTokenHeader,
url: "/api/admin/contest/",
dataType: "json",
@ -52,7 +56,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
if (!data.code) {
bsAlert("修改成功!");
vm.editingContestId = 0; // Hide the editor
vm.getPage(1); // Refresh the contest list
vm.getPage(1); // Refresh the contest list
}
else {
bsAlert(data.data);
@ -63,138 +67,124 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
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.group= "-1";
vm.groupList= [];
vm.choseGroupList= [];
vm.passwordUsable= false;
vm.keyword= "";
vm.editingContestId= 0;
vm.editTitle= "";
vm.editDescription= "";
vm.editProblemList= [];
vm.editPassword= "";
vm.editStartTime= "";
vm.editEndTime= "";
vm.editMode= "";
vm.editShowRank= false;
vm.editShowSubmission= false;
vm.editProblemList= [];
vm.editVisible= false;
vm.editChoseGroupList= [];
vm.editingProblemContestIndex= 0;
}
else {
var vm = avalon.define({
$id: "contestList",
contestList: [],
previousPage: 0,
nextPage: 0,
page: 1,
totalPage: 1,
showVisibleOnly: false,
group: "-1",
groupList: [],
choseGroupList: [],
passwordUsable: false,
keyword: "",
editingContestId: 0,
editTitle: "",
editDescription: "",
editProblemList: [],
editPassword: "",
editStartTime: "",
editEndTime: "",
editMode: "",
editShowRank: false,
editShowSubmission: false,
editProblemList: [],
editVisible: false,
editChoseGroupList: [],
editingProblemContestIndex: 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";
}
},
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;
if (vm.contestList[contestId-1].contest_type == 0) { //contest type == 0, contest in group
//Clear the choseGroupList
while (vm.choseGroupList.length) {
vm.removeGroup(0);
}
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,
for (var i = 0; i < vm.contestList[contestId-1].groups.length; i++){
var id = parseInt(vm.contestList[contestId-1].groups[i]);
var index = 0;
for (; vm.groupList[index]; index++) {
if (vm.groupList[index].id == id)
break;
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;
}
}
}
vm.groupList[index].chose = true;
vm.choseGroupList.push({
name:vm.groupList[index].name,
index:index,
id:id
});
}
else {
vm.isGlobal = true;
}
vm.editShowSubmission = vm.contestList[contestId - 1].show_user_submission;
editor("#editor").setValue(vm.contestList[contestId - 1].description);
vm.editingProblemContestIndex = 0;
}
else{
vm.group = "0";
vm.addGroup()//vm.editChoseGroupList = [0]; id 0 is for the group of everyone~
},
showEditProblemArea: function (contestId) {
if (vm.editingProblemContestIndex == contestId) {
vm.editingProblemContestIndex = 0;
return;
}
vm.editShowRank = vm.contestList[contestId-1].show_rank;
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
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,
url: "/api/admin/contest_problem/?contest_id=" + vm.contestList[contestId - 1].id,
method: "get",
dataType: "json",
success: function (data) {
@ -206,51 +196,27 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
}
}
});
vm.editingContestId = 0;
vm.editingProblemContestIndex = contestId;
vm.editMode = vm.contestList[contestId-1].mode;
},
addGroup: function() {
if (vm.group == -1) return;
if (vm.groupList[vm.group].id == 0){
vm.passwordUsable = true;
vm.choseGroupList = [];
for (var i = 0; i < vm.groupList.length; i++) {
vm.groupList[i].chose = true;
}
}
vm.groupList[vm.group]. chose = true;
// index of the group is relative. It is related to user
vm.choseGroupList.push({name:vm.groupList[vm.group].name, index:vm.group, id:vm.groupList[vm.group].id});
vm.group = -1;
},
removeGroup: function(groupIndex){
if (vm.groupList[vm.choseGroupList[groupIndex].index].id == 0){
vm.passwordUsable = false;
for (var i = 0; i < vm.groupList.length; i++) {
vm.groupList[i].chose = false;
}
}
vm.groupList[vm.choseGroupList[groupIndex].index].chose = false;
vm.choseGroupList.remove(vm.choseGroupList[groupIndex]);
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);
}
});
vm.$watch("showVisibleOnly", function() {
getPageData(1);
})
}
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);
}
});
vm.$watch("showVisibleOnly", function () {
getPageData(1);
})
}
getPageData(1);
//init time picker
@ -293,39 +259,40 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
}
// Get group list
$.ajax({ // Get current user type
$.ajax({
url: "/api/user/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
if (data.data.admin_type == 2) { // Is super user
vm.isGlobal = true;
vm.groupList.push({id:0,name:"所有人",chose:false});
var admin_type = data.data.admin_type;
if (data.data.admin_type == 1) {
vm.isGlobal = false;
vm.showGlobalViewRadio = false;
}
$.ajax({ // Get the group list of current user
beforeSend: csrfTokenHeader,
url: "/api/admin/group/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
if (!data.data.length) {
//this user have no group can use
return;
}
for (var i = 0; i < data.data.length; i++) {
var item = data.data[i];
item["chose"] = false;
vm.groupList.push(item);
}
}
$.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;
}
else {
bsAlert(data.data);
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

@ -13,6 +13,10 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
bsAlert("题目描述不能为空!");
return false;
}
if (vm.timeLimit < 100 || vm.timeLimit > 5000) {
bsAlert("保证时间限制是一个100-5000的合法整数");
return false;
}
if (vm.samples.length == 0) {
bsAlert("请至少添加一组样例!");
return false;

View File

@ -14,8 +14,8 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
bsAlert("题目描述不能为空!");
return false;
}
if (vm.timeLimit < 1000 || vm.timeLimit > 5000) {
bsAlert("保证时间限制是一个1000-5000的合法整数");
if (vm.timeLimit < 100 || vm.timeLimit > 5000) {
bsAlert("保证时间限制是一个100-5000的合法整数");
return false;
}
if (vm.samples.length == 0) {

View File

@ -1,15 +1,32 @@
require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"],
function ($, codeMirror, csrfTokenHeader, bsAlert, ZeroClipboard) {
// 复制样例需要 Flash 的支持 检测浏览器是否安装了 Flash
function detect_flash() {
var ie_flash;
try {
ie_flash = (window.ActiveXObject && (new ActiveXObject("ShockwaveFlash.ShockwaveFlash")) !== false)
} catch (err) {
ie_flash = false;
}
var _flash_installed = ((typeof navigator.plugins != "undefined" && typeof navigator.plugins["Shockwave Flash"] == "object") || ie_flash);
return _flash_installed;
}
if(detect_flash()) {
// 提供点击复制到剪切板的功能
ZeroClipboard.config({swfPath: "/static/img/ZeroClipboard.swf"});
new ZeroClipboard($(".copy-sample"));
}
else{
$(".copy-sample").hide();
}
var codeEditorSelector = $("#code-editor")[0];
// 部分界面逻辑会隐藏代码输入框,先判断有没有。
if (codeEditorSelector == undefined) {
return;
}
// 提供点击复制到剪切板的功能
ZeroClipboard.config({swfPath: "/static/img/ZeroClipboard.swf"});
new ZeroClipboard($(".copy-sample"));
var codeEditor = codeMirror(codeEditorSelector, "text/x-csrc");
var language = $("input[name='language'][checked]").val();
var submissionId;

View File

@ -1,8 +1,9 @@
({
// RequireJS 通过一个相对的路径 baseUrl来加载所有代码。baseUrl通常被设置成data-main属性指定脚本的同级目录。
baseUrl: "js/",
baseUrl: "/static/js/",
// 第三方脚本模块的别名,jquery比libs/jquery-1.11.1.min.js简洁明了
paths: {
jquery: "lib/jquery/jquery",
avalon: "lib/avalon/avalon",
editor: "utils/editor",
@ -37,12 +38,12 @@
webUploader: "lib/webuploader/webuploader",
"_datetimePicker": "lib/datetime_picker/bootstrap-datetimepicker"
},
shim: {
"bootstrap": {"deps": ['jquery']},
"_datetimepicker": {"deps": ["jquery"]},
"datetimepicker": {"deps": ["_datetimepicker"]}
bootstrap: {deps: ["jquery"]},
_datetimePicker: {dep: ["jquery"]},
datetimePicker: {deps: ["_datetimePicker"]},
validator: ["jquery"]
},
findNestedDependencies: true,
appDir: "../",

View File

@ -143,6 +143,16 @@ Uploader = (function(superClass) {
processData: false,
contentType: false,
type: 'POST',
beforeSend: function(){
var name = "csrftoken=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1);
if (c.indexOf(name) != -1) name = c.substring(name.length, c.length);
}
arguments[0].setRequestHeader("X-CSRFToken", name);
},
headers: {
'X-File-Name': encodeURIComponent(file.name)
},

View File

@ -8,7 +8,7 @@ define("editor", ["simditor"], function(Simditor){
toolbarFloat: false,
defaultImage: null,
upload: {
url: "",
url: "/api/admin/upload_image/",
params: null,
fileKey: "image",
connectionCount: 3,

View File

@ -165,6 +165,16 @@ def my_submission_list_page(request, page=1):
if result:
submissions = submissions.filter(result=int(result))
filter = {"name": "result", "content": result}
# 为 submission 查询题目 因为提交页面经常会有重复的题目,缓存一下查询结果
cache_result = {}
for item in submissions:
problem_id = item["problem_id"]
if problem_id not in cache_result:
problem = Problem.objects.get(id=problem_id)
cache_result[problem_id] = problem.title
item["title"] = cache_result[problem_id]
paginator = Paginator(submissions, 20)
try:
current_page = paginator.page(int(page))

View File

@ -7,7 +7,7 @@
<div class="col-md-12">
<div class="form-group">
<input type="text" name="name" class="form-control" ms-duplex="title"
data-error="请填写比赛名称(名称不能超过50个字)" ms-attr-readonly="contestCreated" required>
data-error="请填写比赛名称(名称不能超过50个字)" required>
<div class="help-block with-errors"></div>
</div>
@ -26,7 +26,6 @@
<div class="form-group">
<input type="text" class="form-control" name="start_time" id="contest_start_time"
ms-duplex="startTime" data-error="请填写比赛开始时间" required>
<div class="help-block with-errors"></div>
</div>
</div>
@ -35,61 +34,66 @@
<div class="form-group">
<input type="text" class="form-control" name="end_time" id="contest_end_time"
ms-duplex="endTime" data-error="请填写比赛结束时间" required>
<div class="help-block with-errors"></div>
</div>
</div>
<div class="col-md-6">
<label>允许参加的用户</label>
<div class="form-group">
<select class="form-control" ms-duplex="group" ms-change="addGroup" value="-1">
<option value="-1">请选择</option>
<option ms-repeat="groupList" ms-attr-value="$index" ms-visible="!el.chose">{{el.name}}</option>
</select>
<label>可见范围</label>
<div>
<span ms-if="showGlobalViewRadio">
<label>
<small><input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">全局可见
</small>
</label>
</span>
<span>
<label>
<small><input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组内可见
</small>
</label>
</span>
</div>
</div>
</div>
<div class="col-md-6" ms-visible="passwordUsable">
<div class="col-md-6" ms-visible="isGlobal">
<label>密码保护</label>
<div class="form-group">
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="password">
</div>
</div>
<div class="col-md-12">
<div ms-repeat="choseGroupList" class="group-tag" ms-click="removeGroup($index)">{{el.name}}</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>
</div>
<div class="col-md-6">
<div class="col-md-3">
<label>排名方式</label>
</div>
<div class="col-md-3">
<label>结束前是否开放排名</label>
</div>
<div class="col-md-3">
<label>是否公开提交记录</label>
</div>
<div class="col-md-6">
<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>AC数量</small>
</label>
<label><input type="radio" name="mode" ms-duplex-string="mode" value="2">
<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="showRank">
<small>放排名</small>
<label class="text"><input type="checkbox" ms-duplex-checked="showSubmission">
<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 class="text"><input type="checkbox" ms-duplex-checked="realTimeRank">
<small></small>
</label>
</div>
</div>

View File

@ -24,7 +24,7 @@
<tr ms-repeat="contestList">
<td>{{ el.id }}</td>
<td>{{ el.title }}</td>
<td ms-text="el.show_rank?'公开':'不公开'"></td>
<td ms-text="el.real_time_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>
@ -63,7 +63,7 @@
<textarea id="editor" placeholder="这里输入内容" autofocus ms-duplex="editDescription"></textarea>
<div class="help-block with-errors"></div>
<p class="error-info" ms-visible="editDescription==''" >请填写比赛描述</p>
<p class="error-info" ms-visible="editDescription==''">请填写比赛描述</p>
</div>
</div>
<div class="col-md-6">
@ -87,32 +87,33 @@
</div>
</div>
<div class="col-md-6">
<label>允许参加的用户</label>
<div class="form-group">
<select class="form-control" ms-duplex="group" ms-change="addGroup" value="-1">
<option value="-1">请选择</option>
<option ms-repeat="groupList" ms-attr-value="$index" ms-visible="!el.chose">{{el.name}}</option>
</select>
<label>可见范围</label>
<span ms-if="showGlobalViewRadio">
<label>
<small><input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">全局可见
</small>
</label>
</span>
<span>
<label>
<small><input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组内可见
</small>
</label>
</span>
</div>
</div>
<div class="col-md-6" ms-visible="passwordUsable">
<div class="col-md-6" ms-visible="isGlobal">
<label>密码保护</label>
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="editPassword">
</div>
<div class="col-md-12">
<div ms-repeat="choseGroupList" class="group-tag" ms-click="removeGroup($index)">{{el.name}}</div>
</div>
<div class="col-md-6">
<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>
</div>
<div class="col-md-3">
</div>
<div class="col-md-3">
</div>
<div class="col-md-4">
<label>排名方式</label>
<div class="form-group">
@ -120,34 +121,31 @@
<small>ACM</small>
</label>
<label><input type="radio" name="mode" ms-duplex-string="editMode" value="1">
<small>AC数量</small>
</label>
<label><input type="radio" name="mode" ms-duplex-string="editMode" value="2">
<small>分数</small>
</label>
</div>
</div>
<div class="col-md-2">
<label>是否可见</label><br>
<label><input type="checkbox" ms-duplex-checked="editVisible">
<small> 可见</small>
</label>
</div>
<div class="col-md-3">
<label>结束前是否开放排名</label>
<div class="form-group">
<label class="text"><input type="checkbox" ms-duplex-checked="editShowRank">
<small>开放排名</small>
<label>是否可见</label>
<label><input type="checkbox" ms-duplex-checked="editVisible">
<small> 可见</small>
</label>
</div>
</div>
<div class="col-md-3">
<label>是否公开提交记录</label>
<label>公开提交记录</label>
<div class="form-group">
<label class="text"><input type="checkbox" ms-duplex-checked="editShowSubmission">
<small>允许查看提交记录</small>
<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="editRealTimeRank">
<small></small>
</label>
</div>
</div>
@ -172,7 +170,7 @@
<tr ms-repeat="editProblemList">
<td>{{ el.sort_index }}</td>
<td>{{ el.title }}</td>
<td ms-visible="editMode=='2'">{{ el.score}}</td>
<td ms-visible="editMode=='2'">{{ el.score }}</td>
<td ms-text="el.visible?'可见':'不可见'"></td>
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss") }}</td>
<td>

View File

@ -18,7 +18,7 @@
<div class="col-md-3">
<div class="form-group"><label>时间限制(ms)</label>
<input type="number" name="timeLimit" class="form-control" ms-duplex="timeLimit"
data-error="请输入时间限制(保证是一个1000-5000的合法整数)" required>
data-error="请输入时间限制(保证是一个100-5000的合法整数)" required>
<div class="help-block with-errors"></div>
</div>
</div>
@ -31,8 +31,12 @@
</div>
<div class="col-md-3">
<div class="form-group"><label>难度</label>
<input type="number" name="difficulty" class="form-control" ms-duplex="difficulty"
data-error="请输入难度(保证是一个合法整数)" required>
<select name="difficulty" class="form-control" ms-duplex="difficulty"
data-error="请选择难度" required>
<option value="1" selected="selected">简单</option>
<option value="2">中等</option>
<option value="3"></option>
</select>
<div class="help-block with-errors"></div>
</div>
</div>

View File

@ -24,7 +24,7 @@
<div class="col-md-3">
<div class="form-group"><label>时间限制(ms)</label>
<input type="number" name="timeLimit" class="form-control" ms-duplex="timeLimit"
data-error="请输入时间限制(保证是一个1000-5000的合法整数)" required>
data-error="请输入时间限制(保证是一个100-5000的合法整数)" required>
<div class="help-block with-errors"></div>
</div>
</div>
@ -37,8 +37,12 @@
</div>
<div class="col-md-3">
<div class="form-group"><label>难度</label>
<input type="number" name="difficulty" class="form-control" ms-duplex="difficulty"
data-error="请输入难度(保证是一个合法整数)" required>
<select name="difficulty" class="form-control" ms-duplex="difficulty"
data-error="请选择难度" required>
<option value="1">简单</option>
<option value="2">中等</option>
<option value="3"></option>
</select>
<div class="help-block with-errors"></div>
</div>
</div>

View File

@ -1,3 +1,4 @@
{% load announcement_list %}
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">
@ -5,8 +6,9 @@
公告
</h3></div>
<div class="panel-body">
{% public_announcement_list as announcements %}
{% if announcements %}
{% for item in announcements %}
{% for item in announcements%}
<p>{{ forloop.counter }}.&nbsp;&nbsp;<a href="/announcement/{{ item.id }}/" target="_blank">{{ item.title }}</a>
</p>
{% endfor %}

View File

@ -48,12 +48,13 @@
{% endfor %}
</tbody>
</table>
{% if request.user.is_authenticated %}
<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/'}">
</label>
</div>
{% endif %}
<nav>
<ul class="pager">
{% if previous_page %}

View File

@ -20,7 +20,7 @@
<div class="problem-section">
<label class="problem-label">描述</label>
<p class="problem-detail">{{ contest_problem.description|safe }}</p>
<div class="problem-detail">{{ contest_problem.description|safe }}</div>
</div>
<div class="problem-section">
<label class="problem-label">输入</label>
@ -37,6 +37,7 @@
{% for item in samples %}
<div class="problem-section">
<label class="problem-label">样例输入{{ forloop.counter }}</label>
<a href="javascript:void(0)" class="copy-sample" data-clipboard-text="{{ item.input }}">复制</a>
<pre>
{{ item.input }}</pre>
@ -59,7 +60,7 @@
<div class="problem-section hide">
<label class="problem-label">提示</label>
<p class="problem-detail">{{ contest_problem.hint|safe }}</p>
<div class="problem-detail">{{ contest_problem.hint|safe }}</div>
</div>
{% endif %}

View File

@ -21,7 +21,6 @@
</li>
</ul>
</div>
{% if submissions %}
<table class="table table-bordered">
<thead>
<tr class="" success>
@ -64,6 +63,7 @@
</th>
</tr>
</thead>
{% if submissions %}
<tbody>
{% for item in submissions %}
<tr>
@ -94,10 +94,11 @@
</tr>
{% endfor %}
</tbody>
{% else %}
<p>本场比赛还没有提交记录</p>
{% endif %}
</table>
{% else %}
<p>你还没有提交记录!</p>
{% endif %}
<nav>
<ul class="pager">
{% if previous_page %}

View File

@ -0,0 +1,114 @@
{% extends 'oj_base.html' %}
{% block body %}
{% load submission %}
{% load user %}
<div class="container main">
<div class="contest-tab">
<ul class="nav nav-tabs nav-tabs-google">
<li role="presentation">
<a href="/contest/{{ contest.id }}/">比赛详情</a>
</li>
<li role="presentation">
<a href="/contest/{{ contest.id }}/problems/">题目</a>
</li>
<li role="presentation" class="active">
<a href="/contest/{{ contest.id }}/submissions/">提交</a>
</li>
<li role="presentation">
<a href="/contest/{{ contest.id }}/rank/">排名</a>
</li>
</ul>
</div>
<table class="table table-bordered">
<thead>
<tr class="" success>
<th>#</th>
<th>题目名称</th>
<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="/contest/{{ contest.id }}/submissions/?language=1">C</a></li>
<li><a href="/contest/{{ contest.id }}/submissions/?language=2">C++</a></li>
<li><a href="/contest/{{ contest.id }}/submissions/?language=3">Java</a></li>
<li><a href="/contest/{{ contest.id }}/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="/contest/{{ contest.id }}/submissions/?result=0">Accepted</a></li>
<li><a href="/contest/{{ contest.id }}/submissions/?result=6">Wrong Answer</a></li>
<li><a href="/contest/{{ contest.id }}/submissions/?result=1">Runtime Error</a></li>
<li><a href="/contest/{{ contest.id }}/submissions/?result=2">Time Limit Exceeded</a></li>
<li><a href="/contest/{{ contest.id }}/submissions/?result=3">Memory Limit Exceeded</a></li>
<li><a href="/contest/{{ contest.id }}/submissions/?result=4">Compile Error</a></li>
<li><a href="/contest/{{ contest.id }}/submissions/?result=5">Format Error</a></li>
<li><a href="/contest/{{ contest.id }}/submissions/">取消筛选</a></li>
</ul>
</div>
</th>
</tr>
</thead>
{% if submissions %}
<tbody>
{% for item in submissions %}
<tr>
<th scope="row"><a href="/submission/{{ item.id }}/">
{{ forloop.counter |add:start_id }}</a></th>
<th scope="row">
<a href="/contest/{{ item.contest_id }}/problem/{{ item.problem_id }}/">{{ item.title }}</a>
</th>
<td>{{ item.user_id|get_username }}</td>
<td>{{ item.create_time }}</td>
<td>
{{ item.language|translate_language }}
</td>
<td>
{% if item.accepted_answer_time %}
{{ item.accepted_answer_time }}ms
{% else %}
--
{% endif %}
</td>
<td class="alert-{{ item.result|translate_result_class }}">
<strong>{{ item.result|translate_result }}</strong>
</td>
</tr>
{% endfor %}
</tbody>
{% else %}
<p>本场比赛还没有提交记录</p>
{% endif %}
</table>
<nav>
<ul class="pager">
{% if previous_page %}
<li class="previous"><a
href="/contest/{{ contest.id }}/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="/contest/{{ contest.id }}/submissions/{{ next_page }}/{% if filter %}?{{ filter.name }}={{ filter.content }}{% endif %}">
下一页 <span aria-hidden="true">&rarr;</span></a></li>
{% endif %}
</ul>
</nav>
</div>
{% endblock %}

View File

@ -16,7 +16,7 @@
<div class="problem-section">
<label class="problem-label">描述</label>
<p class="problem-detail">{{ problem.description|safe }}</p>
<div class="problem-detail">{{ problem.description|safe }}</div>
</div>
<div class="problem-section">
<label class="problem-label">输入</label>
@ -48,7 +48,7 @@
{% if problem.hint %}
<div class="problem-section hide">
<label class="problem-label">提示</label>
<p class="problem-detail">{{ problem.hint|safe }}</p>
<div class="problem-detail">{{ problem.hint|safe }}</div>
</div>
{% endif %}
<div class="problem-section hide">

View File

@ -21,8 +21,8 @@
<th></th>
<th>#</th>
<th>题目</th>
<th><a href="/problems/?order_by=difficulty">难度</a></th>
<th><a href="/problems/?order_by=acceptance">通过率</a></th>
<th><a href="/problems/?order_by={{ difficulty_order }}">难度</a></th>
<th>通过率</th>
</tr>
</thead>
<tbody>
@ -31,7 +31,16 @@
<th><span class="glyphicon glyphicon-ok ac-flag"></span></th>
<th scope="row"><a href="/problem/{{ item.id }}/">{{ item.id }}</a></th>
<td><a href="/problem/{{ item.id }}/">{{ item.title }}</a></td>
<td>{{ item.difficulty }}</td>
<td>
{% ifequal item.difficulty 1 %}
简单
{% else %}
{% ifequal item.difficulty 2 %}
中等
{% else %}
{% endifequal %}
{% endifequal %}</td>
<td>{{ item|accepted_radio }}</td>
</tr>
{% endfor %}
@ -65,10 +74,11 @@
</div>
<ul class="list-group">
{% for item in tags %}
<li class="list-group-item problem-tag" onclick="location.href='/problems/?tag={{ item.name }}'">
<span class="badge">{{ item.problem_number }}</span>
{{ item.name }}
</li>
<li class="list-group-item problem-tag"
onclick="location.href='/problems/?tag={{ item.name }}'">
<span class="badge">{{ item.problem_number }}</span>
{{ item.name }}
</li>
{% endfor %}
</ul>
</div>

View File

@ -3,7 +3,7 @@
{% block body %}
{% load submission %}
<div class="container main">
<div class="col-md-9 col-lg-9">
<div class="col-md-12 col-lg-12">
<table class="table table-striped">
<thead>
<tr>
@ -52,7 +52,7 @@
<a href="/submission/{{ item.id }}/">{{ forloop.counter |add:start_id }}</a>
</th>
<td>
<a href="/problem/{{ item.problem_id }}/">{{ item.problem_id }}</a>
<a href="/problem/{{ item.problem_id }}/">{{ item.title }}</a>
</td>
<td>{{ item.create_time }}</td>
<td>
@ -92,8 +92,6 @@
<p>你还没有提交记录!</p>
{% endif %}
</div>
<div class="col-md-3 col-lg-3">
{% include "oj/announcement/_announcement_panel.html" %}
</div>
</div>
{% endblock %}

View File

@ -11,6 +11,7 @@
</ul>
<div>
<h2 class="text-center">ACM 简介</h2>
<img src="/static/img/acm_logo.png" id="about-acm-logo" class="center-block">
<p>
ACM国际大学生程序设计竞赛英语ACM International Collegiate Programming Contest,

View File

@ -0,0 +1,10 @@
# coding=utf-8
from django import template
from announcement.models import Announcement
def public_announcement_list():
return Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
register = template.Library()
register.assignment_tag(public_announcement_list, name="public_announcement_list")

33
utils/views.py Normal file
View File

@ -0,0 +1,33 @@
# coding=utf-8
from rest_framework.views import APIView
from rest_framework.response import Response
from django.conf import settings
from utils.shortcuts import rand_str
class SimditorImageUploadAPIView(APIView):
def post(self, request):
if "image" not in request.FILES:
return Response(data={
"success": False,
"msg": "上传失败",
"file_path": "/"})
img = request.FILES["image"]
image_name = rand_str() + '.' + str(request.FILES["image"].name.split('.')[-1])
image_dir = settings.IMAGE_UPLOAD_DIR + image_name
try:
with open(image_dir, "wb") as imageFile:
for chunk in img:
imageFile.write(chunk)
except IOError:
return Response(data={
"success": True,
"msg": "上传错误",
"file_path": "/static/upload_image/" + image_name})
return Response(data={
"success": True,
"msg": "",
"file_path": "/static/upload_image/" + image_name})