This commit is contained in:
2025-10-23 00:54:06 +08:00
parent f4318a069d
commit 32a608476d
11 changed files with 408 additions and 222 deletions

View File

@@ -6,8 +6,8 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATABASES = { DATABASES = {
"default": { "default": {
"ENGINE": "django.db.backends.postgresql", "ENGINE": "django.db.backends.postgresql",
"HOST": "10.13.114.114", "HOST": "150.158.29.156",
"PORT": "5433", "PORT": "5455",
"NAME": "onlinejudge", "NAME": "onlinejudge",
"USER": "onlinejudge", "USER": "onlinejudge",
"PASSWORD": "onlinejudge", "PASSWORD": "onlinejudge",
@@ -15,8 +15,8 @@ DATABASES = {
} }
REDIS_CONF = { REDIS_CONF = {
"host": "10.13.114.114", "host": "150.158.29.156",
"port": 6379, "port": 5456,
} }

View File

@@ -184,7 +184,6 @@ class ProblemListSerializer(BaseProblemSerializer):
"created_by", "created_by",
"tags", "tags",
"contest", "contest",
"rule_type",
"allow_flowchart", "allow_flowchart",
] ]

View File

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

View File

@@ -24,7 +24,7 @@ class ProblemSet(models.Model):
difficulty = models.TextField(default="Easy", verbose_name="难度等级") difficulty = models.TextField(default="Easy", verbose_name="难度等级")
# 题单状态 # 题单状态
status = models.TextField( status = models.TextField(
default="active", verbose_name="状态" default="draft", verbose_name="状态"
) # active, archived, draft ) # active, archived, draft
class Meta: class Meta:
@@ -164,6 +164,49 @@ class ProblemSetProgress(models.Model):
self.save() 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): class UserBadge(models.Model):
"""用户奖章模型""" """用户奖章模型"""

View File

@@ -5,15 +5,48 @@ from .models import (
ProblemSetBadge, ProblemSetBadge,
ProblemSetProgress, ProblemSetProgress,
UserBadge, 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): class ProblemSetSerializer(serializers.ModelSerializer):
"""题单序列化器""" """题单序列化器"""
created_by = UsernameSerializer() created_by = UsernameSerializer()
problems_count = serializers.SerializerMethodField() problems_count = serializers.SerializerMethodField()
completed_count = serializers.SerializerMethodField() completed_count = serializers.SerializerMethodField()
user_progress = serializers.SerializerMethodField()
class Meta: class Meta:
model = ProblemSet model = ProblemSet
@@ -36,6 +69,11 @@ class ProblemSetSerializer(serializers.ModelSerializer):
return 0 return 0
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): class ProblemSetListSerializer(serializers.ModelSerializer):
"""题单列表序列化器""" """题单列表序列化器"""
@@ -43,6 +81,7 @@ class ProblemSetListSerializer(serializers.ModelSerializer):
created_by = UsernameSerializer() created_by = UsernameSerializer()
problems_count = serializers.SerializerMethodField() problems_count = serializers.SerializerMethodField()
user_progress = serializers.SerializerMethodField() user_progress = serializers.SerializerMethodField()
badges = serializers.SerializerMethodField()
class Meta: class Meta:
model = ProblemSet model = ProblemSet
@@ -56,6 +95,7 @@ class ProblemSetListSerializer(serializers.ModelSerializer):
"status", "status",
"problems_count", "problems_count",
"user_progress", "user_progress",
"badges",
"visible", "visible",
] ]
@@ -66,33 +106,12 @@ class ProblemSetListSerializer(serializers.ModelSerializer):
def get_user_progress(self, obj): def get_user_progress(self, obj):
"""获取当前用户在该题单中的进度""" """获取当前用户在该题单中的进度"""
request = self.context.get("request") request = self.context.get("request")
if request and request.user.is_authenticated: return get_user_progress_data(obj, request)
try:
progress = ProblemSetProgress.objects.get( def get_badges(self, obj):
problemset=obj, user=request.user """获取题单的奖章列表"""
) badges = ProblemSetBadge.objects.filter(problemset=obj)
return { return ProblemSetBadgeSerializer(badges, many=True).data
"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 CreateProblemSetSerializer(serializers.Serializer): class CreateProblemSetSerializer(serializers.Serializer):
@@ -126,9 +145,9 @@ class ProblemSetProblemSerializer(serializers.ModelSerializer):
def get_problem(self, obj): 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): class AddProblemToSetSerializer(serializers.Serializer):
@@ -211,3 +230,46 @@ class UpdateProgressSerializer(serializers.Serializer):
status = serializers.CharField() # completed, attempted, not_started status = serializers.CharField() # completed, attempted, not_started
score = serializers.IntegerField(default=0) score = serializers.IntegerField(default=0)
submit_time = serializers.DateTimeField(required=False) 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, "未知")

