refactor(reaction): 下线 comment app
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,7 +54,7 @@ Each Django app follows the same structure:
|
|||||||
└── admin.py # Admin URL patterns
|
└── admin.py # Admin URL patterns
|
||||||
```
|
```
|
||||||
|
|
||||||
Apps: `account`, `problem`, `submission`, `contest`, `ai`, `flowchart`, `problemset`, `class_pk`, `announcement`, `tutorial`, `message`, `comment`, `conf`, `options`, `judge`
|
Apps: `account`, `problem`, `submission`, `contest`, `ai`, `flowchart`, `problemset`, `class_pk`, `announcement`, `tutorial`, `message`, `reaction`, `conf`, `options`, `judge`
|
||||||
|
|
||||||
`utils/` is itself a Django app (listed in `INSTALLED_APPS`) — not just a helpers package. It provides `RichTextField` (XSS-sanitized `TextField`), `APIError`, the base `APIView`, caching, WebSocket helpers, and the `inituser` management command. Import shared utilities from `utils.*`.
|
`utils/` is itself a Django app (listed in `INSTALLED_APPS`) — not just a helpers package. It provides `RichTextField` (XSS-sanitized `TextField`), `APIError`, the base `APIView`, caching, WebSocket helpers, and the `inituser` management command. Import shared utilities from `utils.*`.
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
# Generated by Django 5.2.3 on 2025-06-14 08:51
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('problem', '0001_initial'),
|
|
||||||
('submission', '0001_initial'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Comment',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('language', models.CharField(choices=[('Python', 'Python'), ('C', 'C'), ('C++', 'C++'), ('Java', 'Java')], default='Python', max_length=10, verbose_name='解决这道题使用的语言')),
|
|
||||||
('description_rating', models.PositiveSmallIntegerField(default=5, verbose_name='题目描述的分数')),
|
|
||||||
('difficulty_rating', models.PositiveSmallIntegerField(default=5, verbose_name='题目难度的分数')),
|
|
||||||
('comprehensive_rating', models.PositiveSmallIntegerField(default=5, verbose_name='综合的分数')),
|
|
||||||
('content', models.TextField(blank=True, null=True)),
|
|
||||||
('create_time', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='problem.problem')),
|
|
||||||
('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='submission.submission')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'db_table': 'comment',
|
|
||||||
'ordering': ('-create_time',),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# Generated by Django 6.0 on 2026-04-23 20:07
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('comment', '0001_initial'),
|
|
||||||
('problem', '0007_problem_problem_visible_idx'),
|
|
||||||
('submission', '0004_submission_problem_user_idx'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddIndex(
|
|
||||||
model_name='comment',
|
|
||||||
index=models.Index(fields=['problem', 'create_time'], name='comment_problem_time_idx'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
|
|
||||||
from account.models import User
|
|
||||||
from problem.models import Problem
|
|
||||||
from submission.models import Submission
|
|
||||||
|
|
||||||
|
|
||||||
class Languages(models.TextChoices):
|
|
||||||
Python = "Python", "Python"
|
|
||||||
C = "C", "C"
|
|
||||||
Cpp = "C++", "C++"
|
|
||||||
Java = "Java", "Java"
|
|
||||||
|
|
||||||
|
|
||||||
class Comment(models.Model):
|
|
||||||
problem = models.ForeignKey(Problem, on_delete=models.CASCADE)
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
||||||
submission = models.ForeignKey(Submission, on_delete=models.CASCADE)
|
|
||||||
language = models.CharField(
|
|
||||||
max_length=10,
|
|
||||||
default=Languages.Python,
|
|
||||||
choices=Languages.choices,
|
|
||||||
verbose_name="解决这道题使用的语言",
|
|
||||||
)
|
|
||||||
description_rating = models.PositiveSmallIntegerField(
|
|
||||||
default=5,
|
|
||||||
verbose_name="题目描述的分数",
|
|
||||||
)
|
|
||||||
difficulty_rating = models.PositiveSmallIntegerField(
|
|
||||||
default=5,
|
|
||||||
verbose_name="题目难度的分数",
|
|
||||||
)
|
|
||||||
comprehensive_rating = models.PositiveSmallIntegerField(
|
|
||||||
default=5,
|
|
||||||
verbose_name="综合的分数",
|
|
||||||
)
|
|
||||||
content = models.TextField(null=True, blank=True)
|
|
||||||
create_time = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = "comment"
|
|
||||||
ordering = ("-create_time",)
|
|
||||||
indexes = [
|
|
||||||
models.Index(fields=["problem", "create_time"], name="comment_problem_time_idx"),
|
|
||||||
]
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
from comment.models import Comment
|
|
||||||
from utils.api import UsernameSerializer, serializers
|
|
||||||
|
|
||||||
|
|
||||||
class CreateCommentSerializer(serializers.Serializer):
|
|
||||||
problem_id = serializers.IntegerField()
|
|
||||||
description_rating = serializers.IntegerField()
|
|
||||||
difficulty_rating = serializers.IntegerField()
|
|
||||||
comprehensive_rating = serializers.IntegerField()
|
|
||||||
content = serializers.CharField(required=False, allow_blank=True)
|
|
||||||
|
|
||||||
|
|
||||||
class CommentSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Comment
|
|
||||||
fields = [
|
|
||||||
"comprehensive_rating",
|
|
||||||
"description_rating",
|
|
||||||
"difficulty_rating",
|
|
||||||
"content",
|
|
||||||
"create_time",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class CommentListSerializer(serializers.ModelSerializer):
|
|
||||||
problem = serializers.SlugRelatedField(read_only=True, slug_field="_id")
|
|
||||||
user = UsernameSerializer()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Comment
|
|
||||||
fields = "__all__"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
from django.urls import path
|
|
||||||
|
|
||||||
from ..views.admin import CommentAPI
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path("comment", CommentAPI.as_view()),
|
|
||||||
]
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
from django.urls import path
|
|
||||||
|
|
||||||
from ..views.oj import CommentAPI, CommentStatisticsAPI
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path("comment", CommentAPI.as_view()),
|
|
||||||
path("comment/statistics", CommentStatisticsAPI.as_view()),
|
|
||||||
]
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
from account.decorators import super_admin_required
|
|
||||||
from comment.models import Comment
|
|
||||||
from comment.serializers import CommentListSerializer
|
|
||||||
from problem.models import Problem
|
|
||||||
from utils.api import APIView
|
|
||||||
|
|
||||||
|
|
||||||
class CommentAPI(APIView):
|
|
||||||
@super_admin_required
|
|
||||||
def get(self, request):
|
|
||||||
comments = Comment.objects.select_related("problem").exclude(content="")
|
|
||||||
problem_id = request.GET.get("problem")
|
|
||||||
if problem_id:
|
|
||||||
try:
|
|
||||||
# 这里如果题目不可见,也需要显示该题目的评论
|
|
||||||
problem = Problem.objects.get(_id__iexact=problem_id, contest_id__isnull=True)
|
|
||||||
except Problem.DoesNotExist:
|
|
||||||
return self.error("Problem doesn't exist")
|
|
||||||
comments = comments.filter(problem=problem)
|
|
||||||
return self.success(self.paginate_data(request, comments, CommentListSerializer))
|
|
||||||
|
|
||||||
@super_admin_required
|
|
||||||
def delete(self, request):
|
|
||||||
id = request.GET.get("id")
|
|
||||||
if id:
|
|
||||||
Comment.objects.filter(id=id).delete()
|
|
||||||
return self.success()
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
from django.db.models import Avg, Count
|
|
||||||
from django.db.models.functions import Round
|
|
||||||
|
|
||||||
from account.decorators import login_required
|
|
||||||
from comment.models import Comment
|
|
||||||
from comment.serializers import CommentSerializer, CreateCommentSerializer
|
|
||||||
from problem.models import Problem
|
|
||||||
from submission.models import JudgeStatus, Submission
|
|
||||||
from utils.api import AsyncAPIView
|
|
||||||
from utils.api.api import validate_serializer
|
|
||||||
from utils.async_helpers import async_cache_delete, async_cache_get, async_cache_set
|
|
||||||
from utils.constants import CacheKey
|
|
||||||
|
|
||||||
|
|
||||||
class CommentAPI(AsyncAPIView):
|
|
||||||
@login_required
|
|
||||||
@validate_serializer(CreateCommentSerializer)
|
|
||||||
async def post(self, request):
|
|
||||||
data = request.data
|
|
||||||
try:
|
|
||||||
problem = await Problem.objects.aget(id=data["problem_id"], visible=True)
|
|
||||||
except Problem.DoesNotExist:
|
|
||||||
return self.error("problem is not exists")
|
|
||||||
|
|
||||||
submission = await (
|
|
||||||
Submission.objects.select_related("problem")
|
|
||||||
.filter(
|
|
||||||
user_id=request.user.id,
|
|
||||||
problem_id=data["problem_id"],
|
|
||||||
result__in=[JudgeStatus.ACCEPTED, JudgeStatus.AST_CHECK_FAILED],
|
|
||||||
)
|
|
||||||
.afirst()
|
|
||||||
)
|
|
||||||
if not submission:
|
|
||||||
return self.error("submission is not exists or not accepted")
|
|
||||||
|
|
||||||
language = submission.language
|
|
||||||
if language == "Python3":
|
|
||||||
language = "Python"
|
|
||||||
|
|
||||||
await Comment.objects.acreate(
|
|
||||||
user=request.user,
|
|
||||||
problem=problem,
|
|
||||||
submission=submission,
|
|
||||||
language=language,
|
|
||||||
description_rating=data["description_rating"],
|
|
||||||
difficulty_rating=data["difficulty_rating"],
|
|
||||||
comprehensive_rating=data["comprehensive_rating"],
|
|
||||||
content=data["content"],
|
|
||||||
)
|
|
||||||
await async_cache_delete(f"{CacheKey.comment_stats}:{problem.id}")
|
|
||||||
return self.success()
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
async def get(self, request):
|
|
||||||
problem_id = request.GET.get("problem_id")
|
|
||||||
comment = await Comment.objects.select_related("problem").filter(user=request.user, problem_id=problem_id).afirst()
|
|
||||||
if comment:
|
|
||||||
return self.success(await self.async_serialize_data(CommentSerializer, comment))
|
|
||||||
else:
|
|
||||||
return self.success()
|
|
||||||
|
|
||||||
|
|
||||||
class CommentStatisticsAPI(AsyncAPIView):
|
|
||||||
async def get(self, request):
|
|
||||||
problem_id = request.GET.get("problem_id")
|
|
||||||
cache_key = f"{CacheKey.comment_stats}:{problem_id}"
|
|
||||||
cached = await async_cache_get(cache_key)
|
|
||||||
if cached is not None:
|
|
||||||
return self.success(cached)
|
|
||||||
|
|
||||||
agg = await Comment.objects.filter(problem_id=problem_id).aaggregate(
|
|
||||||
count=Count("id"),
|
|
||||||
description=Round(Avg("description_rating"), 2),
|
|
||||||
difficulty=Round(Avg("difficulty_rating"), 2),
|
|
||||||
comprehensive=Round(Avg("comprehensive_rating"), 2),
|
|
||||||
)
|
|
||||||
if not agg["count"]:
|
|
||||||
return self.success()
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"count": agg["count"],
|
|
||||||
"rating": {
|
|
||||||
"description": agg["description"],
|
|
||||||
"difficulty": agg["difficulty"],
|
|
||||||
"comprehensive": agg["comprehensive"],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await async_cache_set(cache_key, data, 3600)
|
|
||||||
return self.success(data)
|
|
||||||
@@ -54,7 +54,6 @@ LOCAL_APPS = [
|
|||||||
"options",
|
"options",
|
||||||
"judge",
|
"judge",
|
||||||
"message",
|
"message",
|
||||||
"comment",
|
|
||||||
"reaction",
|
"reaction",
|
||||||
"tutorial",
|
"tutorial",
|
||||||
"ai",
|
"ai",
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ urlpatterns = [
|
|||||||
path("api/", include("message.urls.oj")),
|
path("api/", include("message.urls.oj")),
|
||||||
path("api/", include("reaction.urls.oj")),
|
path("api/", include("reaction.urls.oj")),
|
||||||
path("api/admin/", include("reaction.urls.admin")),
|
path("api/admin/", include("reaction.urls.admin")),
|
||||||
path("api/", include("comment.urls.oj")),
|
|
||||||
path("api/admin/", include("comment.urls.admin")),
|
|
||||||
path("api/", include("tutorial.urls.tutorial")),
|
path("api/", include("tutorial.urls.tutorial")),
|
||||||
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")),
|
||||||
|
|||||||
14
reaction/migrations/0002_delete_comment.py
Normal file
14
reaction/migrations/0002_delete_comment.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("reaction", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunSQL(
|
||||||
|
sql="DROP TABLE IF EXISTS comment CASCADE;",
|
||||||
|
reverse_sql=migrations.RunSQL.noop,
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -17,7 +17,6 @@ class CacheKey:
|
|||||||
website_config = "website_config"
|
website_config = "website_config"
|
||||||
problem_authors = "problem_authors"
|
problem_authors = "problem_authors"
|
||||||
problem_tags = "problem_tags"
|
problem_tags = "problem_tags"
|
||||||
comment_stats = "comment_stats"
|
|
||||||
reaction_stats = "reaction_stats"
|
reaction_stats = "reaction_stats"
|
||||||
user_activity_rank = "user_activity_rank"
|
user_activity_rank = "user_activity_rank"
|
||||||
problem_yearly_ac = "problem_yearly_ac"
|
problem_yearly_ac = "problem_yearly_ac"
|
||||||
|
|||||||
Reference in New Issue
Block a user