update for django6

This commit is contained in:
2026-05-09 05:57:30 -06:00
parent b297eb5475
commit e4e8b7759d
12 changed files with 284 additions and 69 deletions

View File

@@ -0,0 +1,58 @@
# Generated by Django 6.0.4 on 2026-05-09 11:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0004_alter_user_admin_type_alter_user_problem_permission'),
]
operations = [
migrations.AlterField(
model_name='user',
name='is_disabled',
field=models.BooleanField(db_default=False, default=False),
),
migrations.AlterField(
model_name='user',
name='open_api',
field=models.BooleanField(db_default=False, default=False),
),
migrations.AlterField(
model_name='user',
name='session_keys',
field=models.JSONField(db_default=models.Value([]), default=list),
),
migrations.AlterField(
model_name='user',
name='two_factor_auth',
field=models.BooleanField(db_default=False, default=False),
),
migrations.AlterField(
model_name='userprofile',
name='accepted_number',
field=models.IntegerField(db_default=0, default=0),
),
migrations.AlterField(
model_name='userprofile',
name='acm_problems_status',
field=models.JSONField(db_default=models.Value({}), default=dict),
),
migrations.AlterField(
model_name='userprofile',
name='oi_problems_status',
field=models.JSONField(db_default=models.Value({}), default=dict),
),
migrations.AlterField(
model_name='userprofile',
name='submission_number',
field=models.IntegerField(db_default=0, default=0),
),
migrations.AlterField(
model_name='userprofile',
name='total_score',
field=models.BigIntegerField(db_default=0, default=0),
),
]

View File

@@ -36,13 +36,13 @@ class User(AbstractBaseUser):
reset_password_token_expire_time = models.DateTimeField(null=True)
# SSO auth token
auth_token = models.TextField(null=True)
two_factor_auth = models.BooleanField(default=False)
two_factor_auth = models.BooleanField(default=False, db_default=False)
tfa_token = models.TextField(null=True)
session_keys = JSONField(default=list)
session_keys = JSONField(default=list, db_default=models.Value([]))
# open api key
open_api = models.BooleanField(default=False)
open_api = models.BooleanField(default=False, db_default=False)
open_api_appkey = models.TextField(null=True)
is_disabled = models.BooleanField(default=False)
is_disabled = models.BooleanField(default=False, db_default=False)
raw_password = models.CharField(max_length=20, null=True, blank=True, verbose_name="明文密码")
USERNAME_FIELD = "username"
@@ -93,9 +93,9 @@ class UserProfile(models.Model):
# }
# }
# }
acm_problems_status = JSONField(default=dict)
acm_problems_status = JSONField(default=dict, db_default=models.Value({}))
# like acm_problems_status, merely add "score" field
oi_problems_status = JSONField(default=dict)
oi_problems_status = JSONField(default=dict, db_default=models.Value({}))
real_name = models.TextField(null=True)
avatar = models.TextField(default=f"{settings.AVATAR_URI_PREFIX}/default.png")
@@ -106,24 +106,24 @@ class UserProfile(models.Model):
major = models.TextField(null=True)
language = models.TextField(null=True)
# for ACM
accepted_number = models.IntegerField(default=0)
accepted_number = models.IntegerField(default=0, db_default=0)
# for OI
total_score = models.BigIntegerField(default=0)
submission_number = models.IntegerField(default=0)
total_score = models.BigIntegerField(default=0, db_default=0)
submission_number = models.IntegerField(default=0, db_default=0)
def add_accepted_problem_number(self):
self.accepted_number = models.F("accepted_number") + 1
self.save()
self.save(update_fields=["accepted_number"])
def add_submission_number(self):
self.submission_number = models.F("submission_number") + 1
self.save()
self.save(update_fields=["submission_number"])
# 计算总分时, 应先减掉上次该题所得分数, 然后再加上本次所得分数
def add_score(self, this_time_score, last_time_score=None):
last_time_score = last_time_score or 0
self.total_score = models.F("total_score") - last_time_score + this_time_score
self.save()
self.save(update_fields=["total_score"])
class Meta:
db_table = "user_profile"

