add problemset

This commit is contained in:
2025-10-22 18:47:40 +08:00
parent 9235b111c0
commit 07aaff69f2
16 changed files with 1404 additions and 4 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": "150.158.29.156", "HOST": "10.13.114.114",
"PORT": "5445", "PORT": "5433",
"NAME": "onlinejudge", "NAME": "onlinejudge",
"USER": "onlinejudge", "USER": "onlinejudge",
"PASSWORD": "onlinejudge", "PASSWORD": "onlinejudge",
@@ -15,8 +15,8 @@ DATABASES = {
} }
REDIS_CONF = { REDIS_CONF = {
"host": "150.158.29.156", "host": "10.13.114.114",
"port": 5446, "port": 6379,
} }

View File

@@ -59,6 +59,7 @@ LOCAL_APPS = [
"tutorial", "tutorial",
"ai", "ai",
"flowchart", "flowchart",
"problemset",
] ]
INSTALLED_APPS = VENDOR_APPS + LOCAL_APPS INSTALLED_APPS = VENDOR_APPS + LOCAL_APPS

View File

@@ -21,4 +21,6 @@ urlpatterns = [
path("api/admin/", include("tutorial.urls.admin")), path("api/admin/", include("tutorial.urls.admin")),
path("api/", include("ai.urls.oj")), path("api/", include("ai.urls.oj")),
path("api/", include("flowchart.urls.oj")), path("api/", include("flowchart.urls.oj")),
path("api/", include("problemset.urls.oj")),
path("api/admin/", include("problemset.urls.admin")),
] ]

0
problemset/__init__.py Normal file
View File

9
problemset/apps.py Normal file
View File

@@ -0,0 +1,9 @@
from django.apps import AppConfig
class ProblemsetConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'problemset'
def ready(self):
import problemset.signals

View File

@@ -0,0 +1,115 @@
# Generated by Django 5.2.3 on 2025-10-22 10:27
import django.db.models.deletion
import utils.models
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('problem', '0005_remove_spj_fields'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='ProblemSet',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.TextField(verbose_name='题单标题')),
('description', utils.models.RichTextField(verbose_name='题单描述')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('last_update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('visible', models.BooleanField(default=True, verbose_name='是否可见')),
('is_public', models.BooleanField(default=True, verbose_name='是否公开')),
('difficulty', models.TextField(default='Easy', verbose_name='难度等级')),
('status', models.TextField(default='active', verbose_name='状态')),
('created_by', 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',
'ordering': ('-create_time',),
},
),
migrations.CreateModel(
name='ProblemSetBadge',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.TextField(verbose_name='奖章名称')),
('description', models.TextField(verbose_name='奖章描述')),
('icon', models.TextField(verbose_name='奖章图标')),
('condition_type', models.TextField(verbose_name='获得条件类型')),
('condition_value', models.IntegerField(default=0, verbose_name='条件值')),
('level', models.IntegerField(default=1, verbose_name='奖章等级')),
('problemset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='problemset.problemset', verbose_name='题单')),
],
options={
'verbose_name': '题单奖章',
'verbose_name_plural': '题单奖章',
'db_table': 'problemset_badge',
},
),
migrations.CreateModel(
name='ProblemSetProblem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.IntegerField(default=0, verbose_name='顺序')),
('is_required', models.BooleanField(default=True, verbose_name='是否必做')),
('score', models.IntegerField(default=0, verbose_name='分值')),
('hint', models.TextField(blank=True, null=True, 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='题单')),
],
options={
'verbose_name': '题单题目',
'verbose_name_plural': '题单题目',
'db_table': 'problemset_problem',
'ordering': ('order',),
'unique_together': {('problemset', 'problem')},
},
),
migrations.CreateModel(
name='ProblemSetProgress',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('join_time', models.DateTimeField(auto_now_add=True, verbose_name='加入时间')),
('complete_time', models.DateTimeField(blank=True, null=True, verbose_name='完成时间')),
('is_completed', models.BooleanField(default=False, verbose_name='是否完成')),
('progress_percentage', models.FloatField(default=0.0, verbose_name='完成进度')),
('completed_problems_count', models.IntegerField(default=0, verbose_name='已完成题目数')),
('total_problems_count', models.IntegerField(default=0, verbose_name='总题目数')),
('total_score', models.IntegerField(default=0, verbose_name='总分')),
('progress_detail', models.JSONField(default=dict, verbose_name='详细进度')),
('problemset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='problemset.problemset', 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_progress',
'unique_together': {('problemset', 'user')},
},
),
migrations.CreateModel(
name='UserBadge',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('earned_time', models.DateTimeField(auto_now_add=True, verbose_name='获得时间')),
('is_displayed', models.BooleanField(default=False, verbose_name='是否已展示')),
('badge', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='problemset.problemsetbadge', 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': 'user_badge',
'unique_together': {('user', 'badge')},
},
),
]

