From 32a608476dda0c24487e38095d44573d6a3a3f12 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Thu, 23 Oct 2025 00:54:06 +0800 Subject: [PATCH] update --- oj/dev_settings.py | 8 +- problem/serializers.py | 1 - ..._problemset_status_problemsetsubmission.py | 47 ++++ problemset/models.py | 45 ++- problemset/serializers.py | 120 ++++++-- problemset/signals.py | 31 +- problemset/urls/admin.py | 20 +- problemset/urls/oj.py | 34 ++- problemset/views/admin.py | 58 ++-- problemset/views/oj.py | 264 +++++++++--------- utils/api/api.py | 2 +- 11 files changed, 408 insertions(+), 222 deletions(-) create mode 100644 problemset/migrations/0004_alter_problemset_status_problemsetsubmission.py diff --git a/oj/dev_settings.py b/oj/dev_settings.py index 067efe7..16cb2bd 100644 --- a/oj/dev_settings.py +++ b/oj/dev_settings.py @@ -6,8 +6,8 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", - "HOST": "10.13.114.114", - "PORT": "5433", + "HOST": "150.158.29.156", + "PORT": "5455", "NAME": "onlinejudge", "USER": "onlinejudge", "PASSWORD": "onlinejudge", @@ -15,8 +15,8 @@ DATABASES = { } REDIS_CONF = { - "host": "10.13.114.114", - "port": 6379, + "host": "150.158.29.156", + "port": 5456, } diff --git a/problem/serializers.py b/problem/serializers.py index 4f69aab..342ef67 100644 --- a/problem/serializers.py +++ b/problem/serializers.py @@ -184,7 +184,6 @@ class ProblemListSerializer(BaseProblemSerializer): "created_by", "tags", "contest", - "rule_type", "allow_flowchart", ] diff --git a/problemset/migrations/0004_alter_problemset_status_problemsetsubmission.py b/problemset/migrations/0004_alter_problemset_status_problemsetsubmission.py new file mode 100644 index 0000000..cfe9078 --- /dev/null +++ b/problemset/migrations/0004_alter_problemset_status_problemsetsubmission.py @@ -0,0 +1,47 @@ +# Generated by Django 5.2.3 on 2025-10-22 16:49 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('problem', '0005_remove_spj_fields'), + ('problemset', '0003_remove_badge_level'), + ('submission', '0002_submission_user_create_time_idx'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name='problemset', + name='status', + field=models.TextField(default='draft', verbose_name='状态'), + ), + migrations.CreateModel( + name='ProblemSetSubmission', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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='执行时间')), + ('memory_usage', models.IntegerField(default=0, verbose_name='内存使用')), + ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='problem.problem', verbose_name='题目')), + ('problemset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='problemset.problemset', verbose_name='题单')), + ('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='submission.submission', verbose_name='提交记录')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='用户')), + ], + options={ + 'verbose_name': '题单提交记录', + 'verbose_name_plural': '题单提交记录', + 'db_table': 'problemset_submission', + 'ordering': ('-submit_time',), + 'indexes': [models.Index(fields=['problemset', 'user'], name='problemset__problem_1f39fa_idx'), models.Index(fields=['problemset', 'problem'], name='problemset__problem_22f053_idx'), models.Index(fields=['user', 'submit_time'], name='problemset__user_id_63c1d0_idx')], + }, + ), + ] diff --git a/problemset/models.py b/problemset/models.py index 1cb74ad..1117ac0 100644 --- a/problemset/models.py +++ b/problemset/models.py @@ -24,7 +24,7 @@ class ProblemSet(models.Model): difficulty = models.TextField(default="Easy", verbose_name="难度等级") # 题单状态 status = models.TextField( - default="active", verbose_name="状态" + default="draft", verbose_name="状态" ) # active, archived, draft class Meta: @@ -164,6 +164,49 @@ class ProblemSetProgress(models.Model): self.save() +class ProblemSetSubmission(models.Model): + """题单提交记录模型""" + + problemset = models.ForeignKey( + ProblemSet, on_delete=models.CASCADE, verbose_name="题单" + ) + user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户") + submission = models.ForeignKey( + "submission.Submission", on_delete=models.CASCADE, verbose_name="提交记录" + ) + 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",) + verbose_name = "题单提交记录" + verbose_name_plural = "题单提交记录" + indexes = [ + models.Index(fields=["problemset", "user"]), + models.Index(fields=["problemset", "problem"]), + models.Index(fields=["user", "submit_time"]), + ] + + def __str__(self): + return f"{self.user.username} - {self.problemset.title} - {self.problem.title}" + + class UserBadge(models.Model): """用户奖章模型""" diff --git a/problemset/serializers.py b/problemset/serializers.py index 3252dce..936474b 100644 --- a/problemset/serializers.py +++ b/problemset/serializers.py @@ -5,15 +5,48 @@ from .models import ( ProblemSetBadge, ProblemSetProgress, UserBadge, + ProblemSetSubmission, ) +def get_user_progress_data(problemset, request): + """获取当前用户在该题单中的进度 - 公共方法""" + if request and request.user.is_authenticated: + try: + progress = ProblemSetProgress.objects.get( + problemset=problemset, user=request.user + ) + return { + "is_joined": True, + "progress_percentage": progress.progress_percentage, + "completed_count": progress.completed_problems_count, + "total_count": progress.total_problems_count, + "is_completed": progress.is_completed, + } + except ProblemSetProgress.DoesNotExist: + return { + "is_joined": False, + "progress_percentage": 0, + "completed_count": 0, + "total_count": 0, + "is_completed": False, + } + return { + "is_joined": False, + "progress_percentage": 0, + "completed_count": 0, + "total_count": 0, + "is_completed": False, + } + + class ProblemSetSerializer(serializers.ModelSerializer): """题单序列化器""" created_by = UsernameSerializer() problems_count = serializers.SerializerMethodField() completed_count = serializers.SerializerMethodField() + user_progress = serializers.SerializerMethodField() class Meta: model = ProblemSet @@ -36,6 +69,11 @@ class ProblemSetSerializer(serializers.ModelSerializer): return 0 return 0 + def get_user_progress(self, obj): + """获取当前用户在该题单中的进度""" + request = self.context.get("request") + return get_user_progress_data(obj, request) + class ProblemSetListSerializer(serializers.ModelSerializer): """题单列表序列化器""" @@ -43,6 +81,7 @@ class ProblemSetListSerializer(serializers.ModelSerializer): created_by = UsernameSerializer() problems_count = serializers.SerializerMethodField() user_progress = serializers.SerializerMethodField() + badges = serializers.SerializerMethodField() class Meta: model = ProblemSet @@ -56,6 +95,7 @@ class ProblemSetListSerializer(serializers.ModelSerializer): "status", "problems_count", "user_progress", + "badges", "visible", ] @@ -66,33 +106,12 @@ class ProblemSetListSerializer(serializers.ModelSerializer): def get_user_progress(self, obj): """获取当前用户在该题单中的进度""" request = self.context.get("request") - if request and request.user.is_authenticated: - try: - progress = ProblemSetProgress.objects.get( - problemset=obj, user=request.user - ) - return { - "is_joined": True, - "progress_percentage": progress.progress_percentage, - "completed_count": progress.completed_problems_count, - "total_count": progress.total_problems_count, - "is_completed": progress.is_completed, - } - except ProblemSetProgress.DoesNotExist: - return { - "is_joined": False, - "progress_percentage": 0, - "completed_count": 0, - "total_count": 0, - "is_completed": False, - } - return { - "is_joined": False, - "progress_percentage": 0, - "completed_count": 0, - "total_count": 0, - "is_completed": False, - } + return get_user_progress_data(obj, request) + + def get_badges(self, obj): + """获取题单的奖章列表""" + badges = ProblemSetBadge.objects.filter(problemset=obj) + return ProblemSetBadgeSerializer(badges, many=True).data class CreateProblemSetSerializer(serializers.Serializer): @@ -126,9 +145,9 @@ class ProblemSetProblemSerializer(serializers.ModelSerializer): def get_problem(self, obj): """获取题目详细信息""" - from problem.serializers import ProblemSerializer + from problem.serializers import ProblemListSerializer - return ProblemSerializer(obj.problem, context=self.context).data + return ProblemListSerializer(obj.problem, context=self.context).data class AddProblemToSetSerializer(serializers.Serializer): @@ -211,3 +230,46 @@ class UpdateProgressSerializer(serializers.Serializer): status = serializers.CharField() # completed, attempted, not_started score = serializers.IntegerField(default=0) submit_time = serializers.DateTimeField(required=False) + + +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() + + class Meta: + model = ProblemSetSubmission + fields = [ + "id", + "problem", + "problem_id", + "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 7e0cd1f..717a117 100644 --- a/problemset/signals.py +++ b/problemset/signals.py @@ -2,14 +2,15 @@ from django.db.models.signals import post_save from django.dispatch import receiver from django.utils import timezone -from .models import ProblemSetProgress, ProblemSetBadge, UserBadge +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): """当提交状态更新时,自动更新题单进度""" - if not created: # 只处理更新,不处理新建 + # 处理新建和更新,但只在提交状态确定时更新(不是Pending状态) + if instance.result == 6: # 6表示PENDING状态,不更新进度 return # 检查该提交是否属于某个题单中的题目 @@ -24,14 +25,34 @@ def update_problemset_progress(sender, instance, created, **kwargs): 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": "completed" - if instance.result == 0 - else "attempted", # 0表示AC + "status": status, "score": instance.score if hasattr(instance, "score") else 0, "submit_time": timezone.now().isoformat(), + "result": instance.result, # 保存原始结果代码 } # 更新进度 diff --git a/problemset/urls/admin.py b/problemset/urls/admin.py index 140c32b..0d53723 100644 --- a/problemset/urls/admin.py +++ b/problemset/urls/admin.py @@ -12,52 +12,52 @@ from problemset.views.admin import ( urlpatterns = [ # 管理员题单管理API - path("problemset/", ProblemSetAdminAPI.as_view(), name="admin_problemset_api"), + path("problemset", ProblemSetAdminAPI.as_view(), name="admin_problemset_api"), path( - "problemset//", + "problemset/", ProblemSetDetailAdminAPI.as_view(), name="admin_problemset_detail_api", ), path( - "problemset//problems/", + "problemset//problems", ProblemSetProblemAdminAPI.as_view(), name="admin_problemset_problems_api", ), path( - "problemset//problems//", + "problemset//problems/", ProblemSetProblemAdminAPI.as_view(), name="admin_problemset_problem_detail_api", ), # 管理员奖章管理API path( - "problemset//badges/", + "problemset//badges", ProblemSetBadgeAdminAPI.as_view(), name="admin_problemset_badges_api", ), path( - "problemset//badges//", + "problemset//badges/", ProblemSetBadgeAdminAPI.as_view(), name="admin_problemset_badge_detail_api", ), # 管理员进度管理API path( - "problemset//progress/", + "problemset//progress", ProblemSetProgressAdminAPI.as_view(), name="admin_problemset_progress_api", ), path( - "problemset//progress//", + "problemset//progress/", ProblemSetProgressAdminAPI.as_view(), name="admin_problemset_progress_detail_api", ), # 题单状态管理API path( - "problemset/visible/", + "problemset/visible", ProblemSetVisibleAPI.as_view(), name="admin_problemset_visible_api", ), path( - "problemset/status/", + "problemset/status", ProblemSetStatusAPI.as_view(), name="admin_problemset_status_api", ), diff --git a/problemset/urls/oj.py b/problemset/urls/oj.py index d21bbc3..faa2c8a 100644 --- a/problemset/urls/oj.py +++ b/problemset/urls/oj.py @@ -7,48 +7,62 @@ from problemset.views.oj import ( UserBadgeAPI, UserProgressAPI, ProblemSetBadgeAPI, + ProblemSetSubmissionAPI, + ProblemSetStatisticsAPI, ) urlpatterns = [ # 题单相关API - path("api/problemset/", ProblemSetAPI.as_view(), name="problemset_api"), + path("problemset", ProblemSetAPI.as_view(), name="problemset_api"), path( - "api/problemset//", + "problemset/", ProblemSetDetailAPI.as_view(), name="problemset_detail_api", ), path( - "api/problemset//problems/", + "problemset//problems", ProblemSetProblemAPI.as_view(), name="problemset_problems_api", ), path( - "api/problemset//problems//", + "problemset//problems/", ProblemSetProblemAPI.as_view(), name="problemset_problem_detail_api", ), # 进度相关API path( - "api/problemset/progress/", + "problemset/progress", ProblemSetProgressAPI.as_view(), name="problemset_progress_api", ), path( - "api/problemset//progress/", + "problemset//progress", ProblemSetProgressAPI.as_view(), name="problemset_progress_detail_api", ), - path("api/user/progress/", UserProgressAPI.as_view(), name="user_progress_api"), + path("user/progress", UserProgressAPI.as_view(), name="user_progress_api"), # 奖章相关API - path("api/user/badges/", UserBadgeAPI.as_view(), name="user_badges_api"), + path("user/badges", UserBadgeAPI.as_view(), name="user_badges_api"), path( - "api/user/badges//", + "user/badges/", UserBadgeAPI.as_view(), name="user_badge_detail_api", ), path( - "api/problemset//badges/", + "problemset//badges", ProblemSetBadgeAPI.as_view(), name="problemset_badges_api", ), + # 提交记录相关API + path( + "problemset//submissions", + ProblemSetSubmissionAPI.as_view(), + name="problemset_submissions_api", + ), + # 统计相关API + path( + "problemset//statistics", + ProblemSetStatisticsAPI.as_view(), + name="problemset_statistics_api", + ), ] diff --git a/problemset/views/admin.py b/problemset/views/admin.py index 6de8ef9..6f8e8d7 100644 --- a/problemset/views/admin.py +++ b/problemset/views/admin.py @@ -154,9 +154,13 @@ class ProblemSetProblemAdminAPI(APIView): data = request.data try: - problem = Problem.objects.get(_id=data["problem_id"]) + problem = Problem.objects.filter( + _id=data["problem_id"], + visible=True, + contest_id__isnull=True, + ).get() except Problem.DoesNotExist: - return self.error("题目不存在") + return self.error("题目不存在或不可见") # 检查题目是否已经在题单中 if ProblemSetProblem.objects.filter( @@ -194,15 +198,15 @@ class ProblemSetProblemAdminAPI(APIView): data = request.data # 更新题目属性 - if 'order' in data: - problem_set_problem.order = data['order'] - if 'is_required' in data: - problem_set_problem.is_required = data['is_required'] - if 'score' in data: - problem_set_problem.score = data['score'] - if 'hint' in data: - problem_set_problem.hint = data['hint'] - + if "order" in data: + problem_set_problem.order = data["order"] + if "is_required" in data: + problem_set_problem.is_required = data["is_required"] + if "score" in data: + problem_set_problem.score = data["score"] + if "hint" in data: + problem_set_problem.hint = data["hint"] + problem_set_problem.save() return self.success("题目已更新") @@ -274,19 +278,19 @@ class ProblemSetBadgeAdminAPI(APIView): data = request.data # 更新奖章属性 - if 'name' in data: - badge.name = data['name'] - if 'description' in data: - badge.description = data['description'] - if 'icon' in data: - badge.icon = data['icon'] - if 'condition_type' in data: - badge.condition_type = data['condition_type'] - if 'condition_value' in data: - badge.condition_value = data['condition_value'] - if 'level' in data: - badge.level = data['level'] - + if "name" in data: + badge.name = data["name"] + if "description" in data: + badge.description = data["description"] + if "icon" in data: + badge.icon = data["icon"] + if "condition_type" in data: + badge.condition_type = data["condition_type"] + if "condition_value" in data: + badge.condition_value = data["condition_value"] + if "level" in data: + badge.level = data["level"] + badge.save() return self.success("奖章已更新") @@ -319,9 +323,9 @@ class ProblemSetProgressAdminAPI(APIView): except ProblemSet.DoesNotExist: return self.error("题单不存在") - progress_list = ProblemSetProgress.objects.filter(problemset=problem_set).order_by( - "-join_time" - ) + progress_list = ProblemSetProgress.objects.filter( + problemset=problem_set + ).order_by("-join_time") serializer = ProblemSetProgressSerializer(progress_list, many=True) return self.success(serializer.data) diff --git a/problemset/views/oj.py b/problemset/views/oj.py index da489ce..562156f 100644 --- a/problemset/views/oj.py +++ b/problemset/views/oj.py @@ -1,5 +1,6 @@ from django.db.models import Q +from django.db import models from django.utils import timezone from utils.api import APIView, validate_serializer @@ -10,30 +11,27 @@ from problemset.models import ( ProblemSetBadge, ProblemSetProgress, UserBadge, + ProblemSetSubmission, ) from problemset.serializers import ( ProblemSetSerializer, ProblemSetListSerializer, - CreateProblemSetSerializer, - EditProblemSetSerializer, ProblemSetProblemSerializer, - AddProblemToSetSerializer, ProblemSetBadgeSerializer, - CreateProblemSetBadgeSerializer, ProblemSetProgressSerializer, UserBadgeSerializer, JoinProblemSetSerializer, UpdateProgressSerializer, + ProblemSetSubmissionSerializer, ) -from problem.models import Problem class ProblemSetAPI(APIView): - """题单API""" + """题单API - 用户端""" def get(self, request): """获取题单列表""" - problem_sets = ProblemSet.objects.filter(visible=True) + problem_sets = ProblemSet.objects.filter(visible=True).exclude(status="draft") # 过滤条件 keyword = request.GET.get("keyword", "").strip() @@ -50,8 +48,6 @@ class ProblemSetAPI(APIView): if status_filter: problem_sets = problem_sets.filter(status=status_filter) - # 所有用户都可以看到可见的题单 - # 排序 sort = request.GET.get("sort") if sort: @@ -62,78 +58,31 @@ class ProblemSetAPI(APIView): data = self.paginate_data(request, problem_sets, ProblemSetListSerializer) return self.success(data) - @validate_serializer(CreateProblemSetSerializer) - def post(self, request): - """创建题单""" - data = request.data - data["created_by"] = request.user - problem_set = ProblemSet.objects.create(**data) - return self.success(ProblemSetSerializer(problem_set).data) - class ProblemSetDetailAPI(APIView): - """题单详情API""" + """题单详情API - 用户端""" def get(self, request, problem_set_id): """获取题单详情""" try: - problem_set = ProblemSet.objects.get(id=problem_set_id, visible=True) + problem_set = ProblemSet.objects.filter(id=problem_set_id, visible=True).exclude(status="draft").get() except ProblemSet.DoesNotExist: return self.error("题单不存在") - # 题单可见即可访问 serializer = ProblemSetSerializer(problem_set, context={"request": request}) return self.success(serializer.data) - @validate_serializer(EditProblemSetSerializer) - def put(self, request, problem_set_id): - """编辑题单""" - try: - problem_set = ProblemSet.objects.get(id=problem_set_id) - except ProblemSet.DoesNotExist: - return self.error("题单不存在") - - # 检查权限 - if not request.user.is_admin_role() and problem_set.created_by != request.user: - return self.error("无权限编辑该题单") - - data = request.data - for key, value in data.items(): - if key != "id": - setattr(problem_set, key, value) - problem_set.save() - - return self.success(ProblemSetSerializer(problem_set).data) - - def delete(self, request, problem_set_id): - """删除题单""" - try: - problem_set = ProblemSet.objects.get(id=problem_set_id) - except ProblemSet.DoesNotExist: - return self.error("题单不存在") - - # 检查权限 - if not request.user.is_admin_role() and problem_set.created_by != request.user: - return self.error("无权限删除该题单") - - problem_set.visible = False - problem_set.save() - - return self.success("题单已删除") - class ProblemSetProblemAPI(APIView): - """题单题目管理API""" + """题单题目API - 用户端""" def get(self, request, problem_set_id): """获取题单中的题目列表""" try: - problem_set = ProblemSet.objects.get(id=problem_set_id, visible=True) + problem_set = ProblemSet.objects.filter(id=problem_set_id, visible=True).exclude(status="draft").get() except ProblemSet.DoesNotExist: return self.error("题单不存在") - # 题单可见即可访问 - problems = ProblemSetProblem.objects.filter(problemset=problem_set).order_by( "order" ) @@ -142,61 +91,6 @@ class ProblemSetProblemAPI(APIView): ) return self.success(serializer.data) - @validate_serializer(AddProblemToSetSerializer) - def post(self, request, problem_set_id): - """添加题目到题单""" - try: - problem_set = ProblemSet.objects.get(id=problem_set_id) - except ProblemSet.DoesNotExist: - return self.error("题单不存在") - - # 检查权限 - if not request.user.is_admin_role() and problem_set.created_by != request.user: - return self.error("无权限管理该题单") - - data = request.data - try: - problem = Problem.objects.get(id=data["problem_id"]) - except Problem.DoesNotExist: - return self.error("题目不存在") - - # 检查题目是否已经在题单中 - if ProblemSetProblem.objects.filter( - problemset=problem_set, problem=problem - ).exists(): - return self.error("题目已在该题单中") - - ProblemSetProblem.objects.create( - problemset=problem_set, - problem=problem, - order=data.get("order", 0), - is_required=data.get("is_required", True), - score=data.get("score", 0), - hint=data.get("hint", ""), - ) - - return self.success("题目已添加到题单") - - def delete(self, request, problem_set_id, problem_id): - """从题单中移除题目""" - try: - problem_set = ProblemSet.objects.get(id=problem_set_id) - except ProblemSet.DoesNotExist: - return self.error("题单不存在") - - # 检查权限 - if not request.user.is_admin_role() and problem_set.created_by != request.user: - return self.error("无权限管理该题单") - - try: - problem_set_problem = ProblemSetProblem.objects.get( - problemset=problem_set, problem_id=problem_id - ) - problem_set_problem.delete() - return self.success("题目已从题单中移除") - except ProblemSetProblem.DoesNotExist: - return self.error("题目不在该题单中") - class ProblemSetProgressAPI(APIView): """题单进度API""" @@ -206,13 +100,10 @@ class ProblemSetProgressAPI(APIView): """加入题单""" data = request.data try: - problem_set = ProblemSet.objects.get(id=data["problemset_id"], visible=True) + problem_set = ProblemSet.objects.filter(id=data["problemset_id"], visible=True).exclude(status="draft").get() except ProblemSet.DoesNotExist: return self.error("题单不存在") - # 题单可见即可加入 - - # 检查是否已经加入 if ProblemSetProgress.objects.filter( problemset=problem_set, user=request.user ).exists(): @@ -229,7 +120,7 @@ class ProblemSetProgressAPI(APIView): def get(self, request, problem_set_id): """获取题单进度""" try: - problem_set = ProblemSet.objects.get(id=problem_set_id, visible=True) + problem_set = ProblemSet.objects.filter(id=problem_set_id, visible=True).exclude(status="draft").get() except ProblemSet.DoesNotExist: return self.error("题单不存在") @@ -248,7 +139,7 @@ class ProblemSetProgressAPI(APIView): """更新进度""" data = request.data try: - problem_set = ProblemSet.objects.get(id=data["problemset_id"], visible=True) + problem_set = ProblemSet.objects.filter(id=data["problemset_id"], visible=True).exclude(status="draft").get() except ProblemSet.DoesNotExist: return self.error("题单不存在") @@ -329,12 +220,12 @@ class UserBadgeAPI(APIView): class ProblemSetBadgeAPI(APIView): - """题单奖章管理API""" + """题单奖章API - 用户端""" def get(self, request, problem_set_id): """获取题单的奖章列表""" try: - problem_set = ProblemSet.objects.get(id=problem_set_id, visible=True) + problem_set = ProblemSet.objects.filter(id=problem_set_id, visible=True).exclude(status="draft").get() except ProblemSet.DoesNotExist: return self.error("题单不存在") @@ -342,20 +233,125 @@ class ProblemSetBadgeAPI(APIView): serializer = ProblemSetBadgeSerializer(badges, many=True) return self.success(serializer.data) - @validate_serializer(CreateProblemSetBadgeSerializer) - def post(self, request, problem_set_id): - """创建题单奖章""" + +class ProblemSetSubmissionAPI(APIView): + """题单提交记录API - 用户端""" + + def get(self, request, problem_set_id): + """获取用户在题单中的提交记录""" try: - problem_set = ProblemSet.objects.get(id=problem_set_id) + problem_set = ProblemSet.objects.filter(id=problem_set_id, visible=True).exclude(status="draft").get() except ProblemSet.DoesNotExist: return self.error("题单不存在") - # 检查权限 - if not request.user.is_admin_role() and problem_set.created_by != request.user: - return self.error("无权限管理该题单") + # 检查用户是否已加入该题单 + try: + ProblemSetProgress.objects.get(problemset=problem_set, user=request.user) + except ProblemSetProgress.DoesNotExist: + return self.error("您还未加入该题单") - data = request.data - data["problemset"] = problem_set - badge = ProblemSetBadge.objects.create(**data) + # 获取查询参数 + 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 + } + + if problem_id: + query_filter["problem_id"] = problem_id + if result: + query_filter["result"] = result + if language: + query_filter["language"] = language - return self.success(ProblemSetBadgeSerializer(badge).data) + # 获取提交记录 + submissions = ProblemSetSubmission.objects.filter(**query_filter).order_by("-submit_time") + + # 分页 + data = self.paginate_data(request, submissions, ProblemSetSubmissionSerializer) + return self.success(data) + + +class ProblemSetStatisticsAPI(APIView): + """题单统计API - 用户端""" + + def get(self, request, problem_set_id): + """获取题单统计信息""" + try: + 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) + except ProblemSetProgress.DoesNotExist: + return self.error("您还未加入该题单") + + # 获取统计信息 + 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 + ).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 + ) + + 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" + } + + # 按语言统计 + language_stats = {} + language_submissions = ProblemSetSubmission.objects.filter( + problemset=problem_set, user=request.user + ).values('language').annotate(count=models.Count('id')) + + for item in language_submissions: + language_stats[item['language']] = item['count'] + + # 按结果统计 + result_stats = {} + result_submissions = ProblemSetSubmission.objects.filter( + problemset=problem_set, user=request.user + ).values('result').annotate(count=models.Count('id')) + + for item in result_submissions: + result_stats[item['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, + "problem_stats": problem_stats, + "language_stats": language_stats, + "result_stats": result_stats, + "progress": { + "completed_problems_count": progress.completed_problems_count, + "total_problems_count": progress.total_problems_count, + "progress_percentage": progress.progress_percentage, + "total_score": progress.total_score + } + } + + return self.success(data) diff --git a/utils/api/api.py b/utils/api/api.py index a603bfb..18b4752 100644 --- a/utils/api/api.py +++ b/utils/api/api.py @@ -132,7 +132,7 @@ class APIView(View): results = query_set[offset:offset + limit] if object_serializer: count = query_set.count() - results = object_serializer(results, many=True).data + results = object_serializer(results, many=True, context={"request": request}).data else: count = query_set.count() data = {"results": results,