View File

@@ -0,0 +1,48 @@
# Generated by Django 6.0.4 on 2026-05-09 11:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contest', '0004_alter_acmcontestrank_unique_together_and_more'),
]
operations = [
migrations.AlterField(
model_name='acmcontestrank',
name='accepted_number',
field=models.IntegerField(db_default=0, default=0),
),
migrations.AlterField(
model_name='acmcontestrank',
name='submission_info',
field=models.JSONField(db_default=models.Value({}), default=dict),
),
migrations.AlterField(
model_name='acmcontestrank',
name='submission_number',
field=models.IntegerField(db_default=0, default=0),
),
migrations.AlterField(
model_name='acmcontestrank',
name='total_time',
field=models.IntegerField(db_default=0, default=0),
),
migrations.AlterField(
model_name='oicontestrank',
name='submission_info',
field=models.JSONField(db_default=models.Value({}), default=dict),
),
migrations.AlterField(
model_name='oicontestrank',
name='submission_number',
field=models.IntegerField(db_default=0, default=0),
),
migrations.AlterField(
model_name='oicontestrank',
name='total_score',
field=models.IntegerField(db_default=0, default=0),
),
]

View File

@@ -55,19 +55,19 @@ class Contest(models.Model):
class AbstractContestRank(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
contest = models.ForeignKey(Contest, on_delete=models.CASCADE)
submission_number = models.IntegerField(default=0)
submission_number = models.IntegerField(default=0, db_default=0)
class Meta:
abstract = True
class ACMContestRank(AbstractContestRank):
accepted_number = models.IntegerField(default=0)
accepted_number = models.IntegerField(default=0, db_default=0)
# total_time is only for ACM contest, total_time = ac time + none-ac times * 20 * 60
total_time = models.IntegerField(default=0)
total_time = models.IntegerField(default=0, db_default=0)
# {"23": {"is_ac": True, "ac_time": 8999, "error_number": 2, "is_first_ac": True}}
# key is problem id
submission_info = JSONField(default=dict)
submission_info = JSONField(default=dict, db_default=models.Value({}))
class Meta:
db_table = "acm_contest_rank"
@@ -81,10 +81,10 @@ class ACMContestRank(AbstractContestRank):
class OIContestRank(AbstractContestRank):
total_score = models.IntegerField(default=0)
total_score = models.IntegerField(default=0, db_default=0)
# {"23": 333}
# key is problem id, value is current score
submission_info = JSONField(default=dict)
submission_info = JSONField(default=dict, db_default=models.Value({}))
class Meta:
db_table = "oi_contest_rank"

View File

@@ -1,11 +1,13 @@
import hashlib
import json
import logging
from datetime import timedelta
from urllib.parse import urljoin
import requests
from django.db import IntegrityError, transaction
from django.db.models import F
from django.utils import timezone
from account.models import User
from conf.models import JudgeServer
@@ -38,14 +40,23 @@ class ChooseJudgeServer:
def __enter__(self) -> [JudgeServer, None]:
with transaction.atomic():
servers = JudgeServer.objects.select_for_update().filter(is_disabled=False).order_by("task_number")
servers = [s for s in servers if s.status == "normal"]
for server in servers:
if server.task_number <= server.cpu_core * 2:
server.task_number = F("task_number") + 1
server.save(update_fields=["task_number"])
self.server = server
return server
cutoff = timezone.now() - timedelta(seconds=6)
server = (
JudgeServer.objects
.select_for_update(skip_locked=True)
.filter(
is_disabled=False,
last_heartbeat__gte=cutoff,
task_number__lte=F("cpu_core") * 2,
)
.order_by("task_number")
.first()
)
if server:
server.task_number = F("task_number") + 1
server.save(update_fields=["task_number"])
self.server = server
return server
return None
def __exit__(self, exc_type, exc_val, exc_tb):
@@ -196,7 +207,7 @@ class JudgeDispatcher(DispatcherBase):
self.submission.result = error_test_case[0]["result"]
else:
self.submission.result = JudgeStatus.PARTIALLY_ACCEPTED
self.submission.save()
self.submission.save(update_fields=["result", "info", "statistic_info"])
# 推送判题完成状态
try:
@@ -241,7 +252,7 @@ class JudgeDispatcher(DispatcherBase):
# update problem status
problem = Problem.objects.select_for_update().get(contest_id=self.contest_id, id=self.problem.id)
if self.last_result != JudgeStatus.ACCEPTED and self.submission.result == JudgeStatus.ACCEPTED:
problem.accepted_number += 1
problem.accepted_number = F("accepted_number") + 1
problem_info = problem.statistic_info
problem_info[self.last_result] = problem_info.get(self.last_result, 1) - 1
problem_info[result] = problem_info.get(result, 0) + 1
@@ -277,9 +288,9 @@ class JudgeDispatcher(DispatcherBase):
with transaction.atomic():
# update problem status
problem = Problem.objects.select_for_update().get(contest_id=self.contest_id, id=self.problem.id)
problem.submission_number += 1
problem.submission_number = F("submission_number") + 1
if self.submission.result == JudgeStatus.ACCEPTED:
problem.accepted_number += 1
problem.accepted_number = F("accepted_number") + 1
problem_info = problem.statistic_info
problem_info[result] = problem_info.get(result, 0) + 1
problem.save(update_fields=["accepted_number", "submission_number", "statistic_info"])
@@ -287,7 +298,7 @@ class JudgeDispatcher(DispatcherBase):
# update_userprofile
user = User.objects.select_for_update().get(id=self.submission.user_id)
user_profile = user.userprofile
user_profile.submission_number += 1
user_profile.submission_number = F("submission_number") + 1
if problem.rule_type == ProblemRuleType.ACM:
acm_problems_status = user_profile.acm_problems_status.get("problems", {})
if problem_id not in acm_problems_status:
@@ -356,9 +367,9 @@ class JudgeDispatcher(DispatcherBase):
result = str(self.submission.result)
problem_info = problem.statistic_info
problem_info[result] = problem_info.get(result, 0) + 1
problem.submission_number += 1
problem.submission_number = F("submission_number") + 1
if self.submission.result == JudgeStatus.ACCEPTED:
problem.accepted_number += 1
problem.accepted_number = F("accepted_number") + 1
problem.save(update_fields=["submission_number", "accepted_number", "statistic_info"])
def update_contest_rank(self):
@@ -422,7 +433,7 @@ class JudgeDispatcher(DispatcherBase):
elif self.submission.result != JudgeStatus.COMPILE_ERROR:
info["error_number"] = 1
rank.submission_info[str(self.submission.problem_id)] = info
rank.save()
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)
@@ -433,4 +444,4 @@ class JudgeDispatcher(DispatcherBase):
else:
rank.total_score = rank.total_score + current_score
rank.submission_info[problem_id] = current_score
rank.save()
rank.save(update_fields=["submission_info", "total_score", "submission_number"])

View File

@@ -0,0 +1,63 @@
# Generated by Django 6.0.4 on 2026-05-09 11:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('problem', '0008_alter_problem_unique_together_and_more'),
]
operations = [
migrations.AlterField(
model_name='problem',
name='accepted_number',
field=models.BigIntegerField(db_default=0, default=0),
),
migrations.AlterField(
model_name='problem',
name='allow_flowchart',
field=models.BooleanField(db_default=False, default=False),
),
migrations.AlterField(
model_name='problem',
name='flowchart_data',
field=models.JSONField(db_default=models.Value({}), default=dict),
),
migrations.AlterField(
model_name='problem',
name='is_public',
field=models.BooleanField(db_default=False, default=False),
),
migrations.AlterField(
model_name='problem',
name='share_submission',
field=models.BooleanField(db_default=False, default=False),
),
migrations.AlterField(
model_name='problem',
name='show_flowchart',
field=models.BooleanField(db_default=False, default=False),
),
migrations.AlterField(
model_name='problem',
name='statistic_info',
field=models.JSONField(db_default=models.Value({}), default=dict),
),
migrations.AlterField(
model_name='problem',
name='submission_number',
field=models.BigIntegerField(db_default=0, default=0),
),
migrations.AlterField(
model_name='problem',
name='total_score',
field=models.IntegerField(db_default=0, default=0),
),
migrations.AlterField(
model_name='problem',
name='visible',
field=models.BooleanField(db_default=True, default=True),
),
]