View File

190
problemset/models.py Normal file
View File

@@ -0,0 +1,190 @@
from django.db import models
from django.utils.timezone import now
from account.models import User
from problem.models import Problem
from utils.models import RichTextField, JSONField
class ProblemSet(models.Model):
"""题单模型"""
title = models.TextField(verbose_name="题单标题")
description = RichTextField(verbose_name="题单描述")
# 创建者
created_by = models.ForeignKey(
User, on_delete=models.CASCADE, verbose_name="创建者"
)
# 创建时间
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
# 更新时间
last_update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
# 是否可见
visible = models.BooleanField(default=True, verbose_name="是否可见")
# 是否公开(所有用户都可以看到)
is_public = models.BooleanField(default=True, verbose_name="是否公开")
# 题单难度等级
difficulty = models.TextField(default="Easy", verbose_name="难度等级")
# 题单状态
status = models.TextField(
default="active", verbose_name="状态"
) # active, archived, draft
class Meta:
db_table = "problemset"
ordering = ("-create_time",)
verbose_name = "题单"
verbose_name_plural = "题单"
def __str__(self):
return self.title
class ProblemSetProblem(models.Model):
"""题单题目关联模型"""
problemset = models.ForeignKey(
ProblemSet, on_delete=models.CASCADE, verbose_name="题单"
)
problem = models.ForeignKey(Problem, on_delete=models.CASCADE, verbose_name="题目")
# 在题单中的顺序
order = models.IntegerField(default=0, verbose_name="顺序")
# 是否为必做题
is_required = models.BooleanField(default=True, verbose_name="是否必做")
# 题目在题单中的分值
score = models.IntegerField(default=0, verbose_name="分值")
# 题目提示信息
hint = models.TextField(null=True, blank=True, verbose_name="提示")
class Meta:
db_table = "problemset_problem"
unique_together = (("problemset", "problem"),)
ordering = ("order",)
verbose_name = "题单题目"
verbose_name_plural = "题单题目"
def __str__(self):
return f"{self.problemset.title} - {self.problem.title}"
class ProblemSetBadge(models.Model):
"""题单奖章模型"""
problemset = models.ForeignKey(
ProblemSet, on_delete=models.CASCADE, verbose_name="题单"
)
name = models.TextField(verbose_name="奖章名称")
description = models.TextField(verbose_name="奖章描述")
# 奖章图标路径
icon = models.TextField(verbose_name="奖章图标")
# 获得条件:完成所有题目、完成指定数量题目、达到指定分数等
condition_type = models.TextField(
verbose_name="获得条件类型"
) # all_problems, problem_count, score
condition_value = models.IntegerField(default=0, verbose_name="条件值")
# 奖章等级
level = models.IntegerField(default=1, verbose_name="奖章等级")
class Meta:
db_table = "problemset_badge"
verbose_name = "题单奖章"
verbose_name_plural = "题单奖章"
def __str__(self):
return f"{self.problemset.title} - {self.name}"
class ProblemSetProgress(models.Model):
"""题单进度模型"""
problemset = models.ForeignKey(
ProblemSet, on_delete=models.CASCADE, verbose_name="题单"
)
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户")
# 加入时间
join_time = models.DateTimeField(auto_now_add=True, verbose_name="加入时间")
# 完成时间
complete_time = models.DateTimeField(null=True, blank=True, verbose_name="完成时间")
# 是否完成
is_completed = models.BooleanField(default=False, verbose_name="是否完成")
# 完成进度百分比
progress_percentage = models.FloatField(default=0.0, verbose_name="完成进度")
# 已完成的题目数量
completed_problems_count = models.IntegerField(
default=0, verbose_name="已完成题目数"
)
# 总题目数量
total_problems_count = models.IntegerField(default=0, verbose_name="总题目数")
# 获得的总分
total_score = models.IntegerField(default=0, verbose_name="总分")
# 用户在该题单中的详细进度信息
# {"problem_id": {"status": "completed", "score": 100, "submit_time": "2024-01-01T00:00:00Z"}}
progress_detail = JSONField(default=dict, verbose_name="详细进度")
class Meta:
db_table = "problemset_progress"
unique_together = (("problemset", "user"),)
verbose_name = "题单进度"
verbose_name_plural = "题单进度"
def __str__(self):
return f"{self.user.username} - {self.problemset.title}"
def update_progress(self):
"""更新进度信息"""
# 获取题单中的所有题目
problemset_problems = ProblemSetProblem.objects.filter(
problemset=self.problemset
)
self.total_problems_count = problemset_problems.count()
# 计算已完成题目数
completed_count = 0
total_score = 0
for psp in problemset_problems:
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)
self.completed_problems_count = completed_count
self.total_score = total_score
# 计算完成百分比
if self.total_problems_count > 0:
self.progress_percentage = (
completed_count / self.total_problems_count
) * 100
else:
self.progress_percentage = 0
# 检查是否完成
self.is_completed = completed_count == self.total_problems_count
if self.is_completed and not self.complete_time:
self.complete_time = now()
self.save()
class UserBadge(models.Model):
"""用户奖章模型"""
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户")
badge = models.ForeignKey(
ProblemSetBadge, on_delete=models.CASCADE, verbose_name="奖章"
)
# 获得时间
earned_time = models.DateTimeField(auto_now_add=True, verbose_name="获得时间")
# 是否已展示给用户
is_displayed = models.BooleanField(default=False, verbose_name="是否已展示")
class Meta:
db_table = "user_badge"
unique_together = (("user", "badge"),)
verbose_name = "用户奖章"
verbose_name_plural = "用户奖章"
def __str__(self):
return f"{self.user.username} - {self.badge.name}"

