diff --git a/problemset/migrations/0005_alter_problemsetsubmission_options_and_more.py b/problemset/migrations/0005_alter_problemsetsubmission_options_and_more.py new file mode 100644 index 0000000..947e806 --- /dev/null +++ b/problemset/migrations/0005_alter_problemsetsubmission_options_and_more.py @@ -0,0 +1,57 @@ +# Generated by Django 5.2.3 on 2025-10-23 01:34 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('problem', '0005_remove_spj_fields'), + ('problemset', '0004_alter_problemset_status_problemsetsubmission'), + ('submission', '0002_submission_user_create_time_idx'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterModelOptions( + name='problemsetsubmission', + options={'ordering': ('-submission__create_time',), 'verbose_name': '题单提交记录', 'verbose_name_plural': '题单提交记录'}, + ), + migrations.RemoveIndex( + model_name='problemsetsubmission', + name='problemset__user_id_63c1d0_idx', + ), + migrations.RemoveField( + model_name='problemsetsubmission', + name='code_length', + ), + migrations.RemoveField( + model_name='problemsetsubmission', + name='execution_time', + ), + migrations.RemoveField( + model_name='problemsetsubmission', + name='language', + ), + migrations.RemoveField( + model_name='problemsetsubmission', + name='memory_usage', + ), + migrations.RemoveField( + model_name='problemsetsubmission', + name='result', + ), + migrations.RemoveField( + model_name='problemsetsubmission', + name='score', + ), + migrations.RemoveField( + model_name='problemsetsubmission', + name='submit_time', + ), + migrations.AddIndex( + model_name='problemsetsubmission', + index=models.Index(fields=['user'], name='problemset__user_id_2f1501_idx'), + ), + ] diff --git a/problemset/models.py b/problemset/models.py index 1117ac0..b16c9fc 100644 --- a/problemset/models.py +++ b/problemset/models.py @@ -113,7 +113,7 @@ class ProblemSetProgress(models.Model): # 获得的总分 total_score = models.IntegerField(default=0, verbose_name="总分") # 用户在该题单中的详细进度信息 - # {"problem_id": {"status": "completed", "score": 100, "submit_time": "2024-01-01T00:00:00Z"}} + # {"problem_id": {"score": 20, "submit_time": "2024-01-01T00:00:00Z"}} progress_detail = JSONField(default=dict, verbose_name="详细进度") class Meta: @@ -141,9 +141,8 @@ class ProblemSetProgress(models.Model): problem_id = str(psp.problem.id) if problem_id in self.progress_detail: problem_progress = self.progress_detail[problem_id] - if problem_progress.get("status") == "completed": - completed_count += 1 - total_score += problem_progress.get("score", 0) + completed_count += 1 + total_score += problem_progress.get("score", 0) self.completed_problems_count = completed_count self.total_score = total_score @@ -177,35 +176,35 @@ class ProblemSetSubmission(models.Model): problem = models.ForeignKey( "problem.Problem", on_delete=models.CASCADE, verbose_name="题目" ) - # 提交时间 - submit_time = models.DateTimeField(auto_now_add=True, verbose_name="提交时间") - # 提交结果 - result = models.IntegerField(verbose_name="提交结果") - # 得分 - score = models.IntegerField(default=0, verbose_name="得分") - # 语言 - language = models.CharField(max_length=20, verbose_name="编程语言") - # 代码长度 - code_length = models.IntegerField(default=0, verbose_name="代码长度") - # 执行时间(毫秒) - execution_time = models.IntegerField(default=0, verbose_name="执行时间") - # 内存使用(KB) - memory_usage = models.IntegerField(default=0, verbose_name="内存使用") - + class Meta: db_table = "problemset_submission" - ordering = ("-submit_time",) + ordering = ("-submission__create_time",) verbose_name = "题单提交记录" verbose_name_plural = "题单提交记录" indexes = [ models.Index(fields=["problemset", "user"]), models.Index(fields=["problemset", "problem"]), - models.Index(fields=["user", "submit_time"]), + models.Index(fields=["user"]), ] def __str__(self): return f"{self.user.username} - {self.problemset.title} - {self.problem.title}" + @property + def submit_time(self): + """提交时间""" + return self.submission.create_time + + @property + def result(self): + """提交结果""" + return self.submission.result + + @property + def language(self): + """编程语言""" + return self.submission.language class UserBadge(models.Model): """用户奖章模型""" diff --git a/problemset/serializers.py b/problemset/serializers.py index 936474b..9e55796 100644 --- a/problemset/serializers.py +++ b/problemset/serializers.py @@ -227,10 +227,6 @@ class UpdateProgressSerializer(serializers.Serializer): problemset_id = serializers.IntegerField() problem_id = serializers.IntegerField() - status = serializers.CharField() # completed, attempted, not_started - score = serializers.IntegerField(default=0) - submit_time = serializers.DateTimeField(required=False) - class ProblemSetSubmissionSerializer(serializers.ModelSerializer): """题单提交记录序列化器""" @@ -238,6 +234,7 @@ class ProblemSetSubmissionSerializer(serializers.ModelSerializer): problem_title = serializers.CharField(source="problem.title", read_only=True) problem_id = serializers.IntegerField(source="problem.id", read_only=True) result_text = serializers.SerializerMethodField() + submit_time = serializers.DateTimeField(source="submission.create_time", read_only=True) class Meta: model = ProblemSetSubmission @@ -248,28 +245,7 @@ class ProblemSetSubmissionSerializer(serializers.ModelSerializer): "problem_title", "submission", "result", - "result_text", - "score", "language", - "code_length", - "execution_time", - "memory_usage", "submit_time", ] - - def get_result_text(self, obj): - """获取结果文本""" - result_map = { - -2: "编译错误", - -1: "答案错误", - 0: "通过", - 1: "时间超限", - 2: "时间超限", - 3: "内存超限", - 4: "运行时错误", - 5: "系统错误", - 6: "等待中", - 7: "评测中", - 8: "部分通过", - } - return result_map.get(obj.result, "未知") + diff --git a/problemset/signals.py b/problemset/signals.py index 717a117..6c66feb 100644 --- a/problemset/signals.py +++ b/problemset/signals.py @@ -1,94 +1,2 @@ -from django.db.models.signals import post_save -from django.dispatch import receiver -from django.utils import timezone - -from .models import ProblemSetProgress, ProblemSetBadge, UserBadge, ProblemSetSubmission -from submission.models import Submission - - -@receiver(post_save, sender=Submission) -def update_problemset_progress(sender, instance, created, **kwargs): - """当提交状态更新时,自动更新题单进度""" - # 处理新建和更新,但只在提交状态确定时更新(不是Pending状态) - if instance.result == 6: # 6表示PENDING状态,不更新进度 - return - - # 检查该提交是否属于某个题单中的题目 - try: - from .models import ProblemSetProblem - - problemset_problems = ProblemSetProblem.objects.filter(problem=instance.problem) - - for psp in problemset_problems: - # 获取或创建用户在该题单中的进度记录 - progress, created = ProblemSetProgress.objects.get_or_create( - problemset=psp.problemset, user=instance.user - ) - - # 创建题单提交记录 - ProblemSetSubmission.objects.create( - problemset=psp.problemset, - user=instance.user, - submission=instance, - problem=instance.problem, - result=instance.result, - score=instance.score if hasattr(instance, "score") else 0, - language=instance.language, - code_length=len(instance.code) if hasattr(instance, "code") else 0, - execution_time=instance.statistic_info.get("time_cost", 0) if hasattr(instance, "statistic_info") else 0, - memory_usage=instance.statistic_info.get("memory_cost", 0) if hasattr(instance, "statistic_info") else 0, - ) - - # 更新详细进度 - problem_id = str(instance.problem.id) - - # 确定题目状态 - if instance.result == 0: # ACCEPTED - status = "completed" # 部分通过也算完成 - else: # 其他状态(错误、超时等) - status = "attempted" - - progress.progress_detail[problem_id] = { - "status": status, - "score": instance.score if hasattr(instance, "score") else 0, - "submit_time": timezone.now().isoformat(), - "result": instance.result, # 保存原始结果代码 - } - - # 更新进度 - progress.update_progress() - - # 检查是否获得奖章 - check_and_award_badges(progress) - - except Exception as e: - # 记录错误但不影响主流程 - import logging - - logger = logging.getLogger(__name__) - logger.error(f"更新题单进度时出错: {e}") - - -def check_and_award_badges(progress): - """检查并颁发奖章""" - badges = ProblemSetBadge.objects.filter(problemset=progress.problemset) - - for badge in badges: - # 检查是否已经获得该奖章 - if UserBadge.objects.filter(user=progress.user, badge=badge).exists(): - continue - - # 检查是否满足获得条件 - should_award = False - - if badge.condition_type == "all_problems": - should_award = ( - progress.completed_problems_count == progress.total_problems_count - ) - elif badge.condition_type == "problem_count": - should_award = progress.completed_problems_count >= badge.condition_value - elif badge.condition_type == "score": - should_award = progress.total_score >= badge.condition_value - - if should_award: - UserBadge.objects.create(user=progress.user, badge=badge) +# 题单应用信号处理 +# 目前暂时为空,后续可以添加信号处理逻辑 diff --git a/problemset/views/oj.py b/problemset/views/oj.py index 562156f..e81a008 100644 --- a/problemset/views/oj.py +++ b/problemset/views/oj.py @@ -1,4 +1,3 @@ - from django.db.models import Q from django.db import models from django.utils import timezone @@ -65,7 +64,11 @@ 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="draft") + .get() + ) except ProblemSet.DoesNotExist: return self.error("题单不存在") @@ -79,7 +82,11 @@ 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="draft") + .get() + ) except ProblemSet.DoesNotExist: return self.error("题单不存在") @@ -100,7 +107,11 @@ 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="draft") + .get() + ) except ProblemSet.DoesNotExist: return self.error("题单不存在") @@ -120,7 +131,11 @@ 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="draft") + .get() + ) except ProblemSet.DoesNotExist: return self.error("题单不存在") @@ -139,7 +154,11 @@ 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="draft") + .get() + ) except ProblemSet.DoesNotExist: return self.error("题单不存在") @@ -152,9 +171,18 @@ class ProblemSetProgressAPI(APIView): # 更新详细进度 problem_id = str(data["problem_id"]) + + # 获取该题目在题单中的分值 + try: + problemset_problem = ProblemSetProblem.objects.get( + problemset=problem_set, problem_id=problem_id + ) + problem_score = problemset_problem.score + except ProblemSetProblem.DoesNotExist: + problem_score = 0 + progress.progress_detail[problem_id] = { - "status": data["status"], - "score": data.get("score", 0), + "score": problem_score, "submit_time": data.get("submit_time", timezone.now().isoformat()), } @@ -225,7 +253,11 @@ 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="draft") + .get() + ) except ProblemSet.DoesNotExist: return self.error("题单不存在") @@ -240,7 +272,11 @@ class ProblemSetSubmissionAPI(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="draft") + .get() + ) except ProblemSet.DoesNotExist: return self.error("题单不存在") @@ -254,22 +290,21 @@ class ProblemSetSubmissionAPI(APIView): problem_id = request.GET.get("problem_id") result = request.GET.get("result") language = request.GET.get("language") - + # 构建查询条件 - query_filter = { - "problemset": problem_set, - "user": request.user - } - + query_filter = {"problemset": problem_set, "user": request.user} + if problem_id: query_filter["problem_id"] = problem_id if result: - query_filter["result"] = result + query_filter["submission__result"] = result if language: - query_filter["language"] = language + query_filter["submission__language"] = language # 获取提交记录 - submissions = ProblemSetSubmission.objects.filter(**query_filter).order_by("-submit_time") + submissions = ProblemSetSubmission.objects.filter(**query_filter).order_by( + "-submission__create_time" + ) # 分页 data = self.paginate_data(request, submissions, ProblemSetSubmissionSerializer) @@ -282,13 +317,19 @@ class ProblemSetStatisticsAPI(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="draft") + .get() + ) except ProblemSet.DoesNotExist: return self.error("题单不存在") # 检查用户是否已加入该题单 try: - progress = ProblemSetProgress.objects.get(problemset=problem_set, user=request.user) + progress = ProblemSetProgress.objects.get( + problemset=problem_set, user=request.user + ) except ProblemSetProgress.DoesNotExist: return self.error("您还未加入该题单") @@ -296,53 +337,60 @@ class ProblemSetStatisticsAPI(APIView): total_submissions = ProblemSetSubmission.objects.filter( problemset=problem_set, user=request.user ).count() - + accepted_submissions = ProblemSetSubmission.objects.filter( - problemset=problem_set, user=request.user, result=0 + problemset=problem_set, user=request.user, submission__result=0 ).count() - + # 按题目统计 problem_stats = {} problemset_problems = ProblemSetProblem.objects.filter(problemset=problem_set) - + for psp in problemset_problems: problem_id = psp.problem.id problem_submissions = ProblemSetSubmission.objects.filter( - problemset=problem_set, - user=request.user, - problem=psp.problem + problemset=problem_set, user=request.user, problem=psp.problem ) - + problem_stats[str(problem_id)] = { "problem_title": psp.problem.title, "total_submissions": problem_submissions.count(), - "accepted_submissions": problem_submissions.filter(result=0).count(), - "is_completed": str(problem_id) in progress.progress_detail and - progress.progress_detail[str(problem_id)].get("status") == "completed" + "accepted_submissions": problem_submissions.filter(submission__result=0).count(), + "is_completed": str(problem_id) in progress.progress_detail, } # 按语言统计 language_stats = {} - language_submissions = ProblemSetSubmission.objects.filter( - problemset=problem_set, user=request.user - ).values('language').annotate(count=models.Count('id')) - + language_submissions = ( + ProblemSetSubmission.objects.filter( + problemset=problem_set, user=request.user + ) + .values("submission__language") + .annotate(count=models.Count("id")) + ) + for item in language_submissions: - language_stats[item['language']] = item['count'] + language_stats[item["submission__language"]] = item["count"] # 按结果统计 result_stats = {} - result_submissions = ProblemSetSubmission.objects.filter( - problemset=problem_set, user=request.user - ).values('result').annotate(count=models.Count('id')) - + result_submissions = ( + ProblemSetSubmission.objects.filter( + problemset=problem_set, user=request.user + ) + .values("submission__result") + .annotate(count=models.Count("id")) + ) + for item in result_submissions: - result_stats[item['result']] = item['count'] + result_stats[item["submission__result"]] = item["count"] data = { "total_submissions": total_submissions, "accepted_submissions": accepted_submissions, - "acceptance_rate": round(accepted_submissions / total_submissions * 100, 2) if total_submissions > 0 else 0, + "acceptance_rate": round(accepted_submissions / total_submissions * 100, 2) + if total_submissions > 0 + else 0, "problem_stats": problem_stats, "language_stats": language_stats, "result_stats": result_stats, @@ -350,8 +398,8 @@ class ProblemSetStatisticsAPI(APIView): "completed_problems_count": progress.completed_problems_count, "total_problems_count": progress.total_problems_count, "progress_percentage": progress.progress_percentage, - "total_score": progress.total_score - } + "total_score": progress.total_score, + }, } - + return self.success(data) diff --git a/submission/serializers.py b/submission/serializers.py index 2a457f0..b09f654 100644 --- a/submission/serializers.py +++ b/submission/serializers.py @@ -8,6 +8,7 @@ class CreateSubmissionSerializer(serializers.Serializer): language = LanguageNameChoiceField() code = serializers.CharField(max_length=1024 * 1024) contest_id = serializers.IntegerField(required=False) + problemset_id = serializers.IntegerField(required=False) captcha = serializers.CharField(required=False) diff --git a/submission/views/oj.py b/submission/views/oj.py index f657322..7634319 100644 --- a/submission/views/oj.py +++ b/submission/views/oj.py @@ -19,6 +19,7 @@ from ..serializers import ( ShareSubmissionSerializer, ) from ..serializers import SubmissionSafeModelSerializer, SubmissionListSerializer +from problemset.models import ProblemSetSubmission class SubmissionAPI(APIView): @@ -91,6 +92,16 @@ class SubmissionAPI(APIView): ip=request.session["ip"], contest_id=data.get("contest_id"), ) + + # 如果有problemset_id,创建ProblemSetSubmission记录 + if data.get("problemset_id"): + ProblemSetSubmission.objects.create( + problemset_id=data["problemset_id"], + user=request.user, + submission=submission, + problem=problem, + ) + # use this for debug # JudgeDispatcher(submission.id, problem.id).judge() judge_task.send(submission.id, problem.id) @@ -174,10 +185,7 @@ class SubmissionListAPI(APIView): return self.error("Problem doesn't exist") submissions = submissions.filter(problem=problem) - if ( - not SysOptions.submission_list_show_all - and request.user.is_regular_user() - ): + if not SysOptions.submission_list_show_all and request.user.is_regular_user(): return self.success({"results": [], "total": 0}) if myself and myself == "1":