diff --git a/account/views/oj.py b/account/views/oj.py index 385399c..de1df67 100644 --- a/account/views/oj.py +++ b/account/views/oj.py @@ -372,10 +372,9 @@ class UserRankAPI(APIView): if rule_type not in ContestRuleType.choices(): rule_type = ContestRuleType.ACM profiles = UserProfile.objects.select_related("user")\ - .filter(submission_number__gt=0)\ .exclude(user__is_disabled=True) if rule_type == ContestRuleType.ACM: - profiles = profiles.order_by("-accepted_number", "submission_number") + profiles = profiles.filter(submission_number__gt=0).order_by("-accepted_number", "submission_number") else: - profiles = profiles.order_by("-total_score") + profiles = profiles.filter(total_score__gt=0).order_by("-total_score") return self.success(self.paginate_data(request, profiles, RankInfoSerializer)) diff --git a/announcement/models.py b/announcement/models.py index c7252e9..a19b06c 100644 --- a/announcement/models.py +++ b/announcement/models.py @@ -15,4 +15,4 @@ class Announcement(models.Model): class Meta: db_table = "announcement" - ordering = ('-create_time',) + ordering = ("-create_time",) diff --git a/contest/models.py b/contest/models.py index adfa3a7..1ed8ff9 100644 --- a/contest/models.py +++ b/contest/models.py @@ -54,7 +54,7 @@ class Contest(models.Model): class Meta: db_table = "contest" - ordering = ("-create_time",) + ordering = ("-start_time",) class AbstractContestRank(models.Model): diff --git a/contest/tests.py b/contest/tests.py index 635c326..9ce9890 100644 --- a/contest/tests.py +++ b/contest/tests.py @@ -95,7 +95,7 @@ class ContestAPITest(APITestCase): self.assertSuccess(resp) -class ContestAnnouncementAPITest(APITestCase): +class ContestAnnouncementAdminAPITest(APITestCase): def setUp(self): self.create_super_admin() self.url = self.reverse("contest_announcement_admin_api") @@ -120,7 +120,7 @@ class ContestAnnouncementAPITest(APITestCase): def test_get_contest_announcements(self): self.test_create_contest_announcement() - response = self.client.get(self.url) + response = self.client.get(self.url + "?contest_id=" + str(self.data["contest_id"])) self.assertSuccess(response) def test_get_one_contest_announcement(self): diff --git a/judge/dispatcher.py b/judge/dispatcher.py index 199e627..3ae1d66 100644 --- a/judge/dispatcher.py +++ b/judge/dispatcher.py @@ -6,6 +6,7 @@ from urllib.parse import urljoin import requests from django.db import transaction from django.db.models import F +from django.conf import settings from account.models import User from conf.models import JudgeServer @@ -79,7 +80,10 @@ class JudgeDispatcher(object): try: for i in range(len(resp_data)): if resp_data[i]["result"] == JudgeStatus.ACCEPTED: - score += self.problem.test_case_score[i]["score"] + resp_data[i]["score"] = self.problem.test_case_score[i]["score"] + score += resp_data[i]["score"] + else: + resp_data[i]["score"] = 0 except IndexError: logger.error(f"Index Error raised when summing up the score in problem {self.problem.id}") self.submission.statistic_info["score"] = 0 @@ -115,8 +119,11 @@ class JudgeDispatcher(object): Submission.objects.filter(id=self.submission.id).update(result=JudgeStatus.JUDGING) - # TODO: try catch - resp = self._request(urljoin(server.service_url, "/judge"), data=data) + service_url = server.service_url + # not set service_url, it should be a linked container + if not service_url: + service_url = settings.DEFAULT_JUDGE_SERVER_SERVICE_URL + resp = self._request(urljoin(service_url, "/judge"), data=data) self.submission.info = resp if resp["err"]: self.submission.result = JudgeStatus.COMPILE_ERROR @@ -201,7 +208,7 @@ class JudgeDispatcher(object): logger.info("Contest debug mode, id: " + str(self.contest_id) + ", submission id: " + self.submission.id) return with transaction.atomic(): - user = User.objects.select_for_update().select_related("userprofile").get(id=self.submission.user_id) + user = User.objects.select_for_update().get(id=self.submission.user_id) user_profile = user.userprofile problem_id = str(self.problem.id) if self.contest.rule_type == ContestRuleType.ACM: @@ -298,5 +305,7 @@ class JudgeDispatcher(object): last_score = rank.submission_info.get(problem_id) if last_score: rank.total_score = rank.total_score - last_score + current_score + else: + rank.total_score = rank.total_score + current_score rank.submission_info[problem_id] = current_score rank.save() diff --git a/oj/dev_settings.py b/oj/dev_settings.py index 15ae239..448bc8f 100644 --- a/oj/dev_settings.py +++ b/oj/dev_settings.py @@ -26,7 +26,7 @@ ALLOWED_HOSTS = ["*"] TEST_CASE_DIR = "/tmp" -LOG_PATH = "/tmp/" +LOG_PATH = f"{BASE_DIR}/log/" AVATAR_URI_PREFIX = "/static/avatar" AVATAR_UPLOAD_DIR = f"{BASE_DIR}{AVATAR_URI_PREFIX}" diff --git a/oj/production_settings.py b/oj/production_settings.py index 56dbc49..bf8b3f8 100644 --- a/oj/production_settings.py +++ b/oj/production_settings.py @@ -30,3 +30,4 @@ AVATAR_UPLOAD_DIR = "/data/avatar" TEST_CASE_DIR = "/data/test_case" LOG_PATH = "/data/log" +DEFAULT_JUDGE_SERVER_SERVICE_URL = "http://judge-server:8080/" diff --git a/oj/settings.py b/oj/settings.py index c25a9b9..9c9ac49 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -10,6 +10,7 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.8/ref/settings/ """ import os +from copy import deepcopy from .custom_settings import * @@ -20,17 +21,17 @@ else: BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - # Applications -INSTALLED_APPS = ( +VENDOR_APPS = ( 'django.contrib.auth', 'django.contrib.sessions', 'django.contrib.contenttypes', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', - - 'account', +) +LOCAL_APPS = ( + 'account', 'announcement', 'conf', 'problem', @@ -38,8 +39,11 @@ INSTALLED_APPS = ( 'utils', 'submission', 'options', + 'judge', ) +INSTALLED_APPS = VENDOR_APPS + LOCAL_APPS + MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -115,17 +119,16 @@ LOGGING = { 'formatters': { 'standard': { 'format': '%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s'} - # 日志格式 }, 'handlers': { 'django_error': { - 'level': 'DEBUG', + 'level': 'WARNING', 'class': 'logging.handlers.RotatingFileHandler', 'filename': os.path.join(LOG_PATH, 'django.log'), 'formatter': 'standard' }, 'app_info': { - 'level': 'DEBUG', + 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', 'filename': os.path.join(LOG_PATH, 'app_info.log'), 'formatter': 'standard' @@ -137,23 +140,30 @@ LOGGING = { } }, 'loggers': { - 'app_info': { - 'handlers': ['app_info', "console"], - 'level': 'DEBUG', - 'propagate': True - }, 'django.request': { 'handlers': ['django_error', 'console'], - 'level': 'DEBUG', + 'level': 'WARNING', + 'propagate': True, + }, + 'django.server': { + 'handlers': ['django_error', 'console'], + 'level': 'ERROR', 'propagate': True, }, 'django.db.backends': { - 'handlers': ['console'], - 'level': 'ERROR', + 'handlers': ['django_error', 'console'], + 'level': 'WARNING', 'propagate': True, - } + }, }, } +app_logger = { + 'handlers': ['app_info', 'console'], + 'level': 'DEBUG', + 'propagate': False +} +LOGGING["loggers"].update({app: deepcopy(app_logger) for app in LOCAL_APPS}) + REST_FRAMEWORK = { 'TEST_REQUEST_DEFAULT_FORMAT': 'json', 'DEFAULT_RENDERER_CLASSES': ( @@ -163,6 +173,7 @@ REST_FRAMEWORK = { REDIS_URL = "redis://%s:%s" % (REDIS_CONF["host"], REDIS_CONF["port"]) + def redis_config(db): def make_key(key, key_prefix, version): return key @@ -175,16 +186,14 @@ def redis_config(db): "KEY_FUNCTION": make_key } + CACHES = { "default": redis_config(db=1) } - - SESSION_ENGINE = "django.contrib.sessions.backends.cache" SESSION_CACHE_ALIAS = "default" - CELERY_RESULT_BACKEND = f"{REDIS_URL}/2" BROKER_URL = f"{REDIS_URL}/3" CELERY_TASK_SOFT_TIME_LIMIT = CELERY_TASK_TIME_LIMIT = 180 diff --git a/options/options.py b/options/options.py index b2d76f1..7d8b9a9 100644 --- a/options/options.py +++ b/options/options.py @@ -1,3 +1,4 @@ +import os from django.core.cache import cache from django.db import transaction, IntegrityError @@ -6,6 +7,11 @@ from utils.shortcuts import rand_str from .models import SysOptions as SysOptionsModel +def default_token(): + token = os.environ.get("JUDGE_SERVER_TOKEN") + return token if token else rand_str() + + class OptionKeys: website_base_url = "website_base_url" website_name = "website_name" @@ -25,7 +31,7 @@ class OptionDefaultValue: allow_register = True submission_list_show_all = True smtp_config = {} - judge_server_token = rand_str + judge_server_token = default_token class _SysOptionsMeta(type): diff --git a/problem/serializers.py b/problem/serializers.py index 47e9a53..671ed88 100644 --- a/problem/serializers.py +++ b/problem/serializers.py @@ -113,5 +113,5 @@ class ContestProblemSerializer(BaseProblemSerializer): class ContestProblemSafeSerializer(BaseProblemSerializer): class Meta: model = Problem - exclude = ("test_case_score", "test_case_id", "visible", "is_public", "difficulty" + exclude = ("test_case_score", "test_case_id", "visible", "is_public", "difficulty", "submission_number", "accepted_number", "statistic_info") diff --git a/problem/views/admin.py b/problem/views/admin.py index 00f46c4..1d26cbe 100644 --- a/problem/views/admin.py +++ b/problem/views/admin.py @@ -76,7 +76,7 @@ class TestCaseUploadAPI(CSRFExemptAPIView): content = zip_file.read(item).replace(b"\r\n", b"\n") size_cache[item] = len(content) if item.endswith(".out"): - md5_cache[item] = hashlib.md5(content).hexdigest() + md5_cache[item] = hashlib.md5(content.rstrip()).hexdigest() f.write(content) test_case_info = {"spj": spj, "test_cases": {}} diff --git a/submission/views/oj.py b/submission/views/oj.py index d29b383..e66aa5b 100644 --- a/submission/views/oj.py +++ b/submission/views/oj.py @@ -55,7 +55,7 @@ class SubmissionAPI(APIView): return self.error("The contest have ended") if contest.status == ContestStatus.CONTEST_NOT_START and not contest.is_contest_admin(request.user): return self.error("Contest have not started") - if not contest.problem_details_permission(): + if not contest.problem_details_permission(request.user): hide_id = True if data.get("captcha"): @@ -110,6 +110,9 @@ class SubmissionAPI(APIView): @validate_serializer(ShareSubmissionSerializer) @login_required def put(self, request): + """ + share submission + """ try: submission = Submission.objects.select_related("problem").get(id=request.data["id"]) except Submission.DoesNotExist: