From c81b5b1a55a58de43fa6894a6870dd572a3ad5bc Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Wed, 19 Mar 2025 09:15:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20rating?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- submission/api.py | 63 +++++++------ ...referee_alter_submission_score_and_more.py | 38 ++++++++ submission/models.py | 92 ++++++++++++++++--- submission/schemas.py | 13 ++- 4 files changed, 157 insertions(+), 49 deletions(-) create mode 100644 submission/migrations/0002_remove_submission_referee_alter_submission_score_and_more.py diff --git a/submission/api.py b/submission/api.py index 9e671c2..6161299 100644 --- a/submission/api.py +++ b/submission/api.py @@ -11,12 +11,12 @@ from .schemas import ( SubmissionFilter, SubmissionIn, SubmissionOut, - SubmissionScoreIn, + RatingScoreIn, SubmissionScoreOut, ) -from .models import Submission +from .models import Rating, Submission from task.models import Task from account.models import RoleChoices @@ -42,20 +42,25 @@ def create_submission(request, payload: SubmissionIn): @router.get("/", response=List[SubmissionOut]) @paginate +@login_required def list_submissions(request, filters: SubmissionFilter = Query(...)): """ 获取提交列表,支持按任务和用户过滤 """ - queryset = Submission.objects.all() + submissions = Submission.objects.all() if filters.task_id: - queryset = queryset.filter(task_id=filters.task_id) + submissions = submissions.filter(task_id=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: - 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) @@ -64,18 +69,18 @@ def get_submission(request, submission_id: UUID): """ 获取单个提交的详细信息 """ - # 如果是普通用户,只能查看自己的提交 - if request.user.role == RoleChoices.NORMAL: - submission = get_object_or_404(Submission, id=submission_id, user=request.user) - else: - submission = get_object_or_404(Submission, id=submission_id) - - return SubmissionOut.get(submission) + submission = get_object_or_404(Submission, id=submission_id) + rating = ( + Rating.objects.select_related("user", "submission") + .filter(user=request.user, submission=submission) + .first() + ) + return SubmissionOut.get(submission, rating) -@router.put("/{submission_id}/score", response=SubmissionScoreOut) -@admin_required -def update_score(request, submission_id: UUID, payload: SubmissionScoreIn): +@router.put("/{submission_id}/score") +@login_required +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) - if submission.score > 0: - raise HttpError(400, "该提交已经有分数了") - if ( - request.user.role == RoleChoices.NORMAL - and submission.user.id == request.user.id - ): - raise HttpError(400, "不能自己给自己打分") + _, created = Rating.objects.get_or_create( + user=request.user, + submission=submission, + defaults={"score": payload.score}, + ) - submission.score = payload.score - submission.referee = request.user - submission.save() - - return { - "id": submission.id, - "score": submission.score, - } + if created: + return {"message": "打分成功"} + else: + return {"message": "你已经给这个提交打过分了"} diff --git a/submission/migrations/0002_remove_submission_referee_alter_submission_score_and_more.py b/submission/migrations/0002_remove_submission_referee_alter_submission_score_and_more.py new file mode 100644 index 0000000..5eb07a0 --- /dev/null +++ b/submission/migrations/0002_remove_submission_referee_alter_submission_score_and_more.py @@ -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')}, + }, + ), + ] diff --git a/submission/models.py b/submission/models.py index aa424ca..9dc0dbc 100644 --- a/submission/models.py +++ b/submission/models.py @@ -1,8 +1,12 @@ import uuid from django.db import models 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 @@ -18,15 +22,7 @@ class Submission(TimeStampedModel): related_name="my_submissions", ) task = models.ForeignKey(Task, on_delete=models.CASCADE) - score = models.IntegerField(default=0, verbose_name="分数") - referee = models.ForeignKey( - User, - on_delete=models.CASCADE, - null=True, - blank=True, - related_name="referee_submissions", - verbose_name="打分人", - ) + score = models.FloatField(default=0.0, verbose_name="分数") html = models.TextField(null=True, blank=True, verbose_name="HTML代码") css = models.TextField(null=True, blank=True, verbose_name="CSS代码") js = models.TextField(null=True, blank=True, verbose_name="JS代码") @@ -43,7 +39,79 @@ class Submission(TimeStampedModel): """ 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): super().save(*args, **kwargs) - if self.score > 0: - self.user.profile.update_total_score(self.score) + # if self.score > 0: + # 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() diff --git a/submission/schemas.py b/submission/schemas.py index 808b904..af7d791 100644 --- a/submission/schemas.py +++ b/submission/schemas.py @@ -17,7 +17,8 @@ class SubmissionOut(Schema): task_id: int task_title: str task_type: Literal["tutorial", "challenge"] - score: int + score: float + my_score: int html: Optional[str] = None css: Optional[str] = None js: Optional[str] = None @@ -25,7 +26,7 @@ class SubmissionOut(Schema): modified: str @staticmethod - def list(submission): + def list(submission, rating_dict): return { "id": submission.id, "userid": submission.user.id, @@ -34,12 +35,13 @@ class SubmissionOut(Schema): "task_title": submission.task.title, "task_type": submission.task.task_type, "score": submission.score, + "my_score": rating_dict.get(submission.id, 0), "created": submission.created.isoformat(), "modified": submission.modified.isoformat(), } @staticmethod - def get(submission): + def get(submission, rating): return { "id": submission.id, "userid": submission.user.id, @@ -48,6 +50,7 @@ class SubmissionOut(Schema): "task_title": submission.task.title, "task_type": submission.task.task_type, "score": submission.score, + "my_score": rating.score if rating else 0, "html": submission.html, "css": submission.css, "js": submission.js, @@ -56,13 +59,13 @@ class SubmissionOut(Schema): } -class SubmissionScoreIn(Schema): +class RatingScoreIn(Schema): score: int class SubmissionScoreOut(Schema): id: UUID - score: int + score: float class SubmissionFilter(Schema):