diff --git a/account/decorators.py b/account/decorators.py index 2c90499..acb5387 100644 --- a/account/decorators.py +++ b/account/decorators.py @@ -3,7 +3,7 @@ import hashlib import inspect import time -from contest.models import Contest, ContestRuleType, ContestStatus, ContestType +from contest.models import Contest, ContestStatus, ContestType from problem.models import Problem from utils.api import APIError, JSONResponse from utils.constants import CONTEST_PASSWORD_SESSION_KEY @@ -139,11 +139,6 @@ def check_contest_permission(check_type="details"): if self.contest.status == ContestStatus.CONTEST_NOT_START and check_type != "details": return self.error("Contest has not started yet.") - # check does user have permission to get ranks, submissions in OI Contest - if self.contest.status == ContestStatus.CONTEST_UNDERWAY and self.contest.rule_type == ContestRuleType.OI: - if not self.contest.real_time_rank and (check_type == "ranks" or check_type == "submissions"): - return self.error(f"No permission to get {check_type}") - return func(*args, **kwargs) return _check_permission return decorator diff --git a/account/views/oj.py b/account/views/oj.py index 4975b3f..86b3135 100644 --- a/account/views/oj.py +++ b/account/views/oj.py @@ -20,7 +20,7 @@ from submission.models import JudgeStatus, Submission from utils.api import APIView, AsyncAPIView, CSRFExemptAPIView, validate_serializer from utils.async_helpers import async_cache_get, async_cache_set from utils.captcha import Captcha -from utils.constants import CacheKey, ContestRuleType +from utils.constants import CacheKey from utils.shortcuts import datetime2str, img2base64, rand_str from ..decorators import login_required @@ -425,24 +425,17 @@ class SessionManagementAPI(APIView): class UserRankAPI(AsyncAPIView): async def get(self, request): - rule_type = request.GET.get("rule") username = request.GET.get("username", "") try: n = int(request.GET.get("n", "0")) except ValueError: n = 0 - if rule_type not in ContestRuleType.values: - rule_type = ContestRuleType.ACM profiles = UserProfile.objects.filter( user__admin_type__in=[AdminType.REGULAR_USER, AdminType.ADMIN], user__is_disabled=False, user__username__icontains=username, - ).select_related("user") - if rule_type == ContestRuleType.ACM: - profiles = profiles.filter(accepted_number__gte=0).order_by("-accepted_number", "submission_number") - else: - profiles = profiles.filter(total_score__gt=0).order_by("-total_score") + ).select_related("user").filter(accepted_number__gte=0).order_by("-accepted_number", "submission_number") if n > 0: profiles = profiles[:n] return self.success(await self.async_paginate_data(request, profiles, RankInfoSerializer)) diff --git a/contest/migrations/0006_remove_oi_and_realtime.py b/contest/migrations/0006_remove_oi_and_realtime.py new file mode 100644 index 0000000..78a681e --- /dev/null +++ b/contest/migrations/0006_remove_oi_and_realtime.py @@ -0,0 +1,13 @@ +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("contest", "0005_alter_acmcontestrank_accepted_number_and_more"), + ] + + operations = [ + migrations.DeleteModel(name="OIContestRank"), + migrations.RemoveField(model_name="contest", name="real_time_rank"), + migrations.RemoveField(model_name="contest", name="rule_type"), + ] diff --git a/contest/models.py b/contest/models.py index 65a6c41..e8f0378 100644 --- a/contest/models.py +++ b/contest/models.py @@ -1,4 +1,3 @@ -from utils.constants import ContestRuleType # noqa from django.db import models from django.utils.timezone import now from utils.models import JSONField @@ -12,10 +11,7 @@ class Contest(models.Model): title = models.TextField() description = RichTextField() tag = models.TextField() - # show real time rank or cached rank - real_time_rank = models.BooleanField() password = models.TextField(null=True) - rule_type = models.TextField(choices=ContestRuleType.choices) start_time = models.DateTimeField() end_time = models.DateTimeField() create_time = models.DateTimeField(auto_now_add=True) @@ -45,7 +41,7 @@ class Contest(models.Model): # 是否有权查看problem 的一些统计信息 诸如submission_number, accepted_number 等 def problem_details_permission(self, user): - return self.rule_type == ContestRuleType.ACM or self.status == ContestStatus.CONTEST_ENDED or user.is_authenticated and user.is_contest_admin(self) or self.real_time_rank + return self.status == ContestStatus.CONTEST_ENDED or user.is_authenticated and user.is_contest_admin(self) class Meta: db_table = "contest" @@ -80,22 +76,6 @@ class ACMContestRank(AbstractContestRank): ] -class OIContestRank(AbstractContestRank): - total_score = models.IntegerField(default=0, db_default=0) - # {"23": 333} - # key is problem id, value is current score - submission_info = JSONField(default=dict, db_default=models.Value({}, output_field=models.JSONField())) - - class Meta: - db_table = "oi_contest_rank" - constraints = [ - models.UniqueConstraint(fields=["user", "contest"], name="unique_oi_rank_user_contest"), - ] - indexes = [ - models.Index(fields=["contest", "total_score"], name="oi_rank_order_idx"), - models.Index(fields=["contest", "user"], name="oi_rank_contest_user_idx"), - ] - class ContestAnnouncement(models.Model): contest = models.ForeignKey(Contest, on_delete=models.CASCADE) diff --git a/contest/serializers.py b/contest/serializers.py index cc4db05..dc4c255 100644 --- a/contest/serializers.py +++ b/contest/serializers.py @@ -1,6 +1,6 @@ from utils.api import UsernameSerializer, serializers -from .models import ACMContestRank, Contest, ContestAnnouncement, ContestRuleType, OIContestRank +from .models import ACMContestRank, Contest, ContestAnnouncement class CreateConetestSeriaizer(serializers.Serializer): @@ -9,10 +9,8 @@ class CreateConetestSeriaizer(serializers.Serializer): tag = serializers.CharField() start_time = serializers.DateTimeField() end_time = serializers.DateTimeField() - rule_type = serializers.ChoiceField(choices=ContestRuleType.choices) password = serializers.CharField(allow_blank=True, max_length=32) visible = serializers.BooleanField() - real_time_rank = serializers.BooleanField() allowed_ip_ranges = serializers.ListField(child=serializers.CharField(max_length=32), allow_empty=True) @@ -25,7 +23,6 @@ class EditConetestSeriaizer(serializers.Serializer): end_time = serializers.DateTimeField() password = serializers.CharField(allow_blank=True, allow_null=True, max_length=32) visible = serializers.BooleanField() - real_time_rank = serializers.BooleanField() allowed_ip_ranges = serializers.ListField(child=serializers.CharField(max_length=32)) @@ -87,20 +84,6 @@ class ACMContestRankSerializer(serializers.ModelSerializer): return UsernameSerializer(obj.user, need_real_name=self.is_contest_admin).data -class OIContestRankSerializer(serializers.ModelSerializer): - user = serializers.SerializerMethodField() - - class Meta: - model = OIContestRank - fields = "__all__" - - def __init__(self, *args, **kwargs): - self.is_contest_admin = kwargs.pop("is_contest_admin", False) - super().__init__(*args, **kwargs) - - def get_user(self, obj): - return UsernameSerializer(obj.user, need_real_name=self.is_contest_admin).data - class ACMContesHelperSerializer(serializers.Serializer): contest_id = serializers.IntegerField() diff --git a/contest/views/admin.py b/contest/views/admin.py index 6189830..d196223 100644 --- a/contest/views/admin.py +++ b/contest/views/admin.py @@ -13,8 +13,6 @@ from account.models import User from problem.models import Problem from submission.models import JudgeStatus, Submission from utils.api import APIView, validate_serializer -from utils.cache import cache -from utils.constants import CacheKey from utils.shortcuts import rand_str from utils.tasks import delete_files @@ -70,10 +68,6 @@ class ContestAPI(APIView): ip_network(ip_range, strict=False) except ValueError: return self.error(f"{ip_range} is not a valid cidr network") - if not contest.real_time_rank and data.get("real_time_rank"): - cache_key = f"{CacheKey.contest_rank_cache}:{contest.id}" - cache.delete(cache_key) - for k, v in data.items(): setattr(contest, k, v) contest.save() @@ -279,9 +273,7 @@ class ContestCloneAPI(APIView): title=original.title, description=original.description, tag=original.tag, - rule_type=original.rule_type, password=original.password, - real_time_rank=original.real_time_rank, visible=False, allowed_ip_ranges=original.allowed_ip_ranges, start_time=new_start, diff --git a/contest/views/oj.py b/contest/views/oj.py index 87781a9..da4c129 100644 --- a/contest/views/oj.py +++ b/contest/views/oj.py @@ -1,7 +1,6 @@ import io import xlsxwriter -from django.core.cache import cache from django.http import HttpResponse from django.utils.timezone import now @@ -13,11 +12,11 @@ from account.decorators import ( from account.models import AdminType from problem.models import Problem from utils.api import APIView, AsyncAPIView, validate_serializer -from utils.constants import CONTEST_PASSWORD_SESSION_KEY, CacheKey, ContestRuleType, ContestStatus +from utils.constants import CONTEST_PASSWORD_SESSION_KEY, ContestStatus from utils.shortcuts import check_is_id, datetime2str -from ..models import ACMContestRank, Contest, ContestAnnouncement, OIContestRank -from ..serializers import ACMContestRankSerializer, ContestAnnouncementSerializer, ContestPasswordVerifySerializer, ContestSerializer, OIContestRankSerializer +from ..models import ACMContestRank, Contest, ContestAnnouncement +from ..serializers import ACMContestRankSerializer, ContestAnnouncementSerializer, ContestPasswordVerifySerializer, ContestSerializer # DEPRECATED: 前端未调用 (2026-05-26) @@ -60,13 +59,10 @@ class ContestListAPI(AsyncAPIView): async def get(self, request): contests = Contest.objects.select_related("created_by").filter(visible=True) keyword = request.GET.get("keyword") - rule_type = request.GET.get("rule_type") status = request.GET.get("status") tag = request.GET.get("tag") if keyword: contests = contests.filter(title__icontains=keyword) - if rule_type: - contests = contests.filter(rule_type=rule_type) if tag: contests = contests.filter(tag=tag) if status: @@ -125,26 +121,15 @@ class ContestAccessAPI(APIView): class ContestRankAPI(APIView): def get_rank(self): - if self.contest.rule_type == ContestRuleType.ACM: - return ( - ACMContestRank.objects.filter( - contest=self.contest, - user__admin_type=AdminType.REGULAR_USER, - user__is_disabled=False, - ) - .select_related("user") - .order_by("-accepted_number", "total_time") - ) - else: - return ( - OIContestRank.objects.filter( - contest=self.contest, - user__admin_type=AdminType.REGULAR_USER, - user__is_disabled=False, - ) - .select_related("user") - .order_by("-total_score") + return ( + ACMContestRank.objects.filter( + contest=self.contest, + user__admin_type=AdminType.REGULAR_USER, + user__is_disabled=False, ) + .select_related("user") + .order_by("-accepted_number", "total_time") + ) def column_string(self, n): string = "" @@ -156,28 +141,15 @@ class ContestRankAPI(APIView): @check_contest_permission(check_type="ranks") def get(self, request): download_csv = request.GET.get("download_csv") - force_refresh = request.GET.get("force_refresh") is_contest_admin = ( request.user.is_authenticated and request.user.is_contest_admin(self.contest) ) - if self.contest.rule_type == ContestRuleType.OI: - serializer = OIContestRankSerializer - else: - serializer = ACMContestRankSerializer - # if force_refresh == "1" and is_contest_admin: - if force_refresh == "1": - qs = self.get_rank() - else: - cache_key = f"{CacheKey.contest_rank_cache}:{self.contest.id}" - qs = cache.get(cache_key) - if not qs: - qs = list(self.get_rank()) - cache.set(cache_key, qs) + qs = self.get_rank() if download_csv: - data = serializer(qs, many=True, is_contest_admin=is_contest_admin).data + data = ACMContestRankSerializer(qs, many=True, is_contest_admin=is_contest_admin).data contest_problems = list(Problem.objects.filter( contest=self.contest, visible=True ).order_by("_id")) @@ -190,41 +162,25 @@ class ContestRankAPI(APIView): worksheet.write("A1", "User ID") worksheet.write("B1", "Username") worksheet.write("C1", "Real Name") - if self.contest.rule_type == ContestRuleType.OI: - worksheet.write("D1", "Total Score") - for i, p in enumerate(contest_problems): - worksheet.write(self.column_string(5 + i) + "1", p.title) - for index, item in enumerate(data): - worksheet.write_string(index + 1, 0, str(item["user"]["id"])) - worksheet.write_string(index + 1, 1, item["user"]["username"]) - worksheet.write_string( - index + 1, 2, item["user"]["real_name"] or "" - ) - worksheet.write_string(index + 1, 3, str(item["total_score"])) - for k, v in item["submission_info"].items(): - worksheet.write_string( - index + 1, 4 + problem_id_to_col[int(k)], str(v) - ) - else: - worksheet.write("D1", "AC") - worksheet.write("E1", "Total Submission") - worksheet.write("F1", "Total Time") - for i, p in enumerate(contest_problems): - worksheet.write(self.column_string(7 + i) + "1", p.title) + worksheet.write("D1", "AC") + worksheet.write("E1", "Total Submission") + worksheet.write("F1", "Total Time") + for i, p in enumerate(contest_problems): + worksheet.write(self.column_string(7 + i) + "1", p.title) - for index, item in enumerate(data): - worksheet.write_string(index + 1, 0, str(item["user"]["id"])) - worksheet.write_string(index + 1, 1, item["user"]["username"]) + for index, item in enumerate(data): + worksheet.write_string(index + 1, 0, str(item["user"]["id"])) + worksheet.write_string(index + 1, 1, item["user"]["username"]) + worksheet.write_string( + index + 1, 2, item["user"]["real_name"] or "" + ) + worksheet.write_string(index + 1, 3, str(item["accepted_number"])) + worksheet.write_string(index + 1, 4, str(item["submission_number"])) + worksheet.write_string(index + 1, 5, str(item["total_time"])) + for k, v in item["submission_info"].items(): worksheet.write_string( - index + 1, 2, item["user"]["real_name"] or "" + index + 1, 6 + problem_id_to_col[int(k)], str(v["is_ac"]) ) - worksheet.write_string(index + 1, 3, str(item["accepted_number"])) - worksheet.write_string(index + 1, 4, str(item["submission_number"])) - worksheet.write_string(index + 1, 5, str(item["total_time"])) - for k, v in item["submission_info"].items(): - worksheet.write_string( - index + 1, 6 + problem_id_to_col[int(k)], str(v["is_ac"]) - ) workbook.close() f.seek(0) @@ -236,7 +192,7 @@ class ContestRankAPI(APIView): return response page_qs = self.paginate_data(request, qs) - page_qs["results"] = serializer( + page_qs["results"] = ACMContestRankSerializer( page_qs["results"], many=True, is_contest_admin=is_contest_admin ).data return self.success(page_qs) diff --git a/judge/dispatcher.py b/judge/dispatcher.py index e37cfed..f394ff5 100644 --- a/judge/dispatcher.py +++ b/judge/dispatcher.py @@ -11,7 +11,7 @@ from django.utils import timezone from account.models import User from conf.models import JudgeServer -from contest.models import ACMContestRank, ContestRuleType, ContestStatus, OIContestRank +from contest.models import ACMContestRank, ContestStatus from options.options import SysOptions from problem.models import Problem, ProblemRuleType from problem.utils import parse_problem_template @@ -350,30 +350,16 @@ class JudgeDispatcher(DispatcherBase): user_profile = user.userprofile problem_id = str(self.problem.id) profile_status = JudgeStatus.ACCEPTED if is_accepted(self.submission.result) else self.submission.result - if self.contest.rule_type == ContestRuleType.ACM: - contest_problems_status = user_profile.acm_problems_status.get("contest_problems", {}) - if problem_id not in contest_problems_status: - contest_problems_status[problem_id] = {"status": profile_status, "_id": self.problem._id} - elif not is_accepted(contest_problems_status[problem_id]["status"]): - contest_problems_status[problem_id]["status"] = profile_status - else: - # 如果已AC, 直接跳过 不计入任何计数器 - return - user_profile.acm_problems_status["contest_problems"] = contest_problems_status - user_profile.save(update_fields=["acm_problems_status"]) - - elif self.contest.rule_type == ContestRuleType.OI: - contest_problems_status = user_profile.oi_problems_status.get("contest_problems", {}) - score = self.submission.statistic_info["score"] - if problem_id not in contest_problems_status: - contest_problems_status[problem_id] = {"status": profile_status, - "_id": self.problem._id, - "score": score} - else: - contest_problems_status[problem_id]["score"] = score - contest_problems_status[problem_id]["status"] = profile_status - user_profile.oi_problems_status["contest_problems"] = contest_problems_status - user_profile.save(update_fields=["oi_problems_status"]) + contest_problems_status = user_profile.acm_problems_status.get("contest_problems", {}) + if problem_id not in contest_problems_status: + contest_problems_status[problem_id] = {"status": profile_status, "_id": self.problem._id} + elif not is_accepted(contest_problems_status[problem_id]["status"]): + contest_problems_status[problem_id]["status"] = profile_status + else: + # 如果已AC, 直接跳过 不计入任何计数器 + return + user_profile.acm_problems_status["contest_problems"] = contest_problems_status + user_profile.save(update_fields=["acm_problems_status"]) problem = Problem.objects.select_for_update().get(contest_id=self.contest_id, id=self.problem.id) result = str(self.submission.result) @@ -385,28 +371,18 @@ class JudgeDispatcher(DispatcherBase): problem.save(update_fields=["submission_number", "accepted_number", "statistic_info"]) def update_contest_rank(self): - if self.contest.rule_type == ContestRuleType.OI or self.contest.real_time_rank: - cache.delete(f"{CacheKey.contest_rank_cache}:{self.contest.id}") - - def get_rank(model): - return model.objects.select_for_update().get(user_id=self.submission.user_id, contest=self.contest) - - if self.contest.rule_type == ContestRuleType.ACM: - model = ACMContestRank - func = self._update_acm_contest_rank - else: - model = OIContestRank - func = self._update_oi_contest_rank + def get_rank(): + return ACMContestRank.objects.select_for_update().get(user_id=self.submission.user_id, contest=self.contest) try: - rank = get_rank(model) - except model.DoesNotExist: + rank = get_rank() + except ACMContestRank.DoesNotExist: try: - model.objects.create(user_id=self.submission.user_id, contest=self.contest) - rank = get_rank(model) + ACMContestRank.objects.create(user_id=self.submission.user_id, contest=self.contest) + rank = get_rank() except IntegrityError: - rank = get_rank(model) - func(rank) + rank = get_rank() + self._update_acm_contest_rank(rank) def _update_acm_contest_rank(self, rank): info = rank.submission_info.get(str(self.submission.problem_id)) @@ -447,13 +423,3 @@ class JudgeDispatcher(DispatcherBase): rank.submission_info[str(self.submission.problem_id)] = info rank.save(update_fields=["submission_info", "total_time", "accepted_number", "submission_number"]) - def _update_oi_contest_rank(self, rank): - problem_id = str(self.submission.problem_id) - current_score = self.submission.statistic_info["score"] - 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(update_fields=["submission_info", "total_score", "submission_number"]) diff --git a/problem/views/admin.py b/problem/views/admin.py index 66832f4..04c4d43 100644 --- a/problem/views/admin.py +++ b/problem/views/admin.py @@ -292,9 +292,6 @@ class ContestProblemAPI(ProblemBase): except Contest.DoesNotExist: return self.error("Contest does not exist") - if data["rule_type"] != contest.rule_type: - return self.error("Invalid rule type") - _id = data["_id"] if not _id: return self.error("Display ID is required") @@ -360,9 +357,6 @@ class ContestProblemAPI(ProblemBase): except Contest.DoesNotExist: return self.error("Contest does not exist") - if data["rule_type"] != contest.rule_type: - return self.error("Invalid rule type") - problem_id = data.pop("id") try: diff --git a/problem/views/oj.py b/problem/views/oj.py index cfa4896..8d01d6e 100644 --- a/problem/views/oj.py +++ b/problem/views/oj.py @@ -6,7 +6,6 @@ from django.utils import timezone from account.decorators import check_contest_permission from account.models import User -from contest.models import ContestRuleType from submission.models import JudgeStatus, Submission from utils.api import APIView, AsyncAPIView from utils.async_helpers import async_cache_get, async_cache_set @@ -149,10 +148,7 @@ class ContestProblemAPI(APIView): def _add_problem_status(self, request, queryset_values): if request.user.is_authenticated: profile = request.user.userprofile - if self.contest.rule_type == ContestRuleType.ACM: - problems_status = profile.acm_problems_status.get("contest_problems", {}) - else: - problems_status = profile.oi_problems_status.get("contest_problems", {}) + problems_status = profile.acm_problems_status.get("contest_problems", {}) for problem in queryset_values: problem["my_status"] = problems_status.get(str(problem["id"]), {}).get("status") diff --git a/submission/views/oj.py b/submission/views/oj.py index f1c03d7..385bb88 100644 --- a/submission/views/oj.py +++ b/submission/views/oj.py @@ -4,7 +4,7 @@ from asgiref.sync import sync_to_async from django.utils import timezone from account.decorators import check_contest_permission, login_required -from contest.models import ContestRuleType, ContestStatus +from contest.models import ContestStatus from judge.tasks import judge_task from options.options import SysOptions @@ -249,12 +249,6 @@ class ContestSubmissionListAPI(APIView): if contest.status != ContestStatus.CONTEST_NOT_START: submissions = submissions.filter(create_time__gte=contest.start_time) - # 封榜的时候只能看到自己的提交 - if contest.rule_type == ContestRuleType.ACM: - if not contest.real_time_rank and not request.user.is_contest_admin( - contest - ): - submissions = submissions.filter(user_id=request.user.id) data = self.paginate_data(request, submissions) results = data["results"] diff --git a/utils/constants.py b/utils/constants.py index 8d7cc5f..622826c 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -12,14 +12,8 @@ class ContestStatus(models.TextChoices): CONTEST_UNDERWAY = "0", "Underway" -class ContestRuleType(models.TextChoices): - ACM = "ACM", "ACM" - OI = "OI", "OI" - - class CacheKey: waiting_queue = "waiting_queue" - contest_rank_cache = "contest_rank_cache" website_config = "website_config" problem_authors = "problem_authors" problem_tags = "problem_tags"