mirror of
https://gitlab.com/megazordpobeda/DataRush.git
synced 2026-05-22 23:17:09 +00:00
Merge branch 'master' of https://gitlab.prodcontest.ru/team-15/project
This commit is contained in:
@@ -109,7 +109,6 @@ deploy:
|
||||
cd ~/deploy
|
||||
docker system prune -a --force
|
||||
docker compose pull > deploy.log 2>&1
|
||||
docker compose down >> deploy.log 2>&1
|
||||
docker compose up -d --remove-orphans --force-recreate >> deploy.log 2>&1
|
||||
docker compose ps >> deploy.log 2>&1
|
||||
EOF
|
||||
|
||||
+4
-1
@@ -400,6 +400,9 @@ services:
|
||||
- type: bind
|
||||
source: /var/run/docker.sock
|
||||
target: /var/run/docker.sock
|
||||
- type: bind
|
||||
source: /tmp
|
||||
target: /tmp
|
||||
|
||||
proxy:
|
||||
image: docker.io/nginx:1.27-alpine3.21
|
||||
@@ -410,7 +413,7 @@ services:
|
||||
test: ["CMD", "service", "nginx", "status", "||", " exit 1"]
|
||||
interval: 1m30s
|
||||
timeout: 5s
|
||||
start_period: 5s
|
||||
start_period: 15s
|
||||
start_interval: 2s
|
||||
retries: 5
|
||||
ports:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from datetime import datetime
|
||||
|
||||
from ninja import ModelSchema, Schema
|
||||
from pydantic import Field
|
||||
|
||||
@@ -19,6 +21,7 @@ class UserAchievementSchema(Schema):
|
||||
name: str = Field(..., alias="achievement.name")
|
||||
description: str = Field(..., alias="achievement.description")
|
||||
icon: str = Field(..., alias="achievement.icon")
|
||||
received_at: datetime
|
||||
|
||||
class Meta:
|
||||
model = UserAchievement
|
||||
|
||||
@@ -32,7 +32,7 @@ class UserSchema(ModelSchema):
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["id", "email", "username", "created_at"]
|
||||
fields = ["id", "avatar", "email", "username", "created_at"]
|
||||
|
||||
|
||||
class StatSchema(Schema):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-03 07:20
|
||||
# Generated by Django 5.1.6 on 2025-03-03 09:41
|
||||
|
||||
import apps.achievement.models
|
||||
import django.db.models.deletion
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-03 07:20
|
||||
# Generated by Django 5.1.6 on 2025-03-03 09:41
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-03 07:20
|
||||
# Generated by Django 5.1.6 on 2025-03-03 09:41
|
||||
|
||||
import apps.competition.models
|
||||
import datetime
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-03 07:20
|
||||
# Generated by Django 5.1.6 on 2025-03-03 09:41
|
||||
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-03 07:20
|
||||
# Generated by Django 5.1.6 on 2025-03-03 09:41
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
@@ -3,8 +3,8 @@ from django.contrib import admin
|
||||
from apps.task.models import (
|
||||
CompetitionTask,
|
||||
CompetitionTaskAttachment,
|
||||
CompetitionTaskCriteria,
|
||||
CompetitionTaskSubmission,
|
||||
CompetitionTaskCriteria
|
||||
)
|
||||
|
||||
|
||||
@@ -23,7 +23,10 @@ class CompetitionTaskAdmin(admin.ModelAdmin):
|
||||
list_display = ("title", "type", "points")
|
||||
filter_horizontal = ("reviewers",)
|
||||
list_filter = ("type",)
|
||||
inlines = (CompletionAttachmentInline, CompetitionCriteriaInline,)
|
||||
inlines = (
|
||||
CompletionAttachmentInline,
|
||||
CompetitionCriteriaInline,
|
||||
)
|
||||
|
||||
|
||||
@admin.register(CompetitionTaskSubmission)
|
||||
@@ -45,9 +48,6 @@ class CompetitionTaskSubmissionAdmin(admin.ModelAdmin):
|
||||
def has_add_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
|
||||
class CompetitionTaskInline(admin.StackedInline):
|
||||
model = CompetitionTask
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-03 07:20
|
||||
# Generated by Django 5.1.6 on 2025-03-03 09:41
|
||||
|
||||
import apps.task.models
|
||||
import django.db.models.deletion
|
||||
import martor.models
|
||||
import mdeditor.fields
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
@@ -22,17 +22,17 @@ class Migration(migrations.Migration):
|
||||
name='CompetitionTask',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('in_competition_position', models.PositiveSmallIntegerField()),
|
||||
('in_competition_position', models.PositiveSmallIntegerField(verbose_name='позиция в соревновании')),
|
||||
('title', models.CharField(max_length=50, verbose_name='заголовок')),
|
||||
('description', martor.models.MartorField(verbose_name='описание')),
|
||||
('max_attempts', models.PositiveSmallIntegerField(blank=True, null=True)),
|
||||
('description', mdeditor.fields.MDTextField(verbose_name='описание')),
|
||||
('max_attempts', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='максимальное кол-во попыток')),
|
||||
('type', models.CharField(choices=[('input', 'Ввод правильного ответа'), ('checker', 'Ввод кода'), ('review', 'Ручная')], max_length=8, verbose_name='тип проверки')),
|
||||
('correct_answer_file', models.FileField(blank=True, null=True, upload_to=apps.task.models.CompetitionTask.answer_file_upload_to, verbose_name='файл с правильным ответом')),
|
||||
('points', models.IntegerField(blank=True, null=True, verbose_name='баллы за задание')),
|
||||
('answer_file_path', models.TextField(blank=True, default='stdout', help_text='Путь до файла в котором ожидается результат. Пример: stdout или ./output.txt', null=True, verbose_name='куда сделать вывод программы участнику')),
|
||||
('submission_reviewers_count', models.PositiveSmallIntegerField(blank=True, default=1, null=True)),
|
||||
('competition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='competition.competition')),
|
||||
('reviewers', models.ManyToManyField(blank=True, help_text='Справа отображаются действующие проверяющие, слева - доступные для выбора. Для перемещения можно кликнуть 2 раза по проверяющему', to='review.reviewer', verbose_name='ревьюверы')),
|
||||
('correct_answer_file', models.FileField(blank=True, help_text='Имеет смысл только при автоматической (ввод ответа или кода) проверке.', null=True, upload_to=apps.task.models.CompetitionTask.answer_file_upload_to, verbose_name='файл с правильным ответом')),
|
||||
('points', models.IntegerField(blank=True, null=True, verbose_name='общий балл за задание')),
|
||||
('answer_file_path', models.TextField(blank=True, default='stdout', help_text='Путь до файла в котором ожидается результат. Пример: stdout или ./output.txt. Имеет смысл только при автоматическом типе проверки.', null=True, verbose_name='куда сделать вывод программы участнику')),
|
||||
('submission_reviewers_count', models.PositiveSmallIntegerField(blank=True, default=1, null=True, verbose_name='кол-во проверяющих для зачета задачи')),
|
||||
('competition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='competition.competition', verbose_name='привязанное соревнование')),
|
||||
('reviewers', models.ManyToManyField(blank=True, help_text='Справа отображаются действующие проверяющие, слева - доступные для выбора. Для перемещения можно кликнуть 2 раза по проверяющему. Имеет смысл только при ручном типе проверки.', to='review.reviewer', verbose_name='ревьюверы')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'задание',
|
||||
@@ -57,14 +57,15 @@ class Migration(migrations.Migration):
|
||||
name='CompetitionTaskCriteria',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('name', models.TextField()),
|
||||
('slug', models.SlugField()),
|
||||
('description', models.TextField()),
|
||||
('max_value', models.PositiveSmallIntegerField()),
|
||||
('name', models.TextField(verbose_name='название')),
|
||||
('slug', models.SlugField(verbose_name='техническое название')),
|
||||
('description', models.TextField(verbose_name='описание критерии')),
|
||||
('max_value', models.PositiveSmallIntegerField(verbose_name='максимальное кол-во баллов')),
|
||||
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='criteries', to='task.competitiontask')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
'verbose_name': 'критерий',
|
||||
'verbose_name_plural': 'критерии',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
|
||||
@@ -22,12 +22,16 @@ class CompetitionTask(BaseModel):
|
||||
in_competition_position = models.PositiveSmallIntegerField(
|
||||
verbose_name="позиция в соревновании"
|
||||
)
|
||||
competition = models.ForeignKey(Competition, on_delete=models.CASCADE,
|
||||
verbose_name="привязанное соревнование")
|
||||
competition = models.ForeignKey(
|
||||
Competition,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="привязанное соревнование",
|
||||
)
|
||||
title = models.CharField(verbose_name="заголовок", max_length=50)
|
||||
description = MDTextField(verbose_name="описание")
|
||||
max_attempts = models.PositiveSmallIntegerField(null=True, blank=True,
|
||||
verbose_name="максимальное кол-во попыток")
|
||||
max_attempts = models.PositiveSmallIntegerField(
|
||||
null=True, blank=True, verbose_name="максимальное кол-во попыток"
|
||||
)
|
||||
type = models.CharField(
|
||||
choices=CompetitionTaskType, max_length=8, verbose_name="тип проверки"
|
||||
)
|
||||
@@ -38,9 +42,10 @@ class CompetitionTask(BaseModel):
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="файл с правильным ответом",
|
||||
help_text="Имеет смысл только при автоматической (ввод ответа или кода) проверке.",
|
||||
)
|
||||
points = models.IntegerField(
|
||||
null=True, blank=True, verbose_name="баллы за задание"
|
||||
null=True, blank=True, verbose_name="общий балл за задание"
|
||||
)
|
||||
|
||||
# only when "checker" type
|
||||
@@ -48,7 +53,10 @@ class CompetitionTask(BaseModel):
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="куда сделать вывод программы участнику",
|
||||
help_text="Путь до файла в котором ожидается результат. Пример: stdout или ./output.txt",
|
||||
help_text=(
|
||||
"Путь до файла в котором ожидается результат. "
|
||||
"Пример: stdout или ./output.txt. Имеет смысл только при автоматическом типе проверки."
|
||||
),
|
||||
default="stdout",
|
||||
)
|
||||
|
||||
@@ -57,10 +65,17 @@ class CompetitionTask(BaseModel):
|
||||
Reviewer,
|
||||
blank=True,
|
||||
verbose_name="ревьюверы",
|
||||
help_text="Справа отображаются действующие проверяющие, слева - доступные для выбора. Для перемещения можно кликнуть 2 раза по проверяющему",
|
||||
help_text=(
|
||||
"Справа отображаются действующие проверяющие, слева - доступные для выбора. "
|
||||
"Для перемещения можно кликнуть 2 раза по проверяющему. Имеет смысл только"
|
||||
" при ручном типе проверки."
|
||||
),
|
||||
)
|
||||
submission_reviewers_count = models.PositiveSmallIntegerField(
|
||||
default=1, null=True, blank=True, verbose_name="кол-во проверяющих для зачета задачи"
|
||||
default=1,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="кол-во проверяющих для зачета задачи",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
@@ -76,15 +91,9 @@ class CompetitionTaskCriteria(BaseModel):
|
||||
CompetitionTask, on_delete=models.CASCADE, related_name="criteries"
|
||||
)
|
||||
|
||||
name = models.TextField(
|
||||
verbose_name="название"
|
||||
)
|
||||
slug = models.SlugField(
|
||||
verbose_name="техническое название"
|
||||
)
|
||||
description = models.TextField(
|
||||
verbose_name="описание критерии"
|
||||
)
|
||||
name = models.TextField(verbose_name="название")
|
||||
slug = models.SlugField(verbose_name="техническое название")
|
||||
description = models.TextField(verbose_name="описание критерии")
|
||||
max_value = models.PositiveSmallIntegerField(
|
||||
verbose_name="максимальное кол-во баллов"
|
||||
)
|
||||
|
||||
@@ -1,28 +1,40 @@
|
||||
import hashlib
|
||||
|
||||
import httpx
|
||||
from celery import shared_task
|
||||
from django.conf import settings
|
||||
from django.core.files.base import ContentFile
|
||||
|
||||
from apps.task.models import CompetitionTaskSubmission
|
||||
|
||||
|
||||
@shared_task(bind=True, max_retries=3)
|
||||
def analyze_data_task(self, submission_id):
|
||||
from .models import CompetitionTaskSubmission
|
||||
|
||||
submission = CompetitionTaskSubmission.objects.get(id=submission_id)
|
||||
try:
|
||||
code = submission.content.read().decode()
|
||||
code_url = (
|
||||
f"{settings.MINIO_DEFAULT_CUSTOM_ENDPOINT_URL}{submission.path}"
|
||||
)
|
||||
files = [
|
||||
(f.name, f.file.open("rb"))
|
||||
for f in submission.task.attachments.filter(public=True)
|
||||
{
|
||||
"url": f"{settings.MINIO_DEFAULT_CUSTOM_ENDPOINT_URL}{attachment.path}",
|
||||
"bind_path": attachment.bind_at,
|
||||
}
|
||||
for attachment in submission.task.attachments.filter(
|
||||
bind_path__isnull=False
|
||||
)
|
||||
]
|
||||
|
||||
response = httpx.post(
|
||||
f"{settings.CHECKER_API_ENDPOINT}/execute",
|
||||
files=[("files", (f.name, f)) for f in files]
|
||||
+ [
|
||||
("code", code),
|
||||
("expected_hash", submission.task.correct_answer_hash),
|
||||
],
|
||||
json={
|
||||
"files": files,
|
||||
"code_url": code_url,
|
||||
"answer_file_path": submission.task.answer_file_path,
|
||||
"expected_hash": hashlib.sha256(
|
||||
submission.task.correct_answer_file.read().encode()
|
||||
).hexdigest(),
|
||||
},
|
||||
timeout=30,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-03 07:20
|
||||
# Generated by Django 5.1.6 on 2025-03-03 09:41
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-03 07:20
|
||||
# Generated by Django 5.1.6 on 2025-03-03 09:41
|
||||
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
@@ -17,11 +17,12 @@ class Migration(migrations.Migration):
|
||||
name='User',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('avatar', models.ImageField(blank=True, null=True, upload_to='', verbose_name='аватар')),
|
||||
('email', models.EmailField(max_length=254, unique=True, verbose_name='почта')),
|
||||
('username', models.SlugField(unique=True, verbose_name='юзернейм')),
|
||||
('password', models.TextField(verbose_name='пароль')),
|
||||
('created_at', models.DateTimeField(auto_now=True)),
|
||||
('status', models.CharField(choices=[('student', 'Student'), ('metodist', 'Metodist')], default='student', max_length=10)),
|
||||
('created_at', models.DateTimeField(auto_now=True, verbose_name='дата создания')),
|
||||
('status', models.CharField(choices=[('student', 'Участник соревнований'), ('metodist', 'Методист (составитель заданий)')], default='student', max_length=10, verbose_name='роль участника')),
|
||||
('achievements', models.ManyToManyField(blank=True, to='achievement.achievement', verbose_name='ачивки пользователя')),
|
||||
],
|
||||
options={
|
||||
|
||||
@@ -11,11 +11,14 @@ class UserRole(models.TextChoices):
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
avatar = models.ImageField(verbose_name="аватар", null=True, blank=True)
|
||||
email = models.EmailField(unique=True, verbose_name="почта")
|
||||
username = models.SlugField(unique=True, verbose_name="юзернейм")
|
||||
password = models.TextField(verbose_name="пароль")
|
||||
|
||||
created_at = models.DateTimeField(auto_now=True, verbose_name="дата создания")
|
||||
created_at = models.DateTimeField(
|
||||
auto_now=True, verbose_name="дата создания"
|
||||
)
|
||||
|
||||
achievements = models.ManyToManyField(
|
||||
Achievement, blank=True, verbose_name="ачивки пользователя"
|
||||
@@ -29,8 +32,10 @@ class User(BaseModel):
|
||||
return check_password(self.password, password)
|
||||
|
||||
status = models.CharField(
|
||||
max_length=10, choices=UserRole.choices, default="student",
|
||||
verbose_name="роль участника"
|
||||
max_length=10,
|
||||
choices=UserRole.choices,
|
||||
default="student",
|
||||
verbose_name="роль участника",
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
@@ -12,12 +12,8 @@ admin.site.index_title = "DataRush"
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
# tinymce
|
||||
path("tinymce/", include("tinymce.urls")),
|
||||
# martor
|
||||
path("martor/", include("martor.urls")),
|
||||
# mdeditor
|
||||
path(r'mdeditor/', include('mdeditor.urls')),
|
||||
path(r"mdeditor/", include("mdeditor.urls")),
|
||||
# Admin urls
|
||||
path("admin/", admin.site.urls),
|
||||
# API urls
|
||||
|
||||
@@ -202,6 +202,7 @@ async def execute_code(request: ExecutionRequest) -> ExecutionResponse:
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
print(tmp_dir)
|
||||
bound_files = {}
|
||||
if request.files:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
|
||||
Reference in New Issue
Block a user