diff --git a/.gitignore b/.gitignore index 31d22157..e0873616 100644 --- a/.gitignore +++ b/.gitignore @@ -62,4 +62,5 @@ static/src/upload_image/* build.txt tmp/ test_case/ -release/ \ No newline at end of file +release/ +upload/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2e10830a..113ed588 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM python:2.7 ENV PYTHONBUFFERED 1 -RUN mkdir -p /code/log /code/test_case +RUN mkdir -p /code/log /code/test_case /code/upload WORKDIR /code ADD requirements.txt /code/ RUN pip install -r requirements.txt diff --git a/account/views.py b/account/views.py index 1304ded5..4aa326a9 100644 --- a/account/views.py +++ b/account/views.py @@ -36,11 +36,24 @@ class UserLoginAPIView(APIView): else: return serializer_invalid_response(serializer) + @login_required def logout(request): auth.logout(request) return http.HttpResponseRedirect("/") + +def page_jump(request): + if not request.user.is_authenticated(): + return render(request, "oj/index.html") + + try: + if request.META['HTTP_REFERER']: + return render(request, "oj/index.html") + except KeyError: + return http.HttpResponseRedirect('/problems/') + + class UserRegisterAPIView(APIView): def post(self, request): """ diff --git a/contest/migrations/0009_contestsubmission_first_achieved.py b/contest/migrations/0009_contestsubmission_first_achieved.py new file mode 100644 index 00000000..ce9529ce --- /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 d345c165..7331ec27 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 65b2fe7d..8f3bf0bc 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 9f2eab81..31a6c36c 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/judge/Dockerfile b/judge/Dockerfile index c2ca1b15..7718e06f 100644 --- a/judge/Dockerfile +++ b/judge/Dockerfile @@ -3,6 +3,8 @@ MAINTAINER virusdefender RUN mkdir /var/install/ WORKDIR /var/install/ ENV DEBIAN_FRONTEND noninteractive +RUN rm /etc/apt/sources.list +COPY sources.list /etc/apt/ RUN apt-get update RUN apt-get -y install software-properties-common python-software-properties RUN add-apt-repository -y ppa:webupd8team/java diff --git a/judge/judger/settings.py b/judge/judger/settings.py index 1f15b96b..224cf5dd 100644 --- a/judge/judger/settings.py +++ b/judge/judger/settings.py @@ -1,4 +1,5 @@ # coding=utf-8 +import os # 单个判题端最多同时运行的程序个数,因为判题端会同时运行多组测试数据,比如一共有5组测试数据 # 如果MAX_RUNNING_NUMBER大于等于5,那么这5组数据就会同时进行评测,然后返回结果。 # 如果MAX_RUNNING_NUMBER小于5,为3,那么就会同时运行前三组测试数据,然后再运行后两组数据 @@ -14,12 +15,10 @@ lrun_gid = 1002 # judger工作目录 judger_workspace = "/var/judger/" - -# 这个是在docker 中访问数据库 ip 不一定和web服务器还有celery的一样 submission_db = { - "host": "192.168.42.1", + "host": os.environ.get("MYSQL_PORT_3306_TCP_ADDR", "127.0.0.1"), "port": 3306, "db": "oj_submission", "user": "root", - "password": "mypwd" + "password": os.environ.get("MYSQL_ENV_MYSQL_ROOT_PASSWORD", "root") } diff --git a/judge/sources.list b/judge/sources.list new file mode 100644 index 00000000..3f18a0f9 --- /dev/null +++ b/judge/sources.list @@ -0,0 +1,10 @@ +deb http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse +deb http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse +deb http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse +deb http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse +deb http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse +deb-src http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse +deb-src http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse +deb-src http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse +deb-src http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse +deb-src http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse \ No newline at end of file diff --git a/mq/scripts/info.py b/mq/scripts/info.py index a3e13b9f..01ef6b2d 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/oj/local_settings.py b/oj/local_settings.py index e61ec9c7..074b1b01 100644 --- a/oj/local_settings.py +++ b/oj/local_settings.py @@ -3,10 +3,6 @@ import os BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - -# 下面是需要自己修改的 -LOG_PATH = "log/" - # 注意这是web 服务器访问的地址,判题端访问的地址不一定一样,因为可能不在一台机器上 DATABASES = { 'default': { @@ -17,11 +13,11 @@ DATABASES = { 'submission': { 'NAME': 'oj_submission', 'ENGINE': 'django.db.backends.mysql', - 'HOST': "121.42.32.129", + 'CONN_MAX_AGE': 0.1, + 'HOST': "127.0.0.1", 'PORT': 3306, 'USER': 'root', - 'PASSWORD': 'mypwd', - 'CONN_MAX_AGE': 0.1, + 'PASSWORD': 'root', } } @@ -33,9 +29,11 @@ REDIS_CACHE = { DEBUG = True -# 同理 这是 web 服务器的上传路径 -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 +# 在 debug 关闭的情况下,静态文件不是有 django runserver 来处理的,应该由 nginx 返回 +# 在 debug 开启的情况下,django 会在下面两个文件夹中寻找对应的静态文件。 +STATICFILES_DIRS = [os.path.join(BASE_DIR, "static/src/"), BASE_DIR] + +# 模板文件夹 +TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'template/src/')] \ No newline at end of file diff --git a/oj/server_settings.py b/oj/server_settings.py index cc5ee829..77ca1fd0 100644 --- a/oj/server_settings.py +++ b/oj/server_settings.py @@ -3,43 +3,41 @@ import os BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - -# 下面是需要自己修改的 -LOG_PATH = "/var/log/oj/" - # 注意这是web 服务器访问的地址,判题端访问的地址不一定一样,因为可能不在一台机器上 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': "oj", 'CONN_MAX_AGE': 0.1, - 'HOST': '127.0.0.1', + 'HOST': os.environ.get("MYSQL_PORT_3306_TCP_ADDR", "127.0.0.1"), 'PORT': 3306, 'USER': 'root', - 'PASSWORD': 'mypwd' + 'PASSWORD': os.environ.get("MYSQL_ENV_MYSQL_ROOT_PASSWORD", "root") }, 'submission': { 'NAME': 'oj_submission', 'ENGINE': 'django.db.backends.mysql', 'CONN_MAX_AGE': 0.1, - 'HOST': "127.0.0.1", + 'HOST': os.environ.get("MYSQL_PORT_3306_TCP_ADDR", "127.0.0.1"), 'PORT': 3306, 'USER': 'root', - 'PASSWORD': 'mypwd' + 'PASSWORD': os.environ.get("MYSQL_ENV_MYSQL_ROOT_PASSWORD", "root") } } REDIS_CACHE = { - "host": "127.0.0.1", + "host": os.environ.get("REDIS_PORT_6379_TCP_ADDR", "127.0.0.1"), "port": 6379, "db": 1 } -DEBUG = True - -# 同理 这是 web 服务器的上传路径 -TEST_CASE_DIR = '/root/test_case/' +DEBUG = False ALLOWED_HOSTS = ['*'] -IMAGE_UPLOAD_DIR = '/var/mnt/source/OnlineJudge/static/src/upload_image/' +# 在 debug 关闭的情况下,静态文件不是有 django runserver 来处理的,应该由 nginx 返回 +# 在 debug 开启的情况下,django 会在下面两个文件夹中寻找对应的静态文件。 +STATICFILES_DIRS = [os.path.join(BASE_DIR, "static/release/"), os.path.join(BASE_DIR, "static/release/")] + +# 模板文件夹 +TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'template/release/')] diff --git a/oj/settings.py b/oj/settings.py index 097a4c21..7e479194 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -32,8 +32,6 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SECRET_KEY = 'hzfp^8mbgapc&x%$#xv)0=t8s7_ilingw(q3!@h&2fty6v6fxz' - - # Application definition INSTALLED_APPS = ( @@ -76,7 +74,7 @@ ROOT_URLCONF = 'oj.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR, 'template/src')], + 'DIRS': TEMPLATE_DIRS, 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -91,7 +89,6 @@ TEMPLATES = [ WSGI_APPLICATION = 'oj.wsgi.application' - # Internationalization # https://docs.djangoproject.com/en/1.8/topics/i18n/ @@ -111,10 +108,11 @@ USE_TZ = True STATIC_URL = '/static/' -STATICFILES_DIRS = (os.path.join(BASE_DIR, "static/src/"),) - AUTH_USER_MODEL = 'account.User' +LOG_PATH = "log/" + + LOGGING = { 'version': 1, 'disable_existing_loggers': True, @@ -166,4 +164,8 @@ REST_FRAMEWORK = { 'TEST_REQUEST_DEFAULT_FORMAT': 'json' } -DATABASE_ROUTERS = ['oj.db_router.DBRouter'] \ No newline at end of file +DATABASE_ROUTERS = ['oj.db_router.DBRouter'] + +TEST_CASE_DIR = os.path.join(BASE_DIR, 'test_case/') + +IMAGE_UPLOAD_DIR = os.path.join(BASE_DIR, 'upload/') \ No newline at end of file diff --git a/oj/urls.py b/oj/urls.py index e9cc880b..eda20bb0 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -26,7 +26,7 @@ from contest_submission.views import contest_problem_my_submissions_list_page urlpatterns = [ url(r'^install/$', "install.views.install"), - url("^$", TemplateView.as_view(template_name="oj/index.html"), name="index_page"), + url("^$", "account.views.page_jump", name="page_jump_api"), url(r'^docs/', include('rest_framework_swagger.urls')), url(r'^admin/$', TemplateView.as_view(template_name="admin/admin.html"), name="admin_spa_page"), url(r'^admin/contest/$', TemplateView.as_view(template_name="admin/contest/add_contest.html"), diff --git a/problem/serizalizers.py b/problem/serizalizers.py index f9c5caa0..afa7f432 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 c8077329..c66b90ed 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 08b6d02b..86748c2d 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 315023ce..c0f30af1 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 d855d113..4817309d 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/static/src/js/build.js b/static/src/js/build.js index 57e6321b..3acf938c 100644 --- a/static/src/js/build.js +++ b/static/src/js/build.js @@ -4,7 +4,7 @@ // 第三方脚本模块的别名,jquery比libs/jquery-1.11.1.min.js简洁明了; paths: { jquery: "empty:", - avalon: "lib/avalon/avalon", + avalon: "empty:", editor: "utils/editor", uploader: "utils/uploader", formValidation: "utils/formValidation", @@ -70,9 +70,71 @@ appDir: "../", dir: "../../release/", modules: [ - + { + name: "bootstrap", + }, + { + name: "addProblem_0_pack" + }, { name: "addContest_1_pack" + }, + { + name: "problem_2_pack" + }, + { + name: "register_3_pack" + }, + { + name: "contestList_4_pack" + }, + { + name: "group_5_pack" + }, + { + name: "editProblem_6_pack" + }, + { + name: "announcement_7_pack" + }, + { + name: "monitor_8_pack" + }, + { + name: "groupDetail_9_pack" + }, + { + name: "admin_10_pack" + }, + { + name: "problem_11_pack" + }, + { + name: "submissionList_12_pack" + }, + { + name: "editProblem_13_pack" + }, + { + name: "joinGroupRequestList_14_pack" + }, + { + name: "changePassword_15_pack" + }, + { + name: "group_16_pack" + }, + { + name: "submissionList_17_pack" + }, + { + name: "login_18_pack" + }, + { + name: "contestPassword_19_pack" + }, + { + name: "userList_20_pack" } ], optimizeCss: "standard", diff --git a/submission/views.py b/submission/views.py index e5c1af71..45e1875c 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 c7577baf..459a69fc 100644 --- a/template/src/admin/admin.html +++ b/template/src/admin/admin.html @@ -31,9 +31,9 @@ {% endblock %} diff --git a/template/src/oj/index.html b/template/src/oj/index.html index 41f5d23a..4a8edd3e 100644 --- a/template/src/oj/index.html +++ b/template/src/oj/index.html @@ -29,6 +29,36 @@ padding: 20px 20px 0 20px; } + #login { + float: right; + position: relative; + height: 30px; + line-height: 64px; + margin-right: 30px; + z-index: 10; + margin-top: 4px; + } + + .login-zone .button:first-child { + margin-right: 5px; + } + + .button { + display: inline-block; + padding: 0 2em; + text-transform: none; + line-height: 2em; + height: 2em; + border-radius: 2em; + border: 1px solid #FFF; + outline: 0; + cursor: pointer; + } + + .button:first-child { + margin-right: 5px; + } + #name { font-size: 45px; margin-right: 20px; @@ -86,8 +116,6 @@ loopBottom: true }); }); - - @@ -100,6 +128,12 @@ 小组   关于 + {% if not request.user.is_authenticated %} +
+ + 注册 +
+ {% endif %} diff --git a/template/src/oj_base.html b/template/src/oj_base.html index b3989765..f90052cc 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 e69de29b..c03b354e 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/tools/release_static.py b/tools/release_static.py index ffee461b..0a51b8a7 100644 --- a/tools/release_static.py +++ b/tools/release_static.py @@ -9,9 +9,11 @@ template_release_path = "template/release/" static_src_path = "static/src/" static_release_path = "static/release/" - -# 删除模板的 release 文件夹 -shutil.rmtree(template_release_path) +try: + # 删除模板的 release 文件夹 + shutil.rmtree(template_release_path) +except Exception: + pass # 复制一份模板文件夹到 release shutil.copytree(template_src_path, template_release_path) diff --git a/utils/templatetags/submission.py b/utils/templatetags/submission.py index 2e734179..aeb11a3e 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 87beeb4f..1b73eb02 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,12 +24,13 @@ 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": "上传错误", - "file_path": "/static/upload_image/" + image_name}) + "file_path": "/static/upload/" + image_name}) return Response(data={ "success": True, "msg": "", - "file_path": "/static/upload_image/" + image_name}) + "file_path": "/static/upload/" + image_name})