添加 rating
This commit is contained in:
@@ -11,12 +11,12 @@ from .schemas import (
|
|||||||
SubmissionFilter,
|
SubmissionFilter,
|
||||||
SubmissionIn,
|
SubmissionIn,
|
||||||
SubmissionOut,
|
SubmissionOut,
|
||||||
SubmissionScoreIn,
|
RatingScoreIn,
|
||||||
SubmissionScoreOut,
|
SubmissionScoreOut,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
from .models import Submission
|
from .models import Rating, Submission
|
||||||
from task.models import Task
|
from task.models import Task
|
||||||
from account.models import RoleChoices
|
from account.models import RoleChoices
|
||||||
|
|
||||||
@@ -42,20 +42,25 @@ def create_submission(request, payload: SubmissionIn):
|
|||||||
|
|
||||||
@router.get("/", response=List[SubmissionOut])
|
@router.get("/", response=List[SubmissionOut])
|
||||||
@paginate
|
@paginate
|
||||||
|
@login_required
|
||||||
def list_submissions(request, filters: SubmissionFilter = Query(...)):
|
def list_submissions(request, filters: SubmissionFilter = Query(...)):
|
||||||
"""
|
"""
|
||||||
获取提交列表,支持按任务和用户过滤
|
获取提交列表,支持按任务和用户过滤
|
||||||
"""
|
"""
|
||||||
queryset = Submission.objects.all()
|
submissions = Submission.objects.all()
|
||||||
|
|
||||||
if filters.task_id:
|
if filters.task_id:
|
||||||
queryset = queryset.filter(task_id=filters.task_id)
|
submissions = submissions.filter(task_id=filters.task_id)
|
||||||
if filters.task_id:
|
if filters.task_id:
|
||||||
queryset = queryset.filter(task_task_type=filters.task_type)
|
submissions = submissions.filter(task_task_type=filters.task_type)
|
||||||
if filters.username:
|
if filters.username:
|
||||||
queryset = queryset.filter(user_username=filters.username)
|
submissions = submissions.filter(user_username=filters.username)
|
||||||
|
|
||||||
return [SubmissionOut.list(submission) for submission in queryset]
|
ratings = Rating.objects.select_related("user", "submission").filter(
|
||||||
|
user=request.user, submission__in=submissions
|
||||||
|
)
|
||||||
|
rating_dict = {rating.submission_id: rating.score for rating in ratings}
|
||||||
|
return [SubmissionOut.list(submission, rating_dict) for submission in submissions]
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{submission_id}", response=SubmissionOut)
|
@router.get("/{submission_id}", response=SubmissionOut)
|
||||||
@@ -64,18 +69,18 @@ def get_submission(request, submission_id: UUID):
|
|||||||
"""
|
"""
|
||||||
获取单个提交的详细信息
|
获取单个提交的详细信息
|
||||||
"""
|
"""
|
||||||
# 如果是普通用户,只能查看自己的提交
|
submission = get_object_or_404(Submission, id=submission_id)
|
||||||
if request.user.role == RoleChoices.NORMAL:
|
rating = (
|
||||||
submission = get_object_or_404(Submission, id=submission_id, user=request.user)
|
Rating.objects.select_related("user", "submission")
|
||||||
else:
|
.filter(user=request.user, submission=submission)
|
||||||
submission = get_object_or_404(Submission, id=submission_id)
|
.first()
|
||||||
|
)
|
||||||
return SubmissionOut.get(submission)
|
return SubmissionOut.get(submission, rating)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{submission_id}/score", response=SubmissionScoreOut)
|
@router.put("/{submission_id}/score")
|
||||||
@admin_required
|
@login_required
|
||||||
def update_score(request, submission_id: UUID, payload: SubmissionScoreIn):
|
def update_score(request, submission_id: UUID, payload: RatingScoreIn):
|
||||||
"""
|
"""
|
||||||
给提交打分
|
给提交打分
|
||||||
"""
|
"""
|
||||||
@@ -84,19 +89,13 @@ def update_score(request, submission_id: UUID, payload: SubmissionScoreIn):
|
|||||||
|
|
||||||
submission = get_object_or_404(Submission, id=submission_id)
|
submission = get_object_or_404(Submission, id=submission_id)
|
||||||
|
|
||||||
if submission.score > 0:
|
_, created = Rating.objects.get_or_create(
|
||||||
raise HttpError(400, "该提交已经有分数了")
|
user=request.user,
|
||||||
if (
|
submission=submission,
|
||||||
request.user.role == RoleChoices.NORMAL
|
defaults={"score": payload.score},
|
||||||
and submission.user.id == request.user.id
|
)
|
||||||
):
|
|
||||||
raise HttpError(400, "不能自己给自己打分")
|
|
||||||
|
|
||||||
submission.score = payload.score
|
if created:
|
||||||
submission.referee = request.user
|
return {"message": "打分成功"}
|
||||||
submission.save()
|
else:
|
||||||
|
return {"message": "你已经给这个提交打过分了"}
|
||||||
return {
|
|
||||||
"id": submission.id,
|
|
||||||
"score": submission.score,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-03-18 15:03
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('submission', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='submission',
|
||||||
|
name='referee',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='submission',
|
||||||
|
name='score',
|
||||||
|
field=models.FloatField(default=0.0, verbose_name='分数'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Rating',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('score', models.IntegerField(default=0, verbose_name='分数')),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ratings', to='submission.submission')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ratings', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('user', 'submission')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django_extensions.db.models import TimeStampedModel
|
from django_extensions.db.models import TimeStampedModel
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.db.models.signals import post_save
|
||||||
|
from django.dispatch import receiver # 导入receiver
|
||||||
|
|
||||||
from account.models import Profile, User
|
from account.models import Profile, RoleChoices, User
|
||||||
from task.models import Task
|
from task.models import Task
|
||||||
|
|
||||||
|
|
||||||
@@ -18,15 +22,7 @@ class Submission(TimeStampedModel):
|
|||||||
related_name="my_submissions",
|
related_name="my_submissions",
|
||||||
)
|
)
|
||||||
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
||||||
score = models.IntegerField(default=0, verbose_name="分数")
|
score = models.FloatField(default=0.0, verbose_name="分数")
|
||||||
referee = models.ForeignKey(
|
|
||||||
User,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
related_name="referee_submissions",
|
|
||||||
verbose_name="打分人",
|
|
||||||
)
|
|
||||||
html = models.TextField(null=True, blank=True, verbose_name="HTML代码")
|
html = models.TextField(null=True, blank=True, verbose_name="HTML代码")
|
||||||
css = models.TextField(null=True, blank=True, verbose_name="CSS代码")
|
css = models.TextField(null=True, blank=True, verbose_name="CSS代码")
|
||||||
js = models.TextField(null=True, blank=True, verbose_name="JS代码")
|
js = models.TextField(null=True, blank=True, verbose_name="JS代码")
|
||||||
@@ -43,7 +39,79 @@ class Submission(TimeStampedModel):
|
|||||||
"""
|
"""
|
||||||
return self.task.task_type
|
return self.task.task_type
|
||||||
|
|
||||||
|
def update_score(self):
|
||||||
|
"""
|
||||||
|
更新当前Submission的分数
|
||||||
|
"""
|
||||||
|
ratings = self.ratings.all()
|
||||||
|
|
||||||
|
super_score = 0.0
|
||||||
|
admin_score = 0.0
|
||||||
|
normal_score = 0.0
|
||||||
|
|
||||||
|
for rating in ratings:
|
||||||
|
if rating.user.role == RoleChoices.SUPER:
|
||||||
|
super_score += rating.score
|
||||||
|
elif rating.user.role == RoleChoices.ADMIN:
|
||||||
|
admin_score += rating.score
|
||||||
|
else:
|
||||||
|
normal_score += rating.score
|
||||||
|
|
||||||
|
if ratings.exists():
|
||||||
|
total_score = super_score * 0.5 + admin_score * 0.3 + normal_score * 0.2
|
||||||
|
self.score = total_score / ratings.count()
|
||||||
|
else:
|
||||||
|
self.score = 0.0
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
if self.score > 0:
|
# if self.score > 0:
|
||||||
self.user.profile.update_total_score(self.score)
|
# self.user.profile.update_total_score(self.score)
|
||||||
|
|
||||||
|
|
||||||
|
class Rating(models.Model):
|
||||||
|
user = models.ForeignKey(
|
||||||
|
User,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="ratings",
|
||||||
|
)
|
||||||
|
submission = models.ForeignKey(
|
||||||
|
Submission,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="ratings",
|
||||||
|
)
|
||||||
|
score = models.IntegerField(default=0, verbose_name="分数")
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ("user", "submission")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user.username} 评分了 {self.submission.id},分数是 {self.score}"
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
"""
|
||||||
|
在保存之前检查用户当天的评分次数
|
||||||
|
"""
|
||||||
|
today_start = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
today_end = today_start + timezone.timedelta(days=1)
|
||||||
|
count = Rating.objects.filter(
|
||||||
|
user=self.user, created__range=(today_start, today_end)
|
||||||
|
).count()
|
||||||
|
|
||||||
|
if self.user.role == RoleChoices.NORMAL and count >= 30:
|
||||||
|
raise ValidationError("普通用户每天最多只能评分30次。")
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.clean()
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=Rating)
|
||||||
|
def update_submission_score_on_save(sender, instance, **kwargs):
|
||||||
|
"""
|
||||||
|
当Rating保存时,更新对应的Submission的平均分
|
||||||
|
"""
|
||||||
|
instance.submission.update_score()
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ class SubmissionOut(Schema):
|
|||||||
task_id: int
|
task_id: int
|
||||||
task_title: str
|
task_title: str
|
||||||
task_type: Literal["tutorial", "challenge"]
|
task_type: Literal["tutorial", "challenge"]
|
||||||
score: int
|
score: float
|
||||||
|
my_score: int
|
||||||
html: Optional[str] = None
|
html: Optional[str] = None
|
||||||
css: Optional[str] = None
|
css: Optional[str] = None
|
||||||
js: Optional[str] = None
|
js: Optional[str] = None
|
||||||
@@ -25,7 +26,7 @@ class SubmissionOut(Schema):
|
|||||||
modified: str
|
modified: str
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def list(submission):
|
def list(submission, rating_dict):
|
||||||
return {
|
return {
|
||||||
"id": submission.id,
|
"id": submission.id,
|
||||||
"userid": submission.user.id,
|
"userid": submission.user.id,
|
||||||
@@ -34,12 +35,13 @@ class SubmissionOut(Schema):
|
|||||||
"task_title": submission.task.title,
|
"task_title": submission.task.title,
|
||||||
"task_type": submission.task.task_type,
|
"task_type": submission.task.task_type,
|
||||||
"score": submission.score,
|
"score": submission.score,
|
||||||
|
"my_score": rating_dict.get(submission.id, 0),
|
||||||
"created": submission.created.isoformat(),
|
"created": submission.created.isoformat(),
|
||||||
"modified": submission.modified.isoformat(),
|
"modified": submission.modified.isoformat(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get(submission):
|
def get(submission, rating):
|
||||||
return {
|
return {
|
||||||
"id": submission.id,
|
"id": submission.id,
|
||||||
"userid": submission.user.id,
|
"userid": submission.user.id,
|
||||||
@@ -48,6 +50,7 @@ class SubmissionOut(Schema):
|
|||||||
"task_title": submission.task.title,
|
"task_title": submission.task.title,
|
||||||
"task_type": submission.task.task_type,
|
"task_type": submission.task.task_type,
|
||||||
"score": submission.score,
|
"score": submission.score,
|
||||||
|
"my_score": rating.score if rating else 0,
|
||||||
"html": submission.html,
|
"html": submission.html,
|
||||||
"css": submission.css,
|
"css": submission.css,
|
||||||
"js": submission.js,
|
"js": submission.js,
|
||||||
@@ -56,13 +59,13 @@ class SubmissionOut(Schema):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SubmissionScoreIn(Schema):
|
class RatingScoreIn(Schema):
|
||||||
score: int
|
score: int
|
||||||
|
|
||||||
|
|
||||||
class SubmissionScoreOut(Schema):
|
class SubmissionScoreOut(Schema):
|
||||||
id: UUID
|
id: UUID
|
||||||
score: int
|
score: float
|
||||||
|
|
||||||
|
|
||||||
class SubmissionFilter(Schema):
|
class SubmissionFilter(Schema):
|
||||||
|
|||||||
Reference in New Issue
Block a user