完成ACM ContestProblem相关判题逻辑

contest,submission等表默认-create_time排序
This commit is contained in:
zemal 2017-08-01 16:52:48 +08:00
parent 17432b4c81
commit 14b850c652
13 changed files with 224 additions and 73 deletions

View File

@ -92,9 +92,5 @@ class UserProfile(models.Model):
self.submission_number = models.F("submission_number") + 1
self.save()
def minus_accepted_problem_number(self):
self.accepted_problem_number = models.F("accepted_problem_number") - 1
self.save()
class Meta:
db_table = "user_profile"

View File

@ -1,7 +1,7 @@
from django.conf.urls import url
from ..views.user import (SSOAPI, AvatarUploadAPI, TwoFactorAuthAPI,
UserNameAPI, UserProfileAPI)
UserProfileAPI)
urlpatterns = [
# url(r"^username/?$", UserNameAPI.as_view(), name="user_name_api"),

View File

@ -14,6 +14,10 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='contest',
options={'ordering': ('create_time',)},
options={'ordering': ('-create_time',)},
),
migrations.AlterModelOptions(
name='contestannouncement',
options={'ordering': ('-create_time',)},
),
]

View File

@ -58,7 +58,7 @@ class Contest(models.Model):
class Meta:
db_table = "contest"
ordering = ("create_time",)
ordering = ("-create_time",)
class ContestRank(models.Model):
@ -91,6 +91,9 @@ class OIContestRank(ContestRank):
class Meta:
db_table = "oi_contest_rank"
def update_rank(self, submission):
self.total_submission_number += 1
class ContestAnnouncement(models.Model):
contest = models.ForeignKey(Contest)
@ -101,3 +104,4 @@ class ContestAnnouncement(models.Model):
class Meta:
db_table = "contest_announcement"
ordering = ("-create_time",)

View File

@ -1,6 +1,7 @@
from utils.api import DateTimeTZField, UsernameSerializer, serializers
from .models import Contest, ContestAnnouncement, ContestRuleType
from .models import ACMContestRank, OIContestRank
class CreateConetestSeriaizer(serializers.Serializer):
@ -61,3 +62,19 @@ class CreateContestAnnouncementSerializer(serializers.Serializer):
class ContestPasswordVerifySerializer(serializers.Serializer):
contest_id = serializers.IntegerField()
password = serializers.CharField(max_length=30, required=True)
class ACMContestRankSerializer(serializers.ModelSerializer):
user = UsernameSerializer()
submission_info = serializers.JSONField()
class Meta:
model = ACMContestRank
class OIContestRankSerializer(serializers.ModelSerializer):
user = UsernameSerializer()
submission_info = serializers.JSONField()
class Meta:
model = OIContestRank

View File

@ -1,9 +1,14 @@
from django.utils.timezone import now
from django.db.models import Q
from django.core.cache import cache
from utils.api import APIView, validate_serializer
from account.decorators import login_required
from account.decorators import login_required, check_contest_permission
from ..models import ContestAnnouncement, Contest, ContestStatus
from ..models import ContestAnnouncement, Contest, ContestStatus, ContestRuleType
from ..models import OIContestRank, ACMContestRank
from ..serializers import ContestAnnouncementSerializer
from ..serializers import ContestSerializer, ContestPasswordVerifySerializer
from ..serializers import OIContestRankSerializer, ACMContestRankSerializer
class ContestAnnouncementListAPI(APIView):
@ -11,7 +16,7 @@ class ContestAnnouncementListAPI(APIView):
contest_id = request.GET.get("contest_id")
if not contest_id:
return self.error("Invalid parameter")
data = ContestAnnouncement.objects.filter(contest_id=contest_id).order_by("-create_time")
data = ContestAnnouncement.objects.filter(contest_id=contest_id)
max_id = request.GET.get("max_id")
if max_id:
data = data.filter(id__gt=max_id)
@ -30,8 +35,20 @@ class ContestAPI(APIView):
contests = Contest.objects.filter(visible=True)
keyword = request.GET.get("keyword")
rule_type = request.GET.get("rule_type")
status = request.GET.get("status")
if keyword:
contests = contests.filter(title__contains=keyword)
if rule_type:
contests = contests.filter(rule_type=rule_type)
if status:
cur = now()
if status == ContestStatus.CONTEST_NOT_START:
contests = contests.filter(start_time__gt=cur)
elif status == ContestStatus.CONTEST_ENDED:
contests = contests.filter(end_time__lt=cur)
else:
contests = contests.filter(Q(start_time__lte=cur) & Q(end_time__gte=cur))
return self.success(self.paginate_data(request, contests, ContestSerializer))
@ -68,3 +85,24 @@ class ContestAccessAPI(APIView):
return self.success({"Access": True})
else:
return self.success({"Access": False})
class ContestRankAPI(APIView):
def get_rank(self):
if self.contest.contest_type == ContestRuleType.ACM:
rank = ACMContestRank.objects.filter(contest=self.contest). \
select_related("user").order_by("-total_ac_number", "total_time")
return ACMContestRankSerializer(rank, many=True).data
else:
rank = OIContestRank.objects.filter(contest=self.contest). \
select_related("user").order_by("-total_score")
return OIContestRankSerializer(rank, many=True).data
@check_contest_permission
def get(self, request):
cache_key = str(self.contest.id) + "_rank_cache"
rank = cache.get(cache_key)
if not rank:
rank = self.get_rank()
cache.set(cache_key, rank)
return self.success(rank)