200
problemset/serializers.py Normal file
View File

@@ -0,0 +1,200 @@
from utils.api import UsernameSerializer, serializers
from .models import (
ProblemSet,
ProblemSetProblem,
ProblemSetBadge,
ProblemSetProgress,
UserBadge,
)
class ProblemSetSerializer(serializers.ModelSerializer):
"""题单序列化器"""
created_by = UsernameSerializer()
problems_count = serializers.SerializerMethodField()
completed_count = serializers.SerializerMethodField()
class Meta:
model = ProblemSet
fields = "__all__"
def get_problems_count(self, obj):
"""获取题单中的题目数量"""
return ProblemSetProblem.objects.filter(problemset=obj).count()
def get_completed_count(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 progress.completed_problems_count
except ProblemSetProgress.DoesNotExist:
return 0
return 0
class ProblemSetListSerializer(serializers.ModelSerializer):
"""题单列表序列化器"""
created_by = UsernameSerializer()
problems_count = serializers.SerializerMethodField()
user_progress = serializers.SerializerMethodField()
class Meta:
model = ProblemSet
fields = [
"id",
"title",
"description",
"created_by",
"create_time",
"difficulty",
"status",
"problems_count",
"user_progress",
]
def get_problems_count(self, obj):
"""获取题单中的题目数量"""
return ProblemSetProblem.objects.filter(problemset=obj).count()
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,
}
class CreateProblemSetSerializer(serializers.Serializer):
"""创建题单序列化器"""
title = serializers.CharField(max_length=200)
description = serializers.CharField()
difficulty = serializers.CharField(default="Easy")
is_public = serializers.BooleanField(default=True)
status = serializers.CharField(default="active")
class EditProblemSetSerializer(serializers.Serializer):
"""编辑题单序列化器"""
id = serializers.IntegerField()
title = serializers.CharField(max_length=200, required=False)
description = serializers.CharField(required=False)
difficulty = serializers.CharField(required=False)
is_public = serializers.BooleanField(required=False)
status = serializers.CharField(required=False)
visible = serializers.BooleanField(required=False)
class ProblemSetProblemSerializer(serializers.ModelSerializer):
"""题单题目序列化器"""
problem = serializers.SerializerMethodField()
class Meta:
model = ProblemSetProblem
fields = "__all__"
def get_problem(self, obj):
"""获取题目详细信息"""
from problem.serializers import ProblemSerializer
return ProblemSerializer(obj.problem, context=self.context).data
class AddProblemToSetSerializer(serializers.Serializer):
"""添加题目到题单序列化器"""
problemset_id = serializers.IntegerField()
problem_id = serializers.IntegerField()
order = serializers.IntegerField(default=0)
is_required = serializers.BooleanField(default=True)
score = serializers.IntegerField(default=0)
hint = serializers.CharField(required=False, allow_blank=True)
class ProblemSetBadgeSerializer(serializers.ModelSerializer):
"""题单奖章序列化器"""
class Meta:
model = ProblemSetBadge
fields = "__all__"
class CreateProblemSetBadgeSerializer(serializers.Serializer):
"""创建题单奖章序列化器"""
problemset_id = serializers.IntegerField()
name = serializers.CharField(max_length=100)
description = serializers.CharField()
icon = serializers.CharField()
condition_type = serializers.CharField() # all_problems, problem_count, score
condition_value = serializers.IntegerField()
level = serializers.IntegerField(default=1)
class ProblemSetProgressSerializer(serializers.ModelSerializer):
"""题单进度序列化器"""
problemset = ProblemSetListSerializer()
user = UsernameSerializer()
class Meta:
model = ProblemSetProgress
fields = "__all__"
class UserBadgeSerializer(serializers.ModelSerializer):
"""用户奖章序列化器"""
badge = ProblemSetBadgeSerializer()
class Meta:
model = UserBadge
fields = "__all__"
class JoinProblemSetSerializer(serializers.Serializer):
"""加入题单序列化器"""
problemset_id = serializers.IntegerField()
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)

