remove contest type

This commit is contained in:
2026-05-26 23:10:27 -06:00
parent 6b81856bfd
commit 6ab2886f77
12 changed files with 69 additions and 213 deletions

View File

@@ -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

View File

@@ -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))

View File

@@ -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"),
]

View File

@@ -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)

View File

@@ -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()

View File

@@ -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,

View File

@@ -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)

View File

@@ -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"])

View File

@@ -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:

View File

@@ -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")

View File

@@ -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"]

View File

@@ -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"