View File

@@ -36,7 +36,7 @@ class Problem(models.Model):
_id = models.TextField(db_index=True)
contest = models.ForeignKey(Contest, null=True, on_delete=models.CASCADE)
# for contest problem
is_public = models.BooleanField(default=False)
is_public = models.BooleanField(default=False, db_default=False)
title = models.TextField()
# HTML
description = RichTextField()
@@ -61,7 +61,7 @@ class Problem(models.Model):
# io mode
io_mode = models.JSONField(default=_default_io_mode)
rule_type = models.TextField(choices=ProblemRuleType.choices)
visible = models.BooleanField(default=True)
visible = models.BooleanField(default=True, db_default=True)
difficulty = models.TextField(choices=Difficulty.choices)
tags = models.ManyToManyField(ProblemTag)
source = models.TextField(null=True)
@@ -69,19 +69,19 @@ class Problem(models.Model):
# [{language: "python", code: "..."}]
answers = models.JSONField(null=True)
# for OI mode
total_score = models.IntegerField(default=0)
submission_number = models.BigIntegerField(default=0)
accepted_number = models.BigIntegerField(default=0)
total_score = models.IntegerField(default=0, db_default=0)
submission_number = models.BigIntegerField(default=0, db_default=0)
accepted_number = models.BigIntegerField(default=0, db_default=0)
# {JudgeStatus.ACCEPTED: 3, JudgeStatus.WRONG_ANSWER: 11}, the number means count
statistic_info = models.JSONField(default=dict)
share_submission = models.BooleanField(default=False)
statistic_info = models.JSONField(default=dict, db_default=models.Value({}))
share_submission = models.BooleanField(default=False, db_default=False)
# 流程图相关字段
allow_flowchart = models.BooleanField(default=False) # 是否允许/需要提交流程图
mermaid_code = models.TextField(null=True, blank=True) # 流程图答案(Mermaid代码)
flowchart_data = models.JSONField(default=dict) # 流程图答案元数据(JSON格式)
flowchart_hint = models.TextField(null=True, blank=True) # 流程图提示信息
show_flowchart = models.BooleanField(default=False) # 是否显示流程图答案数据如果True这样就不需要提交流程图了说明就是给学生看的
allow_flowchart = models.BooleanField(default=False, db_default=False)
mermaid_code = models.TextField(null=True, blank=True)
flowchart_data = models.JSONField(default=dict, db_default=models.Value({}))
flowchart_hint = models.TextField(null=True, blank=True)
show_flowchart = models.BooleanField(default=False, db_default=False)
class Meta:
db_table = "problem"

View File

@@ -118,11 +118,11 @@ class ProblemSetBadge(models.Model):
def _is_eligible(self, progress):
"""判断用户进度是否满足该徽章条件(纯逻辑,不查数据库)"""
if self.condition_type == "all_problems":
if self.condition_type == BadgeConditionType.ALL_PROBLEMS:
return progress.completed_problems_count == progress.total_problems_count
if self.condition_type == "problem_count":
if self.condition_type == BadgeConditionType.PROBLEM_COUNT:
return progress.completed_problems_count >= self.condition_value
if self.condition_type == "score":
if self.condition_type == BadgeConditionType.SCORE:
return progress.total_score >= self.condition_value
return False

View File

