diff --git a/.gitignore b/.gitignore index 285cfd8..1fbe8ef 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,7 @@ log/ static/release/css static/release/js static/release/img +static/src/upload_image/* build.txt tmp/ test_case/ \ No newline at end of file diff --git a/Accessories/__init__.py b/Accessories/__init__.py new file mode 100644 index 0000000..3ed9fd0 --- /dev/null +++ b/Accessories/__init__.py @@ -0,0 +1 @@ +__author__ = 'root' diff --git a/Accessories/reJudge.py b/Accessories/reJudge.py new file mode 100644 index 0000000..dc1ad2e --- /dev/null +++ b/Accessories/reJudge.py @@ -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 + diff --git a/Accessories/utils.py b/Accessories/utils.py new file mode 100755 index 0000000..17ab096 --- /dev/null +++ b/Accessories/utils.py @@ -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 \ No newline at end of file diff --git a/contest/migrations/0008_auto_20150912_1912.py b/contest/migrations/0008_auto_20150912_1912.py new file mode 100644 index 0000000..ee2a8f5 --- /dev/null +++ b/contest/migrations/0008_auto_20150912_1912.py @@ -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', + ), + ] diff --git a/contest/models.py b/contest/models.py index a76913b..258ee19 100644 --- a/contest/models.py +++ b/contest/models.py @@ -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() # 只能超级管理员创建公开赛,管理员只能创建小组内部的比赛 diff --git a/contest/views.py b/contest/views.py index 3bb1d8f..ad5ccf7 100644 --- a/contest/views.py +++ b/contest/views.py @@ -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): diff --git a/contest_submission/views.py b/contest_submission/views.py index f136245..765d116 100644 --- a/contest_submission/views.py +++ b/contest_submission/views.py @@ -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, @@ -155,4 +163,4 @@ class ContestSubmissionAdminAPIView(APIView): return error_response(u"参数错误!") if problem_id: submissions = submissions.filter(problem_id=problem_id) - return paginate(request, submissions, SubmissionSerializer) \ No newline at end of file + return paginate(request, submissions, SubmissionSerializer) diff --git a/group/views.py b/group/views.py index 2929706..a4a3123 100644 --- a/group/views.py +++ b/group/views.py @@ -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 }) diff --git a/judge/judger/client.py b/judge/judger/client.py index fc7d0b9..7740587 100644 --- a/judge/judger/client.py +++ b/judge/judger/client.py @@ -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 diff --git a/judge/judger/compiler.py b/judge/judger/compiler.py index 7f40ff1..127750b 100644 --- a/judge/judger/compiler.py +++ b/judge/judger/compiler.py @@ -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 diff --git a/judge/judger/logger.py b/judge/judger/logger.py new file mode 100644 index 0000000..c71a542 --- /dev/null +++ b/judge/judger/logger.py @@ -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 diff --git a/judge/judger/loose_client.py b/judge/judger/loose_client.py deleted file mode 100644 index e3d2540..0000000 --- a/judge/judger/loose_client.py +++ /dev/null @@ -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 diff --git a/judge/judger/run.py b/judge/judger/run.py index 08e01c8..8aeb547 100644 --- a/judge/judger/run.py +++ b/judge/judger/run.py @@ -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", diff --git a/judge/judger_controller/settings.py b/judge/judger_controller/settings.py index aff08b1..92d1780 100644 --- a/judge/judger_controller/settings.py +++ b/judge/judger_controller/settings.py @@ -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 服务器访问的地址 diff --git a/judge/judger_controller/tasks.py b/judge/judger_controller/tasks.py index cc3d7d5..df1deab 100644 --- a/judge/judger_controller/tasks.py +++ b/judge/judger_controller/tasks.py @@ -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"], diff --git a/judge/tests/c/cpu_time_timeout.c b/judge/tests/c/cpu_time_timeout.c deleted file mode 100644 index 661050e..0000000 --- a/judge/tests/c/cpu_time_timeout.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -int main() -{ - int a = 0; - int i = 0; - for(i = 0; i < 9999999999;i++) - { - a += i; - } - printf("%d", a); - return 0; -} \ No newline at end of file diff --git a/judge/tests/c/real_time_timeout.c b/judge/tests/c/real_time_timeout.c deleted file mode 100644 index 0051986..0000000 --- a/judge/tests/c/real_time_timeout.c +++ /dev/null @@ -1,6 +0,0 @@ -#include -#include -int main() -{ - -} \ No newline at end of file diff --git a/judge/tests/c/success.c b/judge/tests/c/success.c deleted file mode 100644 index aa0b2bb..0000000 --- a/judge/tests/c/success.c +++ /dev/null @@ -1,8 +0,0 @@ -# include -int main() -{ - int a, b; - scanf("%d %d", &a, &b); - printf("%d", a + b); - return 0; -} \ No newline at end of file diff --git a/oj/local_settings.py b/oj/local_settings.py index b923291..51e9095 100644 --- a/oj/local_settings.py +++ b/oj/local_settings.py @@ -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/') \ No newline at end of file diff --git a/oj/server_settings.py b/oj/server_settings.py index 185bf6b..cc5ee82 100644 --- a/oj/server_settings.py +++ b/oj/server_settings.py @@ -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/' diff --git a/oj/urls.py b/oj/urls.py index 8d6e819..e9cc880 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -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"), ] diff --git a/problem/views.py b/problem/views.py index b73085f..c807732 100644 --- a/problem/views.py +++ b/problem/views.py @@ -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}) diff --git a/static/release/fis-conf.js b/static/release/fis-conf.js index ce128cd..f0935c3 100644 --- a/static/release/fis-conf.js +++ b/static/release/fis-conf.js @@ -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} +]) +; \ No newline at end of file diff --git a/static/src/css/oj.css b/static/src/css/oj.css index f75723b..08b6d02 100644 --- a/static/src/css/oj.css +++ b/static/src/css/oj.css @@ -103,4 +103,8 @@ li.list-group-item { #share-code textarea { margin-top: 15px; +} + +#about-acm-logo{ + width: 40%; } \ No newline at end of file diff --git a/static/src/img/acm_logo.png b/static/src/img/acm_logo.png new file mode 100644 index 0000000..ce1cfed Binary files /dev/null and b/static/src/img/acm_logo.png differ diff --git a/static/src/js/app/admin/contest/addContest.js b/static/src/js/app/admin/contest/addContest.js index a87e4e2..8aa5354 100644 --- a/static/src/js/app/admin/contest/addContest.js +++ b/static/src/js/app/admin/contest/addContest.js @@ -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); + } + } + }); } }); diff --git a/static/src/js/app/admin/contest/contestList.js b/static/src/js/app/admin/contest/contestList.js index c920cd6..315023c 100644 --- a/static/src/js/app/admin/contest/contestList.js +++ b/static/src/js/app/admin/contest/contestList.js @@ -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); + } + } + }); } }); diff --git a/static/src/js/app/admin/problem/addProblem.js b/static/src/js/app/admin/problem/addProblem.js index c6e49ca..103396b 100644 --- a/static/src/js/app/admin/problem/addProblem.js +++ b/static/src/js/app/admin/problem/addProblem.js @@ -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; diff --git a/static/src/js/app/admin/problem/editProblem.js b/static/src/js/app/admin/problem/editProblem.js index a746d7a..416d86f 100644 --- a/static/src/js/app/admin/problem/editProblem.js +++ b/static/src/js/app/admin/problem/editProblem.js @@ -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) { diff --git a/static/src/js/app/oj/problem/problem.js b/static/src/js/app/oj/problem/problem.js index 360569c..d855d11 100644 --- a/static/src/js/app/oj/problem/problem.js +++ b/static/src/js/app/oj/problem/problem.js @@ -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; @@ -186,4 +203,4 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"], } } }) - }); + }); \ No newline at end of file diff --git a/static/src/js/build.js b/static/src/js/build.js index 9927a1b..ddd9bb3 100644 --- a/static/src/js/build.js +++ b/static/src/js/build.js @@ -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: "../", diff --git a/static/src/js/lib/simditor/uploader.js b/static/src/js/lib/simditor/uploader.js index d96860e..fc039d7 100644 --- a/static/src/js/lib/simditor/uploader.js +++ b/static/src/js/lib/simditor/uploader.js @@ -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) }, diff --git a/static/src/js/utils/editor.js b/static/src/js/utils/editor.js index bb09bb4..53a7d55 100644 --- a/static/src/js/utils/editor.js +++ b/static/src/js/utils/editor.js @@ -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, diff --git a/submission/views.py b/submission/views.py index 984cb72..19f3c29 100644 --- a/submission/views.py +++ b/submission/views.py @@ -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)) diff --git a/template/src/admin/contest/add_contest.html b/template/src/admin/contest/add_contest.html index b374ec8..54d16d8 100644 --- a/template/src/admin/contest/add_contest.html +++ b/template/src/admin/contest/add_contest.html @@ -7,7 +7,7 @@
+ data-error="请填写比赛名称(名称不能超过50个字)" required>
@@ -26,7 +26,6 @@
-
@@ -35,61 +34,66 @@
-
-
- + + +
+ + + + + + +
+
-
+
-
-
{{el.name}}
+
+ +
+   {{ el.name }} +
-
+
-
-
- -
-
- -
-
-
+
-
+
-
diff --git a/template/src/admin/contest/contest_list.html b/template/src/admin/contest/contest_list.html index 945d1f5..bde5ae4 100644 --- a/template/src/admin/contest/contest_list.html +++ b/template/src/admin/contest/contest_list.html @@ -24,7 +24,7 @@ {{ el.id }} {{ el.title }} - + {{ el.create_time|date("yyyy-MM-dd HH:mm:ss")}} {{ el.created_by.username }} @@ -63,7 +63,7 @@
-

请填写比赛描述

+

请填写比赛描述

@@ -87,32 +87,33 @@
- -
- + + + + + + +
-
+
-
-
{{el.name}}
-
-
- +
+ +
+   {{ el.name }} +
- -
-
- -
-
@@ -120,34 +121,31 @@ ACM -
-
- -
-
- -
-
- - +
+
+
+
+ +
+
@@ -172,7 +170,7 @@ {{ el.sort_index }} {{ el.title }} - {{ el.score}} + {{ el.score }} {{ el.create_time|date("yyyy-MM-dd HH:mm:ss") }} diff --git a/template/src/admin/problem/add_problem.html b/template/src/admin/problem/add_problem.html index 70bd524..37c48bb 100644 --- a/template/src/admin/problem/add_problem.html +++ b/template/src/admin/problem/add_problem.html @@ -18,7 +18,7 @@
+ data-error="请输入时间限制(保证是一个100-5000的合法整数)" required>
@@ -31,8 +31,12 @@
- +
diff --git a/template/src/admin/problem/edit_problem.html b/template/src/admin/problem/edit_problem.html index d2587b3..d7c9743 100644 --- a/template/src/admin/problem/edit_problem.html +++ b/template/src/admin/problem/edit_problem.html @@ -24,7 +24,7 @@
+ data-error="请输入时间限制(保证是一个100-5000的合法整数)" required>
@@ -37,8 +37,12 @@
- +
diff --git a/template/src/oj/announcement/_announcement_panel.html b/template/src/oj/announcement/_announcement_panel.html index d593626..fbe2fca 100644 --- a/template/src/oj/announcement/_announcement_panel.html +++ b/template/src/oj/announcement/_announcement_panel.html @@ -1,3 +1,4 @@ +{% load announcement_list %}

@@ -5,8 +6,9 @@ 公告

+ {% public_announcement_list as announcements %} {% if announcements %} - {% for item in announcements %} + {% for item in announcements%}

{{ forloop.counter }}.  {{ item.title }}

{% endfor %} diff --git a/template/src/oj/contest/contest_list.html b/template/src/oj/contest/contest_list.html index 25b8eae..51e522d 100644 --- a/template/src/oj/contest/contest_list.html +++ b/template/src/oj/contest/contest_list.html @@ -48,12 +48,13 @@ {% endfor %} - + {% if request.user.is_authenticated %}
+ {% endif %}
- {% if submissions %} @@ -64,6 +63,7 @@ + {% if submissions %} {% for item in submissions %} @@ -94,10 +94,11 @@ {% endfor %} + {% else %} +

本场比赛还没有提交记录

+ {% endif %}
- {% else %} -

你还没有提交记录!

- {% endif %} +
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/template/src/oj/contest/submissions_list_admin.html b/template/src/oj/contest/submissions_list_admin.html new file mode 100644 index 0000000..8f1660b --- /dev/null +++ b/template/src/oj/contest/submissions_list_admin.html @@ -0,0 +1,114 @@ +{% extends 'oj_base.html' %} + +{% block body %} + + {% load submission %} + {% load user %} +
+
+ +
+ + + + + + + + + + + + + {% if submissions %} + + + {% for item in submissions %} + + + + + + + + + + {% endfor %} + + {% else %} +

本场比赛还没有提交记录

+ {% endif %} +
#题目名称用户提交时间 + + 运行时间 + +
+ {{ forloop.counter |add:start_id }} + {{ item.title }} + {{ item.user_id|get_username }}{{ item.create_time }} + {{ item.language|translate_language }} + + {% if item.accepted_answer_time %} + {{ item.accepted_answer_time }}ms + {% else %} + -- + {% endif %} + + {{ item.result|translate_result }} +
+ + +
+{% endblock %} \ No newline at end of file diff --git a/template/src/oj/problem/problem.html b/template/src/oj/problem/problem.html index f683343..2ea7914 100644 --- a/template/src/oj/problem/problem.html +++ b/template/src/oj/problem/problem.html @@ -16,7 +16,7 @@
-

{{ problem.description|safe }}

+
{{ problem.description|safe }}
@@ -48,7 +48,7 @@ {% if problem.hint %}
-

{{ problem.hint|safe }}

+
{{ problem.hint|safe }}
{% endif %}
diff --git a/template/src/oj/problem/problem_list.html b/template/src/oj/problem/problem_list.html index ed57e16..0af942d 100644 --- a/template/src/oj/problem/problem_list.html +++ b/template/src/oj/problem/problem_list.html @@ -21,8 +21,8 @@ # 题目 - 难度 - 通过率 + 难度 + 通过率 @@ -31,7 +31,16 @@ {{ item.id }} {{ item.title }} - {{ item.difficulty }} + + {% ifequal item.difficulty 1 %} + 简单 + {% else %} + {% ifequal item.difficulty 2 %} + 中等 + {% else %} + 难 + {% endifequal %} + {% endifequal %} {{ item|accepted_radio }} {% endfor %} @@ -65,10 +74,11 @@
    {% for item in tags %} -
  • - {{ item.problem_number }} - {{ item.name }} -
  • +
  • + {{ item.problem_number }} + {{ item.name }} +
  • {% endfor %}
diff --git a/template/src/oj/submission/my_submissions_list.html b/template/src/oj/submission/my_submissions_list.html index 08cf17d..6325f8b 100644 --- a/template/src/oj/submission/my_submissions_list.html +++ b/template/src/oj/submission/my_submissions_list.html @@ -3,7 +3,7 @@ {% block body %} {% load submission %}
-
+
@@ -52,7 +52,7 @@ {{ forloop.counter |add:start_id }}
- {{ item.problem_id }} + {{ item.title }} {{ item.create_time }} @@ -92,8 +92,6 @@

你还没有提交记录!

{% endif %} -
- {% include "oj/announcement/_announcement_panel.html" %} -
+ {% endblock %} \ No newline at end of file diff --git a/template/src/utils/about.html b/template/src/utils/about.html index fe08460..8404beb 100644 --- a/template/src/utils/about.html +++ b/template/src/utils/about.html @@ -11,6 +11,7 @@

ACM 简介

+

ACM国际大学生程序设计竞赛(英语:ACM International Collegiate Programming Contest, diff --git a/utils/templatetags/announcement_list.py b/utils/templatetags/announcement_list.py new file mode 100644 index 0000000..acf38d9 --- /dev/null +++ b/utils/templatetags/announcement_list.py @@ -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") \ No newline at end of file diff --git a/utils/views.py b/utils/views.py new file mode 100644 index 0000000..87beeb4 --- /dev/null +++ b/utils/views.py @@ -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})