diff --git a/contest/migrations/0009_contestsubmission_first_achieved.py b/contest/migrations/0009_contestsubmission_first_achieved.py new file mode 100644 index 0000000..ce9529c --- /dev/null +++ b/contest/migrations/0009_contestsubmission_first_achieved.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', '0008_auto_20150912_1912'), + ] + + operations = [ + migrations.AddField( + model_name='contestsubmission', + name='first_achieved', + field=models.BooleanField(default=False), + ), + ] diff --git a/contest/models.py b/contest/models.py index d345c16..7331ec2 100644 --- a/contest/models.py +++ b/contest/models.py @@ -89,10 +89,12 @@ class ContestSubmission(models.Model): total_submission_number = models.IntegerField(default=1) # 这道题是 AC 还是没过 ac = models.BooleanField() - # ac 用时 + # ac 用时以秒计 ac_time = models.IntegerField(default=0) # 总的时间,用于acm 类型的,也需要保存罚时 total_time = models.IntegerField(default=0) + # 第一个解出此题目 + first_achieved = models.BooleanField(default=False) class Meta: db_table = "contest_submission" diff --git a/contest/serializers.py b/contest/serializers.py index 65b2fe7..8f3bf0b 100644 --- a/contest/serializers.py +++ b/contest/serializers.py @@ -13,7 +13,7 @@ class CreateContestSerializer(serializers.Serializer): description = serializers.CharField(max_length=5000) mode = serializers.IntegerField() contest_type = serializers.IntegerField() - show_rank = serializers.BooleanField() + real_time_rank = serializers.BooleanField() show_user_submission = serializers.BooleanField() password = serializers.CharField(max_length=30, required=False, default=None) start_time = serializers.DateTimeField() @@ -47,7 +47,7 @@ class EditContestSerializer(serializers.Serializer): description = serializers.CharField(max_length=10000) mode = serializers.IntegerField() contest_type = serializers.IntegerField() - show_rank = serializers.BooleanField() + real_time_rank = serializers.BooleanField() show_user_submission = serializers.BooleanField() password = serializers.CharField(max_length=30, required=False, default=None) start_time = serializers.DateTimeField() diff --git a/contest/views.py b/contest/views.py index 9f2eab8..31a6c36 100644 --- a/contest/views.py +++ b/contest/views.py @@ -21,6 +21,8 @@ from .serializers import (CreateContestSerializer, ContestSerializer, EditContes CreateContestProblemSerializer, ContestProblemSerializer, ContestPasswordVerifySerializer, EditContestProblemSerializer) +from oj.settings import REDIS_CACHE +import redis class ContestAdminAPIView(APIView): @@ -58,7 +60,7 @@ class ContestAdminAPIView(APIView): try: contest = Contest.objects.create(title=data["title"], description=data["description"], mode=data["mode"], contest_type=data["contest_type"], - show_rank=data["show_rank"], password=data["password"], + real_time_rank=data["real_time_rank"], password=data["password"], show_user_submission=data["show_user_submission"], start_time=dateparse.parse_datetime(data["start_time"]), end_time=dateparse.parse_datetime(data["end_time"]), @@ -112,7 +114,7 @@ class ContestAdminAPIView(APIView): contest.description = data["description"] contest.mode = data["mode"] contest.contest_type = data["contest_type"] - contest.show_rank = data["show_rank"] + contest.real_time_rank = data["real_time_rank"] contest.show_user_submission = data["show_user_submission"] contest.start_time = dateparse.parse_datetime(data["start_time"]) contest.end_time = dateparse.parse_datetime(data["end_time"]) @@ -353,7 +355,6 @@ def contest_list_page(request, page=1): 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) try: current_page = paginator.page(int(page)) @@ -390,23 +391,62 @@ def _cmp(x, y): return -1 +def get_the_time_format(seconds): + if not seconds: + return "" + result = str(seconds % 60) + if seconds % 60 < 10: + result = "0" + result + result = str((seconds % 3600) / 60) + ":" + result + if (seconds % 3600) / 60 < 10: + result = "0" + result + result = str(seconds / 3600) + ":" + result + if seconds / 3600 < 10: + result = "0" + result + return result + + @check_user_contest_permission def contest_rank_page(request, contest_id): contest = Contest.objects.get(id=contest_id) contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index") - result = ContestSubmission.objects.filter(contest=contest).values("user_id").\ - annotate(total_submit=Sum("total_submission_number")) - for i in range(0, len(result)): - # 这个人所有的提交 - submissions = ContestSubmission.objects.filter(user_id=result[i]["user_id"], contest_id=contest_id) - result[i]["submissions"] = {} - for item in submissions: - result[i]["submissions"][item.problem_id] = item - result[i]["total_ac"] = submissions.filter(ac=True).count() - result[i]["user"] = User.objects.get(id=result[i]["user_id"]) - result[i]["total_time"] = submissions.filter(ac=True).aggregate(total_time=Sum("total_time"))["total_time"] + r = redis.Redis(host=REDIS_CACHE["host"], port=REDIS_CACHE["port"], db=REDIS_CACHE["db"]) + if contest.real_time_rank: + # 更新rank + result = ContestSubmission.objects.filter(contest=contest).values("user_id"). \ + annotate(total_submit=Sum("total_submission_number")) + for i in range(0, len(result)): + # 这个人所有的提交 + submissions = ContestSubmission.objects.filter(user_id=result[i]["user_id"], contest_id=contest_id) + result[i]["submissions"] = {} + result[i]["problems"] = [] + for problem in contest_problems: + try: + status = submissions.get(problem=problem) + result[i]["problems"].append({ + "first_achieved": status.first_achieved, + "ac": status.ac, + "failed_number": status.total_submission_number, + "ac_time": get_the_time_format(status.ac_time)}) + if status.ac: + result[i]["problems"][-1]["failed_number"] -= 1 + except ContestSubmission.DoesNotExist: + result[i]["problems"].append({}) + result[i]["total_ac"] = submissions.filter(ac=True).count() + result[i]["username"] = User.objects.get(id=result[i]["user_id"]).username + result[i]["total_time"] = get_the_time_format(submissions.filter(ac=True).aggregate(total_time=Sum("total_time"))["total_time"]) + result = sorted(result, cmp=_cmp, reverse=True) + r.set("contest_rank_" + contest_id, json.dumps(list(result))) + else: + # 从缓存读取排名信息 + result = r.get("contest_rank_" + contest_id) + if result: + result = json.loads(result) + else: + result = [] return render(request, "oj/contest/contest_rank.html", {"contest": contest, "contest_problems": contest_problems, - "result": sorted(result, cmp=_cmp, reverse=True), - "auto_refresh": request.GET.get("auto_refresh", None) == "true"}) + "result": result, + "auto_refresh": request.GET.get("auto_refresh", None) == "true", + "real_time_rank": contest.real_time_rank}) diff --git a/mq/scripts/info.py b/mq/scripts/info.py index a3e13b9..01ef6b2 100644 --- a/mq/scripts/info.py +++ b/mq/scripts/info.py @@ -8,7 +8,7 @@ from judge.judger.result import result from submission.models import Submission from problem.models import Problem from contest.models import ContestProblem, Contest, ContestSubmission - +from account.models import User logger = logging.getLogger("app_info") @@ -53,10 +53,8 @@ class MessageQueue(object): contest_submission = ContestSubmission.objects.get(user_id=submission.user_id, contest=contest, problem_id=contest_problem.id) # 提交次数加1 - contest_submission.total_submission_number += 1 if submission.result == result["accepted"]: - # 避免这道题已经 ac 了,但是又重新提交了一遍 if not contest_submission.ac: # 这种情况是这个题目前处于错误状态,就使用已经存储了的罚时加上这道题的实际用时 @@ -64,30 +62,40 @@ class MessageQueue(object): # logger.debug(submission.create_time) # logger.debug((submission.create_time - contest.start_time).total_seconds()) # logger.debug(int((submission.create_time - contest.start_time).total_seconds() / 60)) - contest_submission.ac_time = int((submission.create_time - contest.start_time).total_seconds() / 60) + contest_submission.ac_time = int((submission.create_time - contest.start_time).total_seconds()) contest_submission.total_time += contest_submission.ac_time + contest_submission.total_submission_number += 1 # 标记为已经通过 + if contest_problem.total_accepted_number == 0: + contest_submission.first_achieved = True contest_submission.ac = True # contest problem ac 计数器加1 contest_problem.total_accepted_number += 1 else: # 如果这个提交是错误的,就罚时20分钟 - contest_submission.total_time += 20 + contest_submission.total_time += 1200 + contest_submission.total_submission_number += 1 contest_submission.save() contest_problem.save() except ContestSubmission.DoesNotExist: # 第一次提交 is_ac = submission.result == result["accepted"] + first_achieved = False + ac_time = 0 if is_ac: - total_time = int((submission.create_time - contest.start_time).total_seconds() / 60) + ac_time = int((submission.create_time - contest.start_time).total_seconds()) + total_time = int((submission.create_time - contest.start_time).total_seconds()) # 增加题目总的ac数计数器 + if contest_problem.total_accepted_number == 0: + first_achieved = True contest_problem.total_accepted_number += 1 contest_problem.save() else: # 没过罚时20分钟 - total_time = 20 + total_time = 1200 ContestSubmission.objects.create(user_id=submission.user_id, contest=contest, problem=contest_problem, - ac=is_ac, total_time=total_time) + ac=is_ac, total_time=total_time, first_achieved=first_achieved, + ac_time=ac_time) logger.debug("Start message queue") diff --git a/problem/serizalizers.py b/problem/serizalizers.py index f9c5caa..afa7f43 100644 --- a/problem/serizalizers.py +++ b/problem/serizalizers.py @@ -31,6 +31,7 @@ class CreateProblemSerializer(serializers.Serializer): difficulty = serializers.IntegerField() tags = serializers.ListField(child=serializers.CharField(max_length=10)) hint = serializers.CharField(max_length=3000, allow_blank=True) + visible = visible = serializers.BooleanField() class ProblemTagSerializer(serializers.ModelSerializer): diff --git a/problem/views.py b/problem/views.py index c807732..c66b90e 100644 --- a/problem/views.py +++ b/problem/views.py @@ -13,13 +13,16 @@ from rest_framework.views import APIView from django.conf import settings + from announcement.models import Announcement from utils.shortcuts import (serializer_invalid_response, error_response, success_response, paginate, rand_str, error_page) from .serizalizers import (CreateProblemSerializer, EditProblemSerializer, ProblemSerializer, ProblemTagSerializer, CreateProblemTagSerializer) from .models import Problem, ProblemTag +import logging +logger = logging.getLogger("app_info") def problem_page(request, problem_id): try: @@ -56,7 +59,8 @@ class ProblemAdminAPIView(APIView): memory_limit=data["memory_limit"], difficulty=data["difficulty"], created_by=request.user, - hint=data["hint"]) + hint=data["hint"], + visible=data["visible"]) for tag in data["tags"]: try: tag = ProblemTag.objects.get(name=tag) @@ -151,8 +155,9 @@ class TestCaseUploadAPIView(APIView): with open(tmp_zip, "wb") as test_case_zip: for chunk in f: test_case_zip.write(chunk) - except IOError: - return error_response(u"上传错误,写入临时目录失败") + except IOError as e: + logger.error(e) + return error_response(u"上传失败") test_case_file = zipfile.ZipFile(tmp_zip, 'r') name_list = test_case_file.namelist() diff --git a/static/src/css/oj.css b/static/src/css/oj.css index 08b6d02..86748c2 100644 --- a/static/src/css/oj.css +++ b/static/src/css/oj.css @@ -107,4 +107,9 @@ li.list-group-item { #about-acm-logo{ width: 40%; -} \ No newline at end of file +} + +.rank .first-achieved{ + background: #33CC99; +} + diff --git a/static/src/js/app/admin/contest/contestList.js b/static/src/js/app/admin/contest/contestList.js index 315023c..c0f30af 100644 --- a/static/src/js/app/admin/contest/contestList.js +++ b/static/src/js/app/admin/contest/contestList.js @@ -28,7 +28,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker", ajaxData.groups = selectedGroups; } else { - if (vm.password) { + if (vm.editPassword) { ajaxData.password = vm.editPassword; ajaxData.contest_type = 2; } @@ -114,7 +114,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker", isGlobal: true, allGroups: [], showGlobalViewRadio: true, - + admin_type: 1, getNext: function () { if (!vm.nextPage) return; @@ -211,6 +211,39 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker", if (el) problemId = el.id; vm.$fire("up!showContestSubmissionPage", problemId, vm.contestList[vm.editingProblemContestIndex - 1].id, vm.editMode); + }, + addToProblemList: function (problem) { + var ajaxData = { + title: problem.title, + description: problem.description, + time_limit: problem.time_limit, + memory_limit: problem.memory_limit, + samples: problem.samples, + test_case_id: problem.test_case_id, + hint: problem.hint, + source: problem.contest.title, + visible: false, + tags: [], + input_description: problem.input_description, + output_description: problem.output_description, + difficulty: 0 + }; + $.ajax({ + beforeSend: csrfTokenHeader, + url: "/api/admin/problem/", + dataType: "json", + data: JSON.stringify(ajaxData), + method: "post", + contentType: "application/json", + success: function (data) { + if (!data.code) { + bsAlert("题目添加成功!题目现在处于隐藏状态,请到题目列表手动修改,并添加分类和难度信息!"); + } + else { + bsAlert(data.data); + } + } + }); } }); vm.$watch("showVisibleOnly", function () { @@ -266,6 +299,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker", success: function (data) { if (!data.code) { var admin_type = data.data.admin_type; + vm.admin_type = admin_type; if (data.data.admin_type == 1) { vm.isGlobal = false; vm.showGlobalViewRadio = false; @@ -278,6 +312,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker", success: function (data) { if (!data.code) { if (!data.data.length) { + if (admin_type != 2) bsAlert("您的用户权限只能创建小组内比赛,但是您还没有创建过小组"); return; diff --git a/static/src/js/app/oj/problem/problem.js b/static/src/js/app/oj/problem/problem.js index d855d11..4817309 100644 --- a/static/src/js/app/oj/problem/problem.js +++ b/static/src/js/app/oj/problem/problem.js @@ -117,11 +117,11 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"], function guessLanguage(code) { //cpp - if (code.indexOf("using namespace std") > -1) { + if (code.indexOf("using namespace std") > -1||code.indexOf("") > -1) { return "2"; } - //c - if (code.indexOf("printf") > -1) { + if (code.indexOf("printf")) + { return "1"; } //java @@ -146,6 +146,19 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"], } } + if (language < 3) { + if (code.indexOf("__int64") > -1) { + if (!confirm("您是否在尝试使用'__int64'类型? 这不是 c/c++ 标准并将引发编译错误可以使用 'long long' 代替(详见 关于->帮助),是否仍然提交?")) { + return; + } + } + if (code.indexOf("%I64d") > -1) { + if (!confirm("您是否在尝试将'%I64d'用于long long类型的I/O? 这不被支持,并可能会导致程序输出异常,可以使用 '%lld' 代替(详见 关于->帮助),是否仍然提交?")) { + return; + } + } + } + if (location.href.indexOf("contest") > -1) { var problemId = location.pathname.split("/")[4]; var contestId = location.pathname.split("/")[2]; diff --git a/submission/views.py b/submission/views.py index e5c1af7..45e1875 100644 --- a/submission/views.py +++ b/submission/views.py @@ -21,7 +21,6 @@ from .models import Submission from .serializers import CreateSubmissionSerializer, SubmissionSerializer, SubmissionhareSerializer - class SubmissionAPIView(APIView): @login_required def post(self, request): @@ -81,7 +80,8 @@ def problem_my_submissions_list_page(request, problem_id): except Problem.DoesNotExist: return error_page(request, u"问题不存在") - submissions = Submission.objects.filter(user_id=request.user.id, problem_id=problem.id, contest_id__isnull=True).order_by("-create_time"). \ + submissions = Submission.objects.filter(user_id=request.user.id, problem_id=problem.id, + contest_id__isnull=True).order_by("-create_time"). \ values("id", "result", "create_time", "accepted_answer_time", "language") return render(request, "oj/problem/my_submissions_list.html", @@ -193,7 +193,7 @@ def my_submission_list_page(request, page=1): return render(request, "oj/submission/my_submissions_list.html", {"submissions": current_page, "page": int(page), "previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20, - "announcements": announcements, "filter":filter}) + "announcements": announcements, "filter": filter}) class SubmissionShareAPIView(APIView): @@ -209,4 +209,4 @@ class SubmissionShareAPIView(APIView): submission.save() return success_response(submission.shared) else: - return serializer_invalid_response(serializer) \ No newline at end of file + return serializer_invalid_response(serializer) diff --git a/template/src/admin/admin.html b/template/src/admin/admin.html index c7577ba..459a69f 100644 --- a/template/src/admin/admin.html +++ b/template/src/admin/admin.html @@ -31,9 +31,9 @@ {% endblock %} diff --git a/template/src/oj_base.html b/template/src/oj_base.html index b398976..f90052c 100644 --- a/template/src/oj_base.html +++ b/template/src/oj_base.html @@ -50,28 +50,31 @@
  • 关于
  • {% if request.user.is_authenticated %} - + {% else %} - + {% endif %} diff --git a/template/src/utils/help.html b/template/src/utils/help.html index e69de29..c03b354 100644 --- a/template/src/utils/help.html +++ b/template/src/utils/help.html @@ -0,0 +1,60 @@ +{% extends "oj_base.html" %} +{% block body %} +
    + +
    +

    判题系统

    +

    判题结果

    +
      +
    • Accepted: 你的答案符合判题标准
    • +
    • Runtime Error: 你的程序运行时出现错误(指针越界,栈溢出,有未处理的异常,主函数返回值非零等)
    • +
    • Time Limit Exceeded: 你的程序执行时间超出题目要求
    • +
    • Memory Limit Exceeded: 你的程序内存使用超出题目要求
    • +
    • Compile Error: 你的程序在编译(包括链接)时出现错误
    • +
    • Wrong Answer: 你的程序输出的答案不符合判题标准
    • +
    • System Error: 判题系统发生故障,请等待重判
    • +
    • Waiting: 你的提交正在等待处理
    • +
    +

    支持的语言

    +
      +
    • C (GCC 4.8)
    • + +
    • C++ (G++ 4.3)
    • + +
    • Java (Oracle JDK 1.7)
    • +
    +

    编译参数

    +
      +
    • C
    • +

      gcc -DONLINE_JUDGE -O2 -w -std=c99 {src_path} -lm -o {exe_path}main

      +
    • C++
    • +

      g++ -DONLINE_JUDGE -O2 -w -std=c++11 {src_path} -lm -o {exe_path}main

      +
    • Java
    • +

      javac {src_path} -d {exe_path}

      +

      java -cp {exe_path} Main

      +
    + + +

    常见问题

    +
      +
    • 输入输出
    • +

      无特殊说明,请使用标准输入输出

      +
    • C/C++的64位整数类型
    • +

      请使用long long声明,使用cin/cout或 %lld输入输出

      +
    • 判题结果与本地执行结果不一致
    • +

      是否使用了不同版本的编译器(VC和TC并不完全符合C/C++标准)

      +

      判题时可能使用了与您测试时不同的测试数据(不仅限于样例中展示的数据)

      +
    • 程序执行时间和占用的内存如何计算
    • +

      执行时间指CPU时间,占用内存按执行过程中内存消耗的峰值计,有多组测试数据时以最大的时间和内存消耗为准

      + +
    +
    +
    +{% endblock %} diff --git a/utils/templatetags/submission.py b/utils/templatetags/submission.py index 2e73417..aeb11a3 100644 --- a/utils/templatetags/submission.py +++ b/utils/templatetags/submission.py @@ -29,33 +29,7 @@ def translate_result_class(value): return "danger" -def get_contest_submission_problem_detail(contest_problem, my_submission): - if contest_problem.id in my_submission: - submission = my_submission[contest_problem.id] - if submission.ac: - # 只提交了一次就AC - if submission.total_submission_number == 1: - return str(submission.ac_time) + " min" - else: - return "20 min × " + str(submission.total_submission_number - 1) + " WA + " + str(submission.ac_time) + " min" - return str(submission.total_submission_number) + " WA" - else: - return "" - - -def get_submission_problem_result_class(contest_problem, my_submission): - if contest_problem.id in my_submission: - submission = my_submission[contest_problem.id] - if submission.ac: - return "success" - else: - return "danger" - else: - return "" - register = template.Library() register.filter("translate_result", translate_result) register.filter("translate_language", translate_language) register.filter("translate_result_class", translate_result_class) -register.simple_tag(get_contest_submission_problem_detail, name="submission_problem") -register.simple_tag(get_submission_problem_result_class, name="submission_problem_result_class") \ No newline at end of file diff --git a/utils/views.py b/utils/views.py index 7e2375e..1b73eb0 100644 --- a/utils/views.py +++ b/utils/views.py @@ -5,7 +5,9 @@ from rest_framework.response import Response from django.conf import settings from utils.shortcuts import rand_str +import logging +logger = logging.getLogger("app_info") class SimditorImageUploadAPIView(APIView): def post(self, request): @@ -22,7 +24,8 @@ class SimditorImageUploadAPIView(APIView): with open(image_dir, "wb") as imageFile: for chunk in img: imageFile.write(chunk) - except IOError: + except IOError as e: + logger.error(e) return Response(data={ "success": True, "msg": "上传错误",