View File

@@ -2,14 +2,15 @@ from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.utils import timezone from django.utils import timezone
from .models import ProblemSetProgress, ProblemSetBadge, UserBadge from .models import ProblemSetProgress, ProblemSetBadge, UserBadge, ProblemSetSubmission
from submission.models import Submission from submission.models import Submission
@receiver(post_save, sender=Submission) @receiver(post_save, sender=Submission)
def update_problemset_progress(sender, instance, created, **kwargs): def update_problemset_progress(sender, instance, created, **kwargs):
"""当提交状态更新时,自动更新题单进度""" """当提交状态更新时,自动更新题单进度"""
if not created: # 处理更新,不处理新建 # 处理新建和更新但只在提交状态确定时更新不是Pending状态
if instance.result == 6: # 6表示PENDING状态不更新进度
return return
# 检查该提交是否属于某个题单中的题目 # 检查该提交是否属于某个题单中的题目
@@ -24,14 +25,34 @@ def update_problemset_progress(sender, instance, created, **kwargs):
problemset=psp.problemset, user=instance.user 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) problem_id = str(instance.problem.id)
# 确定题目状态
if instance.result == 0: # ACCEPTED
status = "completed" # 部分通过也算完成
else: # 其他状态(错误、超时等)
status = "attempted"
progress.progress_detail[problem_id] = { progress.progress_detail[problem_id] = {
"status": "completed" "status": status,
if instance.result == 0
else "attempted", # 0表示AC
"score": instance.score if hasattr(instance, "score") else 0, "score": instance.score if hasattr(instance, "score") else 0,
"submit_time": timezone.now().isoformat(), "submit_time": timezone.now().isoformat(),
"result": instance.result, # 保存原始结果代码
} }
# 更新进度 # 更新进度

View File

