add problemset
This commit is contained in:
0
problemset/__init__.py
Normal file
0
problemset/__init__.py
Normal file
9
problemset/apps.py
Normal file
9
problemset/apps.py
Normal 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
|
||||
115
problemset/migrations/0001_initial.py
Normal file
115
problemset/migrations/0001_initial.py
Normal 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')},
|
||||
},
|
||||
),
|
||||
]
|
||||
0
problemset/migrations/__init__.py
Normal file
0
problemset/migrations/__init__.py
Normal file
190
problemset/models.py
Normal file
190
problemset/models.py
Normal 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
200
problemset/serializers.py
Normal 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
73
problemset/signals.py
Normal 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)
|
||||
0
problemset/urls/__init__.py
Normal file
0
problemset/urls/__init__.py
Normal file
64
problemset/urls/admin.py
Normal file
64
problemset/urls/admin.py
Normal 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
54
problemset/urls/oj.py
Normal 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",
|
||||
),
|
||||
]
|
||||
0
problemset/views/__init__.py
Normal file
0
problemset/views/__init__.py
Normal file
318
problemset/views/admin.py
Normal file
318
problemset/views/admin.py
Normal 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
374
problemset/views/oj.py
Normal 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)
|
||||
Reference in New Issue
Block a user