@@ -4,10 +4,12 @@ from django.utils import timezone
from account.models import User
from problem.models import Problem
from problemset.models import (
BadgeConditionType,
ProblemSet,
ProblemSetBadge,
ProblemSetProblem,
ProblemSetProgress,
ProblemSetStatus,
ProblemSetSubmission,
UserBadge,
)
@@ -31,7 +33,7 @@ class ProblemSetAPI(APIView):
def get(self, request):
"""获取题单列表"""
# 预加载创建者信息
problem_sets = ProblemSet.objects.filter(visible=True).exclude(status="draft").select_related("created_by")
problem_sets = ProblemSet.objects.filter(visible=True).exclude(status=ProblemSetStatus.DRAFT).select_related("created_by")
# 使用annotate在查询时计算题目数量避免N+1查询
problem_sets = problem_sets.annotate(problems_count=Count("problemsetproblem", distinct=True))
@@ -90,7 +92,7 @@ class ProblemSetDetailAPI(APIView):
def get(self, request, problem_set_id):
"""获取题单详情"""
try:
problem_set = ProblemSet.objects.filter(id=problem_set_id, visible=True).exclude(status="draft").get()
problem_set = ProblemSet.objects.filter(id=problem_set_id, visible=True).exclude(status=ProblemSetStatus.DRAFT).get()
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
@@ -104,7 +106,7 @@ class ProblemSetProblemAPI(APIView):
def get(self, request, problem_set_id):
"""获取题单中的题目列表"""
try:
problem_set = ProblemSet.objects.filter(id=problem_set_id, visible=True).exclude(status="draft").get()
problem_set = ProblemSet.objects.filter(id=problem_set_id, visible=True).exclude(status=ProblemSetStatus.DRAFT).get()
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
@@ -128,7 +130,7 @@ class ProblemSetProgressAPI(APIView):
"""加入题单"""
data = request.data
try:
problem_set = ProblemSet.objects.filter(id=data["problemset_id"], visible=True).exclude(status="draft").get()
problem_set = ProblemSet.objects.filter(id=data["problemset_id"], visible=True).exclude(status=ProblemSetStatus.DRAFT).get()
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
@@ -144,7 +146,7 @@ class ProblemSetProgressAPI(APIView):
def get(self, request, problem_set_id):
"""获取题单进度"""
try:
problem_set = ProblemSet.objects.filter(id=problem_set_id, visible=True).exclude(status="draft").get()
problem_set = ProblemSet.objects.filter(id=problem_set_id, visible=True).exclude(status=ProblemSetStatus.DRAFT).get()
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
@@ -161,7 +163,7 @@ class ProblemSetProgressAPI(APIView):
"""更新进度"""
data = request.data
try:
problem_set = ProblemSet.objects.filter(id=data["problemset_id"], visible=True).exclude(status="draft").get()
problem_set = ProblemSet.objects.filter(id=data["problemset_id"], visible=True).exclude(status=ProblemSetStatus.DRAFT).get()
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
@@ -223,13 +225,13 @@ class ProblemSetProgressAPI(APIView):
if UserBadge.objects.filter(user=progress.user, badge=badge).exists():
continue
if badge.condition_type == "all_problems":
if badge.condition_type == BadgeConditionType.ALL_PROBLEMS:
if progress.completed_problems_count == progress.total_problems_count:
UserBadge.objects.create(user=progress.user, badge=badge)
elif badge.condition_type == "problem_count":
elif badge.condition_type == BadgeConditionType.PROBLEM_COUNT:
if progress.completed_problems_count >= badge.condition_value:
UserBadge.objects.create(user=progress.user, badge=badge)
elif badge.condition_type == "score":
elif badge.condition_type == BadgeConditionType.SCORE:
if progress.total_score >= badge.condition_value:
UserBadge.objects.create(user=progress.user, badge=badge)
@@ -273,7 +275,7 @@ class ProblemSetBadgeAPI(APIView):
def get(self, request, problem_set_id):
"""获取题单的奖章列表"""
try:
problem_set = ProblemSet.objects.filter(id=problem_set_id, visible=True).exclude(status="draft").get()
problem_set = ProblemSet.objects.filter(id=problem_set_id, visible=True).exclude(status=ProblemSetStatus.DRAFT).get()
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
@@ -288,7 +290,7 @@ class ProblemSetUserProgressAPI(APIView):
def get(self, request, problem_set_id: int):
"""获取题单的用户进度列表"""
try:
problem_set = ProblemSet.objects.filter(id=problem_set_id, visible=True).exclude(status="draft").get()
problem_set = ProblemSet.objects.filter(id=problem_set_id, visible=True).exclude(status=ProblemSetStatus.DRAFT).get()
except ProblemSet.DoesNotExist:
return self.error("题单不存在")