View File

@ -7,11 +7,13 @@ from urllib.parse import urljoin
from django.db import transaction
from django.db.models import F
from django_redis import get_redis_connection
from django.core.cache import cache
from judge.languages import languages
from account.models import User
from conf.models import JudgeServer, JudgeServerToken
from problem.models import Problem, ProblemRuleType
from problem.models import Problem, ProblemRuleType, ContestProblem
from contest.models import ContestRuleType, ACMContestRank, OIContestRank
from submission.models import JudgeStatus, Submission
logger = logging.getLogger(__name__)
@ -33,8 +35,13 @@ class JudgeDispatcher(object):
token = JudgeServerToken.objects.first().token
self.token = hashlib.sha256(token.encode("utf-8")).hexdigest()
self.redis_conn = get_redis_connection("JudgeQueue")
self.submission_obj = Submission.objects.get(pk=submission_id)
self.problem_obj = Problem.objects.get(pk=problem_id)
self.submission = Submission.objects.get(pk=submission_id)
if self.submission.contest_id:
self.problem = ContestProblem.objects.select_related("contest")\
.get(_id=problem_id, contest_id=self.submission.contest_id)
self.contest = self.problem.contest
else:
self.problem = Problem.objects.get(pk=problem_id)
def _request(self, url, data=None):
kwargs = {"headers": {"X-Judge-Server-Token": self.token,
@ -69,59 +76,60 @@ class JudgeDispatcher(object):
def judge(self, output=False):
server = self.choose_judge_server()
if not server:
data = {"submission_id": self.submission_obj.id, "problem_id": self.problem_obj.id}
data = {"submission_id": self.submission.id, "problem_id": self.problem.id}
self.redis_conn.lpush(WAITING_QUEUE, json.dumps(data))
return
sub_config = list(filter(lambda item: self.submission_obj.language == item["name"], languages))[0]
sub_config = list(filter(lambda item: self.submission.language == item["name"], languages))[0]
spj_config = {}
if self.problem_obj.spj_code:
if self.problem.spj_code:
for lang in languages:
if lang["name"] == self.problem_obj.spj_language:
if lang["name"] == self.problem.spj_language:
spj_config = lang["spj"]
break
data = {
"language_config": sub_config["config"],
"src": self.submission_obj.code,
"max_cpu_time": self.problem_obj.time_limit,
"max_memory": 1024 * 1024 * self.problem_obj.memory_limit,
"test_case_id": self.problem_obj.test_case_id,
"src": self.submission.code,
"max_cpu_time": self.problem.time_limit,
"max_memory": 1024 * 1024 * self.problem.memory_limit,
"test_case_id": self.problem.test_case_id,
"output": output,
"spj_version": self.problem_obj.spj_version,
"spj_version": self.problem.spj_version,
"spj_config": spj_config.get("config"),
"spj_compile_config": spj_config.get("compile"),
"spj_src": self.problem_obj.spj_code
"spj_src": self.problem.spj_code
}
self.submission_obj.result = JudgeStatus.JUDGING
self.submission_obj.save()
self.submission.result = JudgeStatus.JUDGING
self.submission.save()
# TODO: try catch
resp = self._request(urljoin(server.service_url, "/judge"), data=data)
self.submission_obj.info = resp
self.submission.info = resp
if resp["err"]:
self.submission_obj.result = JudgeStatus.COMPILE_ERROR
self.submission_obj.statistic_info["err_info"] = resp["data"]
self.submission.result = JudgeStatus.COMPILE_ERROR
self.submission.statistic_info["err_info"] = resp["data"]
else:
# 用时和内存占用保存为多个测试点中最长的那个
self.submission_obj.statistic_info["time_cost"] = max([x["cpu_time"] for x in resp["data"]])
self.submission_obj.statistic_info["memory_cost"] = max([x["memory"] for x in resp["data"]])
self.submission.statistic_info["time_cost"] = max([x["cpu_time"] for x in resp["data"]])
self.submission.statistic_info["memory_cost"] = max([x["memory"] for x in resp["data"]])
error_test_case = list(filter(lambda case: case["result"] != 0, resp["data"]))
# 多个测试点全部正确则AC否则 ACM模式下取第一个错误的测试点的状态, OI模式若全部错误则取第一个错误测试点状态否则为部分正确
if not error_test_case:
self.submission_obj.result = JudgeStatus.ACCEPTED
elif self.problem_obj.rule_type == ProblemRuleType.ACM or len(error_test_case) == len(resp["data"]):
self.submission_obj.result = error_test_case[0]["result"]
self.submission.result = JudgeStatus.ACCEPTED
elif self.problem.rule_type == ProblemRuleType.ACM or len(error_test_case) == len(resp["data"]):
self.submission.result = error_test_case[0]["result"]
else:
self.submission_obj.result = JudgeStatus.PARTIALLY_ACCEPTED
self.submission_obj.save()
self.submission.result = JudgeStatus.PARTIALLY_ACCEPTED
self.submission.save()
self.release_judge_res(server.id)
if self.submission_obj.contest_id:
# ToDo: update contest status
pass
if self.submission.contest_id:
self.update_contest_problem_status()
self.update_contest_rank()
else:
self.update_problem_status()
# 至此判题结束,尝试处理任务队列中剩余的任务
process_pending_task(self.redis_conn)
def compile_spj(self, service_url, src, spj_version, spj_compile_config, test_case_id):
@ -132,26 +140,88 @@ class JudgeDispatcher(object):
def update_problem_status(self):
with transaction.atomic():
problem = Problem.objects.select_for_update().get(id=self.problem_obj.id)
user = User.objects.select_for_update().get(id=self.submission_obj.user_id)
# 更新提交计数器
problem.add_submission_number()
# 更新problem计数器
self.problem = Problem.objects.select_for_update().get(id=self.problem.id)
self.problem.add_submission_number()
if self.submission.result == JudgeStatus.ACCEPTED:
self.problem.add_ac_number()
# 更新user profile
user = User.objects.select_for_update().get(id=self.submission.user_id)
user_profile = user.userprofile
user_profile.add_submission_number()
if self.submission_obj.result == JudgeStatus.ACCEPTED:
problem.add_ac_number()
problems_status = user_profile.problems_status
if "problems" not in problems_status:
problems_status["problems"] = {}
# 之前状态不是ac, 现在是ac了 需要更新用户ac题目数量计数器,这里需要判重
if problems_status["problems"].get(str(problem.id), JudgeStatus.WRONG_ANSWER) != JudgeStatus.ACCEPTED:
if self.submission_obj.result == JudgeStatus.ACCEPTED:
if problems_status["problems"].get(str(self.problem.id), JudgeStatus.WRONG_ANSWER) != JudgeStatus.ACCEPTED:
if self.submission.result == JudgeStatus.ACCEPTED:
user_profile.add_accepted_problem_number()
problems_status["problems"][str(problem.id)] = JudgeStatus.ACCEPTED
problems_status["problems"][str(self.problem.id)] = JudgeStatus.ACCEPTED
else:
problems_status["problems"][str(problem.id)] = JudgeStatus.WRONG_ANSWER
problems_status["problems"][str(self.problem.id)] = JudgeStatus.WRONG_ANSWER
user_profile.problems_status = problems_status
user_profile.save(update_fields=["problems_status"])
def update_contest_problem_status(self):
with transaction.atomic():
problem = ContestProblem.objects.select_for_update().get(id=self.problem.id)
problem.add_submission_number()
if self.submission.result == JudgeStatus.ACCEPTED:
problem.add_ac_number()
def update_contest_rank(self):
if self.contest.real_time_rank:
cache.delete(str(self.contest.id) + "_rank_cache")
with transaction.atomic():
if self.contest.rule_type == ContestRuleType.ACM:
acm_rank, _ = ACMContestRank.objects.select_for_update(). \
get_or_create(user_id=self.submission.user_id, contest=self.contest)
self._update_acm_contest_rank(acm_rank)
else:
oi_rank, _ = OIContestRank.objects.select_for_update(). \
get_or_create(user_id=self.submission.user_id, contest=self.contest)
self._update_oi_contest_rank(oi_rank)
def _update_acm_contest_rank(self, rank):
info = rank.submission_info.get(str(self.submission.problem_id))
# 因前面更改过,这里需要重新获取
problem = ContestProblem.objects.get(contest_id=self.contest.id, _id=self.problem._id)
# 此题提交过
if info:
if info["is_ac"]:
return
rank.total_submission_number += 1
if self.submission.result == JudgeStatus.ACCEPTED:
rank.total_ac_number += 1
info["is_ac"] = True
info["ac_time"] = (self.submission.create_time - self.contest.start_time).total_seconds()
rank.total_time += info["ac_time"] + info["error_number"] * 20 * 60
if problem.total_accepted_number == 1:
info["is_first_ac"] = True
else:
info["error_number"] += 1
# 第一次提交
else:
rank.total_submission_number += 1
info = {"is_ac": False, "ac_time": 0, "error_number": 0, "is_first_ac": False}
if self.submission.result == JudgeStatus.ACCEPTED:
rank.total_ac_number += 1
info["is_ac"] = True
info["ac_time"] = (self.submission.create_time - self.contest.start_time).total_seconds()
rank.total_time += info["ac_time"]
if problem.total_accepted_number == 1:
info["is_first_ac"] = True
else:
info["error_number"] = 1
rank.submission_info[str(self.submission.problem_id)] = info
rank.save()
def _update_oi_contest_rank(self, rank):
pass

View File

@ -12,8 +12,13 @@ class Migration(migrations.Migration):
]
operations = [
migrations.RenameField(
model_name='submission',
old_name='created_time',
new_name='create_time',
),
migrations.AlterModelOptions(
name='submission',
options={'ordering': ('-created_time',)},
),
options={'ordering': ('-create_time',)},
)
]