73
problemset/signals.py Normal file
View File

@@ -0,0 +1,73 @@
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 submission.models import Submission
@receiver(post_save, sender=Submission)
def update_problemset_progress(sender, instance, created, **kwargs):
"""当提交状态更新时,自动更新题单进度"""
if not created: # 只处理更新,不处理新建
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
)
# 更新详细进度
problem_id = str(instance.problem.id)
progress.progress_detail[problem_id] = {
"status": "completed"
if instance.result == 0
else "attempted", # 0表示AC
"score": instance.score if hasattr(instance, "score") else 0,
"submit_time": timezone.now().isoformat(),
}
# 更新进度
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)

View File

64
problemset/urls/admin.py Normal file
View File

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

54
problemset/urls/oj.py Normal file
View File

@@ -0,0 +1,54 @@
from django.urls import path
from problemset.views.oj import (
ProblemSetAPI,
ProblemSetDetailAPI,
ProblemSetProblemAPI,
ProblemSetProgressAPI,
UserBadgeAPI,
UserProgressAPI,
ProblemSetBadgeAPI,
)
urlpatterns = [
# 题单相关API
path("api/problemset/", ProblemSetAPI.as_view(), name="problemset_api"),
path(
"api/problemset/<int:problem_set_id>/",
ProblemSetDetailAPI.as_view(),
name="problemset_detail_api",
),
path(
"api/problemset/<int:problem_set_id>/problems/",
ProblemSetProblemAPI.as_view(),
name="problemset_problems_api",
),
path(
"api/problemset/<int:problem_set_id>/problems/<int:problem_id>/",
ProblemSetProblemAPI.as_view(),
name="problemset_problem_detail_api",
),
# 进度相关API
path(
"api/problemset/progress/",
ProblemSetProgressAPI.as_view(),
name="problemset_progress_api",
),
path(
"api/problemset/<int:problem_set_id>/progress/",
ProblemSetProgressAPI.as_view(),
name="problemset_progress_detail_api",
),
path("api/user/progress/", UserProgressAPI.as_view(), name="user_progress_api"),
# 奖章相关API
path("api/user/badges/", UserBadgeAPI.as_view(), name="user_badges_api"),
path(
"api/user/badges/<int:badge_id>/",
UserBadgeAPI.as_view(),
name="user_badge_detail_api",
),
path(
"api/problemset/<int:problem_set_id>/badges/",
ProblemSetBadgeAPI.as_view(),
name="problemset_badges_api",
),
]