View File

@@ -0,0 +1,33 @@
# Generated by Django 6.0.4 on 2026-05-09 11:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('submission', '0005_alter_submission_result'),
]
operations = [
migrations.AlterField(
model_name='submission',
name='info',
field=models.JSONField(db_default=models.Value({}), default=dict),
),
migrations.AlterField(
model_name='submission',
name='result',
field=models.IntegerField(choices=[(-2, 'Compile Error'), (-1, 'Wrong Answer'), (0, 'Accepted'), (1, 'CPU Time Limit Exceeded'), (2, 'Real Time Limit Exceeded'), (3, 'Memory Limit Exceeded'), (4, 'Runtime Error'), (5, 'System Error'), (6, 'Pending'), (7, 'Judging'), (8, 'Partially Accepted')], db_default=6, db_index=True, default=6),
),
migrations.AlterField(
model_name='submission',
name='shared',
field=models.BooleanField(db_default=False, default=False),
),
migrations.AlterField(
model_name='submission',
name='statistic_info',
field=models.JSONField(db_default=models.Value({}), default=dict),
),
]

View File

@@ -29,14 +29,14 @@ class Submission(models.Model):
user_id = models.IntegerField(db_index=True)
username = models.TextField()
code = models.TextField()
result = models.IntegerField(choices=JudgeStatus.choices, db_index=True, default=JudgeStatus.PENDING)
result = models.IntegerField(choices=JudgeStatus.choices, db_index=True, default=JudgeStatus.PENDING, db_default=JudgeStatus.PENDING)
# 从JudgeServer返回的判题详情
info = JSONField(default=dict)
info = JSONField(default=dict, db_default=models.Value({}))
language = models.TextField()
shared = models.BooleanField(default=False)
shared = models.BooleanField(default=False, db_default=False)
# 存储该提交所用时间和内存值,方便提交列表显示
# {time_cost: "", memory_cost: "", err_info: "", score: 0}
statistic_info = JSONField(default=dict)
statistic_info = JSONField(default=dict, db_default=models.Value({}))
ip = models.TextField(null=True)
def check_user_permission(self, user, check_share=True):

View File

@@ -2,7 +2,7 @@ from django.db import models
from django.db.models import F
from django.utils import timezone
from problemset.models import ProblemSetProgress
from problemset.models import ProblemSetProgress, ProblemSetStatus
from utils.api import serializers
from utils.serializers import LanguageNameChoiceField
@@ -16,7 +16,7 @@ def bulk_fetch_problemset_progress(user, problem_ids):
rows = (
ProblemSetProgress.objects.filter(
user=user,
problemset__status="active",
problemset__status=ProblemSetStatus.ACTIVE,
problemset__problemsetproblem__problem_id__in=problem_ids,
)
.filter(
@@ -108,7 +108,7 @@ class SubmissionListSerializer(serializers.ModelSerializer):
self._problemset_progress_cache[problem_id] = (
ProblemSetProgress.objects.filter(
user=self.user,
problemset__status="active",
problemset__status=ProblemSetStatus.ACTIVE,
problemset__problemsetproblem__problem_id=problem_id,
)
.filter(