View File

@ -23,7 +23,7 @@ class Submission(models.Model):
id = models.CharField(max_length=32, default=rand_str, primary_key=True, db_index=True)
contest_id = models.IntegerField(db_index=True, null=True)
problem_id = models.IntegerField(db_index=True)
created_time = models.DateTimeField(auto_now_add=True)
create_time = models.DateTimeField(auto_now_add=True)
user_id = models.IntegerField(db_index=True)
code = models.TextField()
result = models.IntegerField(default=JudgeStatus.PENDING)
@ -42,7 +42,7 @@ class Submission(models.Model):
class Meta:
db_table = "submission"
ordering = ("-created_time",)
ordering = ("-create_time",)
def __str__(self):
return self.id

View File

@ -8,6 +8,7 @@ class CreateSubmissionSerializer(serializers.Serializer):
problem_id = serializers.IntegerField()
language = serializers.ChoiceField(choices=language_names)
code = serializers.CharField(max_length=20000)
contest_id = serializers.IntegerField(required=False)
class SubmissionModelSerializer(serializers.ModelSerializer):
@ -32,7 +33,7 @@ class SubmissionSafeSerializer(serializers.ModelSerializer):
return User.objects.get(id=obj.user_id).username
class SubmissionListSerializer(SubmissionSafeSerializer):
class SubmissionListSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField()
statistic_info = serializers.JSONField()
show_link = serializers.SerializerMethodField()

