This commit is contained in:
rngsurrounded
2025-03-02 22:29:17 +09:00
11 changed files with 141 additions and 19 deletions
@@ -38,6 +38,7 @@ class SubmissionOut(ModelSchema):
competition: UUID = Field(..., alias="task.competition.id") competition: UUID = Field(..., alias="task.competition.id")
competition_name: str = Field(..., alias="task.competition.title") competition_name: str = Field(..., alias="task.competition.title")
task_position: int = Field(..., alias="task.in_competition_position") task_position: int = Field(..., alias="task.in_competition_position")
task_title: str = Field(..., alias="task.title")
@staticmethod @staticmethod
def resolve_criteries(self, context) -> list[CriteriaOut] | None: def resolve_criteries(self, context) -> list[CriteriaOut] | None:
+16 -7
View File
@@ -1,5 +1,6 @@
from datetime import datetime from datetime import datetime
from http import HTTPStatus as status from http import HTTPStatus as status
from statistics import median
from uuid import UUID from uuid import UUID
from django.http import HttpRequest from django.http import HttpRequest
@@ -84,15 +85,22 @@ def evaluate_submission(
review.evaluation = evaluation review.evaluation = evaluation
review.state = ReviewStatusChoices.CHECKED.value review.state = ReviewStatusChoices.CHECKED.value
review.submission.checked_at = datetime.now() review.submission.checked_at = datetime.now()
points = 0
for criterea in evaluation:
points += criterea["mark"]
review.submission.earned_points = (
points # TODO: оценка не от последнего проверяющего а средняя по всем
)
review.save() review.save()
submission_evaluations = Review.objects.filter(submission=submission).values_list('evaluation', flat=True)
marks = []
for evaluation in submission_evaluations:
mark = 0
for criterea in evaluation:
mark += criterea["mark"]
marks.append(mark)
earned_points = median(marks)
review.submission.earned_points = (
earned_points
)
all_checked = not submission.reviews.exclude( all_checked = not submission.reviews.exclude(
state=ReviewStatusChoices.CHECKED state=ReviewStatusChoices.CHECKED
).exists() ).exists()
@@ -101,4 +109,5 @@ def evaluate_submission(
CompetitionTaskSubmission.StatusChoices.CHECKED.value CompetitionTaskSubmission.StatusChoices.CHECKED.value
) )
review.submission.save() review.submission.save()
return status.OK, review.submission return status.OK, review.submission
+9
View File
@@ -2,11 +2,20 @@ from typing import Literal
from uuid import UUID from uuid import UUID
from ninja import ModelSchema, Schema from ninja import ModelSchema, Schema
from pydantic import Field
from apps.task.models import CompetitionTask, CompetitionTaskSubmission, CompetitionTaskAttachment from apps.task.models import CompetitionTask, CompetitionTaskSubmission, CompetitionTaskAttachment
class TaskOutSchema(ModelSchema): class TaskOutSchema(ModelSchema):
status: Literal["sent", "checked", "checking", "not_submitted"] = None
@staticmethod
def resolve_status(self, context) -> Literal["sent", "checked", "checking", "not_submitted"]:
if submission := CompetitionTaskSubmission.objects.filter(task=self, user=context.get("request").auth).first():
return submission.status
return "not_submitted"
class Meta: class Meta:
model = CompetitionTask model = CompetitionTask
fields = [ fields = [
@@ -0,0 +1,23 @@
# Generated by Django 5.1.6 on 2025-03-02 12:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('achievement', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='achievement',
name='need_count',
field=models.IntegerField(default=5, help_text='Здесь нужно указать количество действий, необходимое для получения ачивок. Например, если вы указали в предыдущем пункте "Выполненные задания" а тут 5, то ачивка будет выдаваться за 5 решенных заданий', verbose_name='кол-во того, что нужно для получения ачивки'),
),
migrations.AddField(
model_name='achievement',
name='type',
field=models.CharField(choices=[('correct_tasks', 'Выполненные задания')], default='correct_tasks', help_text='За какой тип достижений будет выдаваться ачивка', max_length=20, verbose_name='тип'),
),
]
@@ -3,6 +3,9 @@ from django.db import models
from apps.core.models import BaseModel from apps.core.models import BaseModel
class Achievement(BaseModel): class Achievement(BaseModel):
class AchievementType(models.TextChoices):
CORRECT_TASKS = "correct_tasks", "Выполненные задания"
def image_url_upload_to(instance, filename): def image_url_upload_to(instance, filename):
return f"/achievements/{instance.id}/icon" return f"/achievements/{instance.id}/icon"
@@ -14,6 +17,19 @@ class Achievement(BaseModel):
upload_to=image_url_upload_to, upload_to=image_url_upload_to,
) )
type = models.CharField(
max_length=20,
choices=AchievementType.choices,
verbose_name="тип",
help_text="За какой тип достижений будет выдаваться ачивка",
default=AchievementType.CORRECT_TASKS,
)
need_count = models.IntegerField(
verbose_name="кол-во того, что нужно для получения ачивки",
help_text="Здесь нужно указать количество действий, необходимое для получения ачивок. Например, если вы указали в предыдущем пункте \"Выполненные задания\" а тут 5, то ачивка будет выдаваться за 5 решенных заданий",
default=5
)
def __str__(self): def __str__(self):
return self.name return self.name
@@ -105,6 +105,7 @@ class Command(BaseCommand):
description=description, description=description,
type=task_type, type=task_type,
points=random.randint(1, 10), points=random.randint(1, 10),
submission_reviewers_count=random.randint(2, 10),
max_attempts=random.randint(1, 10), max_attempts=random.randint(1, 10),
) )
tasks.append(task) tasks.append(task)
@@ -0,0 +1,24 @@
# Generated by Django 5.1.6 on 2025-03-02 12:49
import martor.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('task', '0003_alter_competitiontask_description'),
]
operations = [
migrations.AddField(
model_name='competitiontask',
name='submission_reviewers_count',
field=models.PositiveSmallIntegerField(blank=True, default=1, null=True),
),
migrations.AlterField(
model_name='competitiontask',
name='description',
field=martor.models.MartorField(verbose_name='описание'),
),
]
+9 -8
View File
@@ -3,6 +3,7 @@ from uuid import uuid4
from django.db import models from django.db import models
from django.db.models import Count, Q from django.db.models import Count, Q
from tinymce.models import HTMLField from tinymce.models import HTMLField
from martor.models import MartorField
from apps.competition.models import Competition from apps.competition.models import Competition
from apps.core.models import BaseModel from apps.core.models import BaseModel
@@ -24,7 +25,7 @@ class CompetitionTask(BaseModel):
) )
competition = models.ForeignKey(Competition, on_delete=models.CASCADE) competition = models.ForeignKey(Competition, on_delete=models.CASCADE)
title = models.CharField(verbose_name="заголовок", max_length=50) title = models.CharField(verbose_name="заголовок", max_length=50)
description = HTMLField(verbose_name="описание") description = MartorField(verbose_name="описание")
max_attempts = models.PositiveSmallIntegerField(null=True, blank=True) max_attempts = models.PositiveSmallIntegerField(null=True, blank=True)
type = models.CharField( type = models.CharField(
choices=CompetitionTaskType, max_length=8, verbose_name="тип проверки" choices=CompetitionTaskType, max_length=8, verbose_name="тип проверки"
@@ -57,6 +58,7 @@ class CompetitionTask(BaseModel):
verbose_name="ревьюверы", verbose_name="ревьюверы",
help_text="Справа отображаются действующие проверяющие, слева - доступные для выбора. Для перемещения можно кликнуть 2 раза по проверяющему" help_text="Справа отображаются действующие проверяющие, слева - доступные для выбора. Для перемещения можно кликнуть 2 раза по проверяющему"
) )
submission_reviewers_count = models.PositiveSmallIntegerField(default=1, null=True, blank=True)
def __str__(self): def __str__(self):
return self.title return self.title
@@ -152,8 +154,8 @@ class CompetitionTaskSubmission(BaseModel):
if not self.task.reviewers.exists(): if not self.task.reviewers.exists():
return return
reviewer = ( reviewers_count = self.task.submission_reviewers_count
self.task.reviewers.annotate( reviewers = self.task.reviewers.annotate(
pending_count=Count( pending_count=Count(
"review", "review",
filter=Q( filter=Q(
@@ -163,11 +165,10 @@ class CompetitionTaskSubmission(BaseModel):
] ]
), ),
) )
) ).order_by("pending_count")[:reviewers_count] # да это медленно работает и чо
.order_by("pending_count")
.first() for reviewer in reviewers:
) Review.objects.create(
review = Review.objects.create(
reviewer=reviewer, reviewer=reviewer,
submission=self, submission=self,
) )
+36 -1
View File
@@ -442,6 +442,7 @@ INSTALLED_APPS = [
"ninja", "ninja",
"minio_storage", "minio_storage",
"tinymce", "tinymce",
"martor",
# Internal apps # Internal apps
"apps.core", "apps.core",
"apps.user", "apps.user",
@@ -459,15 +460,49 @@ TINYMCE_DEFAULT_CONFIG = {
"menubar": False, "menubar": False,
"plugins": "advlist,autolink,lists,link,image,charmap,print,preview,anchor," "plugins": "advlist,autolink,lists,link,image,charmap,print,preview,anchor,"
"searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,paste," "searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,paste,"
"code,help,wordcount", "code,help,wordcount,markdown",
"toolbar": "undo redo | formatselect | " "toolbar": "undo redo | formatselect | "
"bold italic backcolor | alignleft aligncenter " "bold italic backcolor | alignleft aligncenter "
"alignright alignjustify | bullist numlist outdent indent | " "alignright alignjustify | bullist numlist outdent indent | "
"removeformat | help", "removeformat | help",
"skin": "oxide-dark", "skin": "oxide-dark",
"content_css": "dark", "content_css": "dark",
"textpattern_patterns": [
{"start": "*", "end": "*", "format": "italic"},
{"start": "**", "end": "**", "format": "bold"},
{"start": "#", "format": "h1"},
{"start": "##", "format": "h2"},
{"start": "###", "format": "h3"},
{"start": "####", "format": "h4"},
{"start": "#####", "format": "h5"},
{"start": "######", "format": "h6"},
{"start": "1. ", "cmd": "InsertOrderedList"},
{"start": "* ", "cmd": "InsertUnorderedList"},
{"start": "- ", "cmd": "InsertUnorderedList"}
]
} }
# martor
MARTOR_THEME = 'bootstrap'
MARTOR_ENABLE_CONFIGS = {
'emoji': 'true', # to enable/disable emoji icons.
'imgur': 'true', # to enable/disable imgur/custom uploader.
'mention': 'false', # to enable/disable mention
'jquery': 'true', # to include/revoke jquery (require for admin default django)
'living': 'false', # to enable/disable live updates in preview
'spellcheck': 'false', # to enable/disable spellcheck in form textareas
'hljs': 'true', # to enable/disable hljs highlighting in preview
}
MARTOR_TOOLBAR_BUTTONS = [
'bold', 'italic', 'horizontal', 'heading', 'pre-code',
'blockquote', 'unordered-list', 'ordered-list',
'link', 'emoji',
'direct-mention', 'toggle-maximize', 'help'
]
# GUID # GUID
DJANGO_GUID = { DJANGO_GUID = {
+2
View File
@@ -14,6 +14,8 @@ admin.site.index_title = "DataRush"
urlpatterns = [ urlpatterns = [
# tinymce # tinymce
path("tinymce/", include("tinymce.urls")), path("tinymce/", include("tinymce.urls")),
# martor
path('martor/', include('martor.urls')),
# Admin urls # Admin urls
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
# API urls # API urls
+1
View File
@@ -19,6 +19,7 @@ dependencies = [
"django-tinymce>=4.1.0", "django-tinymce>=4.1.0",
"gunicorn>=23.0.0", "gunicorn>=23.0.0",
"httpx>=0.28.1", "httpx>=0.28.1",
"martor>=1.6.45",
"pillow>=11.1.0", "pillow>=11.1.0",
"psycopg2-binary>=2.9.10", "psycopg2-binary>=2.9.10",
"pydantic>=2.10.5", "pydantic>=2.10.5",