@@ -12,52 +12,52 @@ from problemset.views.admin import (
urlpatterns = [ urlpatterns = [
# 管理员题单管理API # 管理员题单管理API
path("problemset/", ProblemSetAdminAPI.as_view(), name="admin_problemset_api"), path("problemset", ProblemSetAdminAPI.as_view(), name="admin_problemset_api"),
path( path(
"problemset/<int:problem_set_id>/", "problemset/<int:problem_set_id>",
ProblemSetDetailAdminAPI.as_view(), ProblemSetDetailAdminAPI.as_view(),
name="admin_problemset_detail_api", name="admin_problemset_detail_api",
), ),
path( path(
"problemset/<int:problem_set_id>/problems/", "problemset/<int:problem_set_id>/problems",
ProblemSetProblemAdminAPI.as_view(), ProblemSetProblemAdminAPI.as_view(),
name="admin_problemset_problems_api", name="admin_problemset_problems_api",
), ),
path( path(
"problemset/<int:problem_set_id>/problems/<int:problem_set_problem_id>/", "problemset/<int:problem_set_id>/problems/<int:problem_set_problem_id>",
ProblemSetProblemAdminAPI.as_view(), ProblemSetProblemAdminAPI.as_view(),
name="admin_problemset_problem_detail_api", name="admin_problemset_problem_detail_api",
), ),
# 管理员奖章管理API # 管理员奖章管理API
path( path(
"problemset/<int:problem_set_id>/badges/", "problemset/<int:problem_set_id>/badges",
ProblemSetBadgeAdminAPI.as_view(), ProblemSetBadgeAdminAPI.as_view(),
name="admin_problemset_badges_api", name="admin_problemset_badges_api",
), ),
path( path(
"problemset/<int:problem_set_id>/badges/<int:badge_id>/", "problemset/<int:problem_set_id>/badges/<int:badge_id>",
ProblemSetBadgeAdminAPI.as_view(), ProblemSetBadgeAdminAPI.as_view(),
name="admin_problemset_badge_detail_api", name="admin_problemset_badge_detail_api",
), ),
# 管理员进度管理API # 管理员进度管理API
path( path(
"problemset/<int:problem_set_id>/progress/", "problemset/<int:problem_set_id>/progress",
ProblemSetProgressAdminAPI.as_view(), ProblemSetProgressAdminAPI.as_view(),
name="admin_problemset_progress_api", name="admin_problemset_progress_api",
), ),
path( path(
"problemset/<int:problem_set_id>/progress/<int:user_id>/", "problemset/<int:problem_set_id>/progress/<int:user_id>",
ProblemSetProgressAdminAPI.as_view(), ProblemSetProgressAdminAPI.as_view(),
name="admin_problemset_progress_detail_api", name="admin_problemset_progress_detail_api",
), ),
# 题单状态管理API # 题单状态管理API
path( path(
"problemset/visible/", "problemset/visible",
ProblemSetVisibleAPI.as_view(), ProblemSetVisibleAPI.as_view(),
name="admin_problemset_visible_api", name="admin_problemset_visible_api",
), ),
path( path(
"problemset/status/", "problemset/status",
ProblemSetStatusAPI.as_view(), ProblemSetStatusAPI.as_view(),
name="admin_problemset_status_api", name="admin_problemset_status_api",
), ),

View File

@@ -7,48 +7,62 @@ from problemset.views.oj import (
UserBadgeAPI, UserBadgeAPI,
UserProgressAPI, UserProgressAPI,
ProblemSetBadgeAPI, ProblemSetBadgeAPI,
ProblemSetSubmissionAPI,
ProblemSetStatisticsAPI,
) )
urlpatterns = [ urlpatterns = [
# 题单相关API # 题单相关API
path("api/problemset/", ProblemSetAPI.as_view(), name="problemset_api"), path("problemset", ProblemSetAPI.as_view(), name="problemset_api"),
path( path(
"api/problemset/<int:problem_set_id>/", "problemset/<int:problem_set_id>",
ProblemSetDetailAPI.as_view(), ProblemSetDetailAPI.as_view(),
name="problemset_detail_api", name="problemset_detail_api",
), ),
path( path(
"api/problemset/<int:problem_set_id>/problems/", "problemset/<int:problem_set_id>/problems",
ProblemSetProblemAPI.as_view(), ProblemSetProblemAPI.as_view(),
name="problemset_problems_api", name="problemset_problems_api",
), ),
path( path(
"api/problemset/<int:problem_set_id>/problems/<int:problem_id>/", "problemset/<int:problem_set_id>/problems/<int:problem_id>",
ProblemSetProblemAPI.as_view(), ProblemSetProblemAPI.as_view(),
name="problemset_problem_detail_api", name="problemset_problem_detail_api",
), ),
# 进度相关API # 进度相关API
path( path(
"api/problemset/progress/", "problemset/progress",
ProblemSetProgressAPI.as_view(), ProblemSetProgressAPI.as_view(),
name="problemset_progress_api", name="problemset_progress_api",
), ),
path( path(
"api/problemset/<int:problem_set_id>/progress/", "problemset/<int:problem_set_id>/progress",
ProblemSetProgressAPI.as_view(), ProblemSetProgressAPI.as_view(),
name="problemset_progress_detail_api", 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 # 奖章相关API
path("api/user/badges/", UserBadgeAPI.as_view(), name="user_badges_api"), path("user/badges", UserBadgeAPI.as_view(), name="user_badges_api"),
path( path(
"api/user/badges/<int:badge_id>/", "user/badges/<int:badge_id>",
UserBadgeAPI.as_view(), UserBadgeAPI.as_view(),
name="user_badge_detail_api", name="user_badge_detail_api",
), ),
path( path(
"api/problemset/<int:problem_set_id>/badges/", "problemset/<int:problem_set_id>/badges",
ProblemSetBadgeAPI.as_view(), ProblemSetBadgeAPI.as_view(),
name="problemset_badges_api", name="problemset_badges_api",
), ),
# 提交记录相关API
path(
"problemset/<int:problem_set_id>/submissions",
ProblemSetSubmissionAPI.as_view(),
name="problemset_submissions_api",
),
# 统计相关API
path(
"problemset/<int:problem_set_id>/statistics",
ProblemSetStatisticsAPI.as_view(),
name="problemset_statistics_api",
),
] ]

View File

@@ -154,9 +154,13 @@ class ProblemSetProblemAdminAPI(APIView):
data = request.data data = request.data
try: 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: except Problem.DoesNotExist:
return self.error("题目不存在") return self.error("题目不存在或不可见")
# 检查题目是否已经在题单中 # 检查题目是否已经在题单中
if ProblemSetProblem.objects.filter( if ProblemSetProblem.objects.filter(
@@ -194,14 +198,14 @@ class ProblemSetProblemAdminAPI(APIView):
data = request.data data = request.data
# 更新题目属性 # 更新题目属性
if 'order' in data: if "order" in data:
problem_set_problem.order = data['order'] problem_set_problem.order = data["order"]
if 'is_required' in data: if "is_required" in data:
problem_set_problem.is_required = data['is_required'] problem_set_problem.is_required = data["is_required"]
if 'score' in data: if "score" in data:
problem_set_problem.score = data['score'] problem_set_problem.score = data["score"]
if 'hint' in data: if "hint" in data:
problem_set_problem.hint = data['hint'] problem_set_problem.hint = data["hint"]
problem_set_problem.save() problem_set_problem.save()
return self.success("题目已更新") return self.success("题目已更新")
@@ -274,18 +278,18 @@ class ProblemSetBadgeAdminAPI(APIView):
data = request.data data = request.data
# 更新奖章属性 # 更新奖章属性
if 'name' in data: if "name" in data:
badge.name = data['name'] badge.name = data["name"]
if 'description' in data: if "description" in data:
badge.description = data['description'] badge.description = data["description"]
if 'icon' in data: if "icon" in data:
badge.icon = data['icon'] badge.icon = data["icon"]
if 'condition_type' in data: if "condition_type" in data:
badge.condition_type = data['condition_type'] badge.condition_type = data["condition_type"]
if 'condition_value' in data: if "condition_value" in data:
badge.condition_value = data['condition_value'] badge.condition_value = data["condition_value"]
if 'level' in data: if "level" in data:
badge.level = data['level'] badge.level = data["level"]
badge.save() badge.save()
return self.success("奖章已更新") return self.success("奖章已更新")
@@ -319,9 +323,9 @@ class ProblemSetProgressAdminAPI(APIView):
except ProblemSet.DoesNotExist: except ProblemSet.DoesNotExist:
return self.error("题单不存在") return self.error("题单不存在")
progress_list = ProblemSetProgress.objects.filter(problemset=problem_set).order_by( progress_list = ProblemSetProgress.objects.filter(
"-join_time" problemset=problem_set
) ).order_by("-join_time")
serializer = ProblemSetProgressSerializer(progress_list, many=True) serializer = ProblemSetProgressSerializer(progress_list, many=True)
return self.success(serializer.data) return self.success(serializer.data)

View File

@@ -1,5 +1,6 @@
from django.db.models import Q from django.db.models import Q
from django.db import models
from django.utils import timezone from django.utils import timezone
from utils.api import APIView, validate_serializer from utils.api import APIView, validate_serializer
@@ -10,30 +11,27 @@ from problemset.models import (
ProblemSetBadge, ProblemSetBadge,
ProblemSetProgress, ProblemSetProgress,
UserBadge, UserBadge,
ProblemSetSubmission,
) )
from problemset.serializers import ( from problemset.serializers import (
ProblemSetSerializer, ProblemSetSerializer,
ProblemSetListSerializer, ProblemSetListSerializer,
CreateProblemSetSerializer,
EditProblemSetSerializer,
ProblemSetProblemSerializer, ProblemSetProblemSerializer,
AddProblemToSetSerializer,
ProblemSetBadgeSerializer, ProblemSetBadgeSerializer,
CreateProblemSetBadgeSerializer,
ProblemSetProgressSerializer, ProblemSetProgressSerializer,
UserBadgeSerializer, UserBadgeSerializer,
JoinProblemSetSerializer, JoinProblemSetSerializer,
UpdateProgressSerializer, UpdateProgressSerializer,
ProblemSetSubmissionSerializer,
) )
from problem.models import Problem
class ProblemSetAPI(APIView): class ProblemSetAPI(APIView):
"""题单API""" """题单API - 用户端"""
def get(self, request): 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() keyword = request.GET.get("keyword", "").strip()
@@ -50,8 +48,6 @@ class ProblemSetAPI(APIView):
if status_filter: if status_filter:
problem_sets = problem_sets.filter(status=status_filter) problem_sets = problem_sets.filter(status=status_filter)
# 所有用户都可以看到可见的题单
# 排序 # 排序
sort = request.GET.get("sort") sort = request.GET.get("sort")
if sort: if sort:
@@ -62,78 +58,31 @@ class ProblemSetAPI(APIView):
data = self.paginate_data(request, problem_sets, ProblemSetListSerializer) data = self.paginate_data(request, problem_sets, ProblemSetListSerializer)
return self.success(data) 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): class ProblemSetDetailAPI(APIView):
"""题单详情API""" """题单详情API - 用户端"""
def get(self, request, problem_set_id): def get(self, request, problem_set_id):
"""获取题单详情""" """获取题单详情"""
try: 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: except ProblemSet.DoesNotExist:
return self.error("题单不存在") return self.error("题单不存在")
# 题单可见即可访问
serializer = ProblemSetSerializer(problem_set, context={"request": request}) serializer = ProblemSetSerializer(problem_set, context={"request": request})
return self.success(serializer.data) 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): class ProblemSetProblemAPI(APIView):
"""题单题目管理API""" """题单题目API - 用户端"""
def get(self, request, problem_set_id): def get(self, request, problem_set_id):
"""获取题单中的题目列表""" """获取题单中的题目列表"""
try: 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: except ProblemSet.DoesNotExist:
return self.error("题单不存在") return self.error("题单不存在")
# 题单可见即可访问
problems = ProblemSetProblem.objects.filter(problemset=problem_set).order_by( problems = ProblemSetProblem.objects.filter(problemset=problem_set).order_by(
"order" "order"
) )
@@ -142,61 +91,6 @@ class ProblemSetProblemAPI(APIView):
) )
return self.success(serializer.data) 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): class ProblemSetProgressAPI(APIView):
"""题单进度API""" """题单进度API"""
@@ -206,13 +100,10 @@ class ProblemSetProgressAPI(APIView):
"""加入题单""" """加入题单"""
data = request.data data = request.data
try: 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: except ProblemSet.DoesNotExist:
return self.error("题单不存在") return self.error("题单不存在")
# 题单可见即可加入
# 检查是否已经加入
if ProblemSetProgress.objects.filter( if ProblemSetProgress.objects.filter(
problemset=problem_set, user=request.user problemset=problem_set, user=request.user
).exists(): ).exists():
@@ -229,7 +120,7 @@ class ProblemSetProgressAPI(APIView):
def get(self, request, problem_set_id): def get(self, request, problem_set_id):
"""获取题单进度""" """获取题单进度"""
try: 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: except ProblemSet.DoesNotExist:
return self.error("题单不存在") return self.error("题单不存在")
@@ -248,7 +139,7 @@ class ProblemSetProgressAPI(APIView):
"""更新进度""" """更新进度"""
data = request.data data = request.data
try: 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: except ProblemSet.DoesNotExist:
return self.error("题单不存在") return self.error("题单不存在")
@@ -329,12 +220,12 @@ class UserBadgeAPI(APIView):
class ProblemSetBadgeAPI(APIView): class ProblemSetBadgeAPI(APIView):
"""题单奖章管理API""" """题单奖章API - 用户端"""
def get(self, request, problem_set_id): def get(self, request, problem_set_id):
"""获取题单的奖章列表""" """获取题单的奖章列表"""
try: 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: except ProblemSet.DoesNotExist:
return self.error("题单不存在") return self.error("题单不存在")
@@ -342,20 +233,125 @@ class ProblemSetBadgeAPI(APIView):
serializer = ProblemSetBadgeSerializer(badges, many=True) serializer = ProblemSetBadgeSerializer(badges, many=True)
return self.success(serializer.data) 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: 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: except ProblemSet.DoesNotExist:
return self.error("题单不存在") return self.error("题单不存在")
# 检查权限 # 检查用户是否已加入该题单
if not request.user.is_admin_role() and problem_set.created_by != request.user: try:
return self.error("无权限管理该题单") ProblemSetProgress.objects.get(problemset=problem_set, user=request.user)
except ProblemSetProgress.DoesNotExist:
return self.error("您还未加入该题单")
data = request.data # 获取查询参数
data["problemset"] = problem_set problem_id = request.GET.get("problem_id")
badge = ProblemSetBadge.objects.create(**data) result = request.GET.get("result")
language = request.GET.get("language")
return self.success(ProblemSetBadgeSerializer(badge).data) # 构建查询条件
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
# 获取提交记录
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)

View File

@@ -132,7 +132,7 @@ class APIView(View):
results = query_set[offset:offset + limit] results = query_set[offset:offset + limit]
if object_serializer: if object_serializer:
count = query_set.count() count = query_set.count()
results = object_serializer(results, many=True).data results = object_serializer(results, many=True, context={"request": request}).data
else: else:
count = query_set.count() count = query_set.count()
data = {"results": results, data = {"results": results,