View File

@ -1,8 +1,9 @@
from django.conf.urls import url
from ..views.oj import SubmissionAPI, SubmissionListAPI
from ..views.oj import SubmissionAPI, SubmissionListAPI, ContestSubmissionListAPI
urlpatterns = [
url(r"^submission/?$", SubmissionAPI.as_view(), name="submission_api"),
url(r"^submissions/?$", SubmissionListAPI.as_view(), name="submission_list_api"),
url(r"^contest/submissions/?$", ContestSubmissionListAPI.as_view(), name="contest_submission_list_api"),
]

View File

@ -1,17 +1,19 @@
from django_redis import get_redis_connection
from account.decorators import login_required
from problem.models import Problem, ProblemRuleType
from account.decorators import login_required, check_contest_permission
from problem.models import Problem, ProblemRuleType, ContestProblem
from submission.tasks import judge_task
# from judge.dispatcher import JudgeDispatcher
from utils.api import APIView, validate_serializer
from utils.throttling import TokenBucket, BucketController
from ..models import Submission
from ..serializers import CreateSubmissionSerializer, SubmissionModelSerializer
from ..serializers import SubmissionSafeSerializer, SubmissionListSerializer
from utils.api import APIView, validate_serializer
from utils.throttling import TokenBucket, BucketController
def _submit(response, user, problem_id, language, code, contest_id=None):
def _submit(response, user, problem_id, language, code, contest_id):
# TODO: 预设默认值,需修改
controller = BucketController(user_id=user.id,
redis_conn=get_redis_connection("Throttling"),
@ -24,9 +26,11 @@ def _submit(response, user, problem_id, language, code, contest_id=None):
controller.last_capacity -= 1
else:
return response.error("Please wait %d seconds" % int(bucket.expected_time() + 1))
try:
problem = Problem.objects.get(_id=problem_id)
if contest_id:
problem = ContestProblem.objects.get(_id=problem_id, visible=True)
else:
problem = Problem.objects.get(_id=problem_id, visible=True)
except Problem.DoesNotExist:
return response.error("Problem not exist")
@ -35,9 +39,9 @@ def _submit(response, user, problem_id, language, code, contest_id=None):
code=code,
problem_id=problem._id,
contest_id=contest_id)
# todo 暂时保留 方便排错
# JudgeDispatcher(submission.id, problem.id).judge()
judge_task.delay(submission.id, problem.id)
# use this for debug
# JudgeDispatcher(submission.id, problem._id).judge()
judge_task.delay(submission.id, problem._id)
return response.success({"submission_id": submission.id})
@ -46,7 +50,7 @@ class SubmissionAPI(APIView):
@login_required
def post(self, request):
data = request.data
return _submit(self, request.user, data["problem_id"], data["language"], data["code"])
return _submit(self, request.user, data["problem_id"], data["language"], data["code"], data.get("contest_id"))
@login_required
def get(self, request):
@ -71,10 +75,6 @@ class SubmissionAPI(APIView):
class SubmissionListAPI(APIView):
def get(self, request):
contest_id = request.GET.get("contest_id")
if contest_id:
subs = Submission.objects.filter(contest_id=contest_id)
else:
subs = Submission.objects.filter(contest_id__isnull=True)
problem_id = request.GET.get("problem_id")
@ -86,3 +86,18 @@ class SubmissionListAPI(APIView):
data = self.paginate_data(request, subs)
data["results"] = SubmissionListSerializer(data["results"], many=True, user=request.user).data
return self.success(data)
class ContestSubmissionListAPI(APIView):
@check_contest_permission
def get(self, request):
subs = Submission.objects.filter(contest_id=self.contest.id)
problem_id = request.GET.get("problem_id")
if problem_id:
subs = subs.filter(problem_id=problem_id)
if request.GET.get("myself") and request.GET["myself"] == "1":
subs = subs.filter(user_id=request.user.id)
data = self.paginate_data(request, subs)
data["results"] = SubmissionListSerializer(data["results"], many=True, user=request.user).data
return self.success(data)

View File

@ -130,7 +130,7 @@ class APIView(View):
count = query_set.count()
results = object_serializer(results, many=True).data
else:
count = len(query_set)
count = query_set.count()
data = {"results": results,
"total": count}
return data