View File

318
problemset/views/admin.py Normal file
View File

@@ -0,0 +1,318 @@
from django.db.models import Q
from utils.api import APIView, validate_serializer
from account.decorators import super_admin_required, ensure_created_by
from problemset.models import (
ProblemSet,
ProblemSetProblem,
ProblemSetBadge,
ProblemSetProgress,
)
from problemset.serializers import (
ProblemSetSerializer,
ProblemSetListSerializer,
CreateProblemSetSerializer,
EditProblemSetSerializer,
ProblemSetProblemSerializer,
AddProblemToSetSerializer,
ProblemSetBadgeSerializer,
CreateProblemSetBadgeSerializer,
ProblemSetProgressSerializer,
)
from problem.models import Problem
class ProblemSetAdminAPI(APIView):
"""题单管理API"""
@super_admin_required
def get(self, request):
"""获取题单列表(管理员)"""
problem_sets = ProblemSet.objects.all().order_by("-create_time")
# 过滤条件
keyword = request.GET.get("keyword", "").strip()
if keyword:
problem_sets = problem_sets.filter(
Q(title__icontains=keyword) | Q(description__icontains=keyword)
)
difficulty = request.GET.get("difficulty")
if difficulty:
problem_sets = problem_sets.filter(difficulty=difficulty)
status_filter = request.GET.get("status")
if status_filter:
problem_sets = problem_sets.filter(status=status_filter)
# 权限过滤:如果不是超级管理员,只能看到自己创建的题单
if not request.user.is_admin():
problem_sets = problem_sets.filter(created_by=request.user)
# 使用统一的分页方法
data = self.paginate_data(request, problem_sets, ProblemSetListSerializer)
return self.success(data)
@super_admin_required
@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)
@super_admin_required
@validate_serializer(EditProblemSetSerializer)
def put(self, request):
"""编辑题单"""
data = request.data
problem_set_id = data.pop("id")
try:
problem_set = ProblemSet.objects.get(id=problem_set_id)
ensure_created_by(problem_set, request.user)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
# 更新题单信息
for key, value in data.items():
if key != "id":
setattr(problem_set, key, value)
problem_set.save()
return self.success(ProblemSetSerializer(problem_set).data)
@super_admin_required
def delete(self, request):
"""删除题单"""
problem_set_id = request.GET.get("id")
if not problem_set_id:
return self.error("题单ID是必需的")
try:
problem_set = ProblemSet.objects.get(id=problem_set_id)
ensure_created_by(problem_set, request.user)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
# 软删除:设置为不可见
problem_set.visible = False
problem_set.save()
return self.success("题单已删除")
class ProblemSetDetailAdminAPI(APIView):
"""题单详情管理API"""
@super_admin_required
def get(self, request, problem_set_id):
"""获取题单详情(管理员)"""
try:
problem_set = ProblemSet.objects.get(id=problem_set_id)
ensure_created_by(problem_set, request.user)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
serializer = ProblemSetSerializer(problem_set, context={"request": request})
return self.success(serializer.data)
class ProblemSetProblemAdminAPI(APIView):
"""题单题目管理API管理员"""
@super_admin_required
def get(self, request, problem_set_id):
"""获取题单中的题目列表(管理员)"""
try:
problem_set = ProblemSet.objects.get(id=problem_set_id)
ensure_created_by(problem_set, request.user)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
problems = ProblemSetProblem.objects.filter(problemset=problem_set).order_by(
"order"
)
serializer = ProblemSetProblemSerializer(
problems, many=True, context={"request": request}
)
return self.success(serializer.data)
@super_admin_required
@validate_serializer(AddProblemToSetSerializer)
def post(self, request, problem_set_id):
"""添加题目到题单(管理员)"""
try:
problem_set = ProblemSet.objects.get(id=problem_set_id)
ensure_created_by(problem_set, request.user)
except ProblemSet.DoesNotExist:
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("题目已添加到题单")
@super_admin_required
def delete(self, request, problem_set_id, problem_id):
"""从题单中移除题目(管理员)"""
try:
problem_set = ProblemSet.objects.get(id=problem_set_id)
ensure_created_by(problem_set, request.user)
except ProblemSet.DoesNotExist:
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 ProblemSetBadgeAdminAPI(APIView):
"""题单奖章管理API管理员"""
@super_admin_required
def get(self, request, problem_set_id):
"""获取题单的奖章列表(管理员)"""
try:
problem_set = ProblemSet.objects.get(id=problem_set_id)
ensure_created_by(problem_set, request.user)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
badges = ProblemSetBadge.objects.filter(problemset=problem_set)
serializer = ProblemSetBadgeSerializer(badges, many=True)
return self.success(serializer.data)
@super_admin_required
@validate_serializer(CreateProblemSetBadgeSerializer)
def post(self, request, problem_set_id):
"""创建题单奖章(管理员)"""
try:
problem_set = ProblemSet.objects.get(id=problem_set_id)
ensure_created_by(problem_set, request.user)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
data = request.data
data["problemset"] = problem_set
badge = ProblemSetBadge.objects.create(**data)
return self.success(ProblemSetBadgeSerializer(badge).data)
@super_admin_required
def delete(self, request, problem_set_id, badge_id):
"""删除题单奖章(管理员)"""
try:
problem_set = ProblemSet.objects.get(id=problem_set_id)
ensure_created_by(problem_set, request.user)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
try:
badge = ProblemSetBadge.objects.get(id=badge_id, problemset=problem_set)
badge.delete()
return self.success("奖章已删除")
except ProblemSetBadge.DoesNotExist:
return self.error("奖章不存在")
class ProblemSetProgressAdminAPI(APIView):
"""题单进度管理API管理员"""
@super_admin_required
def get(self, request, problem_set_id):
"""获取题单的所有用户进度(管理员)"""
try:
problem_set = ProblemSet.objects.get(id=problem_set_id)
ensure_created_by(problem_set, request.user)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
progress_list = ProblemSetProgress.objects.filter(problemset=problem_set).order_by(
"-join_time"
)
serializer = ProblemSetProgressSerializer(progress_list, many=True)
return self.success(serializer.data)
@super_admin_required
def delete(self, request, problem_set_id, user_id):
"""移除用户从题单(管理员)"""
try:
problem_set = ProblemSet.objects.get(id=problem_set_id)
ensure_created_by(problem_set, request.user)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
try:
progress = ProblemSetProgress.objects.get(
problemset=problem_set, user_id=user_id
)
progress.delete()
return self.success("用户已从题单中移除")
except ProblemSetProgress.DoesNotExist:
return self.error("用户未加入该题单")
class ProblemSetVisibleAPI(APIView):
"""题单可见性管理API"""
@super_admin_required
def put(self, request):
"""切换题单可见性"""
data = request.data
try:
problem_set = ProblemSet.objects.get(id=data["id"])
ensure_created_by(problem_set, request.user)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
problem_set.visible = not problem_set.visible
problem_set.save()
return self.success()
class ProblemSetStatusAPI(APIView):
"""题单状态管理API"""
@super_admin_required
def put(self, request):
"""更新题单状态"""
data = request.data
try:
problem_set = ProblemSet.objects.get(id=data["id"])
ensure_created_by(problem_set, request.user)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
status = data.get("status")
if status not in ["active", "archived", "draft"]:
return self.error("无效的状态")
problem_set.status = status
problem_set.save()
return self.success()

