add problemset
This commit is contained in:
@@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
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