374
problemset/views/oj.py Normal file
View File

@@ -0,0 +1,374 @@
from django.db.models import Q
from django.utils import timezone
from utils.api import APIView, validate_serializer
from problemset.models import (
ProblemSet,
ProblemSetProblem,
ProblemSetBadge,
ProblemSetProgress,
UserBadge,
)
from problemset.serializers import (
ProblemSetSerializer,
ProblemSetListSerializer,
CreateProblemSetSerializer,
EditProblemSetSerializer,
ProblemSetProblemSerializer,
AddProblemToSetSerializer,
ProblemSetBadgeSerializer,
CreateProblemSetBadgeSerializer,
ProblemSetProgressSerializer,
UserBadgeSerializer,
JoinProblemSetSerializer,
UpdateProgressSerializer,
)
from problem.models import Problem
class ProblemSetAPI(APIView):
"""题单API"""
def get(self, request):
"""获取题单列表"""
problem_sets = ProblemSet.objects.filter(visible=True)
# 过滤条件
keyword = request.GET.get("keyword", "").strip()
if keyword:
problem_sets = problem_sets.filter(
Q(title__icontains=keyword) | Q(description__icontains=keyword)
)
difficulty = request.GET.get("difficulty")
if difficulty:
problem_sets = problem_sets.filter(difficulty=difficulty)
status_filter = request.GET.get("status")
if status_filter:
problem_sets = problem_sets.filter(status=status_filter)
# 只显示公开的题单,除非是管理员
if not request.user.is_authenticated or not request.user.is_admin_role():
problem_sets = problem_sets.filter(is_public=True)
# 排序
sort = request.GET.get("sort")
if sort:
problem_sets = problem_sets.order_by(sort)
else:
problem_sets = problem_sets.order_by("-create_time")
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"""
def get(self, request, problem_set_id):
"""获取题单详情"""
try:
problem_set = ProblemSet.objects.get(id=problem_set_id, visible=True)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
# 检查权限
if not problem_set.is_public and not (
request.user.is_authenticated and request.user.is_admin_role()
):
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"""
def get(self, request, problem_set_id):
"""获取题单中的题目列表"""
try:
problem_set = ProblemSet.objects.get(id=problem_set_id, visible=True)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
# 检查权限
if not problem_set.is_public and not (
request.user.is_authenticated and request.user.is_admin_role()
):
return self.error("无权限访问该题单")
problems = ProblemSetProblem.objects.filter(problemset=problem_set).order_by(
"order"
)
serializer = ProblemSetProblemSerializer(
problems, many=True, context={"request": request}
)
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"""
@validate_serializer(JoinProblemSetSerializer)
def post(self, request):
"""加入题单"""
data = request.data
try:
problem_set = ProblemSet.objects.get(id=data["problemset_id"], visible=True)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
# 检查权限
if not problem_set.is_public and not request.user.is_admin_role():
return self.error("无权限加入该题单")
# 检查是否已经加入
if ProblemSetProgress.objects.filter(
problemset=problem_set, user=request.user
).exists():
return self.error("已经加入该题单")
# 创建进度记录
progress = ProblemSetProgress.objects.create(
problemset=problem_set, user=request.user
)
progress.update_progress()
return self.success("成功加入题单")
def get(self, request, problem_set_id):
"""获取题单进度"""
try:
problem_set = ProblemSet.objects.get(id=problem_set_id, visible=True)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
try:
progress = ProblemSetProgress.objects.get(
problemset=problem_set, user=request.user
)
except ProblemSetProgress.DoesNotExist:
return self.error("未加入该题单")
serializer = ProblemSetProgressSerializer(progress)
return self.success(serializer.data)
@validate_serializer(UpdateProgressSerializer)
def put(self, request):
"""更新进度"""
data = request.data
try:
problem_set = ProblemSet.objects.get(id=data["problemset_id"], visible=True)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
try:
progress = ProblemSetProgress.objects.get(
problemset=problem_set, user=request.user
)
except ProblemSetProgress.DoesNotExist:
return self.error("未加入该题单")
# 更新详细进度
problem_id = str(data["problem_id"])
progress.progress_detail[problem_id] = {
"status": data["status"],
"score": data.get("score", 0),
"submit_time": data.get("submit_time", timezone.now().isoformat()),
}
# 更新进度
progress.update_progress()
# 检查是否获得奖章
self._check_badges(progress)
return self.success("进度已更新")
def _check_badges(self, progress):
"""检查是否获得奖章"""
badges = ProblemSetBadge.objects.filter(problemset=progress.problemset)
for badge in badges:
# 检查是否已经获得该奖章
if UserBadge.objects.filter(user=progress.user, badge=badge).exists():
continue
# 检查是否满足获得条件
if badge.condition_type == "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":
if progress.completed_problems_count >= badge.condition_value:
UserBadge.objects.create(user=progress.user, badge=badge)
elif badge.condition_type == "score":
if progress.total_score >= badge.condition_value:
UserBadge.objects.create(user=progress.user, badge=badge)
class UserProgressAPI(APIView):
"""用户进度API"""
def get(self, request):
"""获取用户的题单进度列表"""
progress_list = ProblemSetProgress.objects.filter(user=request.user).order_by(
"-join_time"
)
serializer = ProblemSetProgressSerializer(progress_list, many=True)
return self.success(serializer.data)
class UserBadgeAPI(APIView):
"""用户奖章API"""
def get(self, request):
"""获取用户的奖章列表"""
badges = UserBadge.objects.filter(user=request.user).order_by("-earned_time")
serializer = UserBadgeSerializer(badges, many=True)
return self.success(serializer.data)
def put(self, request, badge_id):
"""标记奖章为已展示"""
try:
user_badge = UserBadge.objects.get(id=badge_id, user=request.user)
user_badge.is_displayed = True
user_badge.save()
return self.success("奖章已标记为已展示")
except UserBadge.DoesNotExist:
return self.error("奖章不存在")
class ProblemSetBadgeAPI(APIView):
"""题单奖章管理API"""
def get(self, request, problem_set_id):
"""获取题单的奖章列表"""
try:
problem_set = ProblemSet.objects.get(id=problem_set_id, visible=True)
except ProblemSet.DoesNotExist:
return self.error("题单不存在")
badges = ProblemSetBadge.objects.filter(problemset=problem_set)
serializer = ProblemSetBadgeSerializer(badges, many=True)
return self.success(serializer.data)
@validate_serializer(CreateProblemSetBadgeSerializer)
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
data["problemset"] = problem_set
badge = ProblemSetBadge.objects.create(**data)
return self.success(ProblemSetBadgeSerializer(badge).data)