This commit is contained in:
rngsurrounded
2025-03-01 22:53:15 +09:00
18 changed files with 280 additions and 30 deletions
+2 -2
View File
@@ -37,6 +37,6 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --start-interval=2s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:8080/health?format=json || exit 1
CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:8080/api/health?format=json || exit 1
CMD gunicorn config.wsgi --workers=8 -b 0.0.0.0:8080 --access-logfile - --error-logfile -
CMD gunicorn config.wsgi --workers=8 -b 0.0.0.0:8080 --access-logfile - --error-logfile - --timeout=600
@@ -0,0 +1,12 @@
from django.contrib import admin
from apps.competition.models import Competition
from apps.task.admin import CompetitionTaskInline
@admin.register(Competition)
class CompetitionAdmin(admin.ModelAdmin):
list_display = ("title", "end_date", "type",)
search_fields = ("title", "description",)
list_filter = ("type", "participation_type",)
inlines = [CompetitionTaskInline]
@@ -4,3 +4,4 @@ from django.apps import AppConfig
class CompetitionsConfig(AppConfig):
name = "apps.competition"
label = "competition"
verbose_name = "Соревнование"
@@ -0,0 +1,35 @@
# Generated by Django 5.1.6 on 2025-03-01 12:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('competition', '0001_initial'),
('task', '0001_initial'),
('user', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='competition',
name='tasks',
field=models.ManyToManyField(blank=True, related_name='tasks', to='task.competitiontask'),
),
migrations.AlterField(
model_name='competition',
name='participants',
field=models.ManyToManyField(blank=True, editable=False, related_name='participants', to='user.user'),
),
migrations.AlterField(
model_name='competition',
name='participation_type',
field=models.CharField(choices=[('edu', 'Образовательный'), ('competitive', 'Соревновательный')], max_length=11, verbose_name='Тип соревнования'),
),
migrations.AlterField(
model_name='competition',
name='type',
field=models.CharField(choices=[('solo', 'Индивидуальный')], max_length=10, verbose_name='Тип участия'),
),
]
+8 -4
View File
@@ -8,11 +8,11 @@ from apps.user.models import User
class Competition(BaseModel):
class CompetitionType(models.TextChoices):
SOLO = "solo"
SOLO = "solo", "Индивидуальный"
class CompetitionParticipationType(models.TextChoices):
EDU = "edu"
COMPETITIVE = "competitive"
EDU = "edu", "Образовательный"
COMPETITIVE = "competitive", "Соревновательный"
title = models.CharField(max_length=100, verbose_name="Название")
description = models.TextField(verbose_name="Описание")
@@ -35,7 +35,11 @@ class Competition(BaseModel):
choices=CompetitionParticipationType.choices,
verbose_name="Тип соревнования",
)
participants = models.ManyToManyField(User, related_name="participants")
participants = models.ManyToManyField(User, related_name="participants", blank=True,
editable=False)
def __str__(self):
return self.title
class Meta:
verbose_name = "соревнование"
+5
View File
@@ -0,0 +1,5 @@
from django.contrib import admin
from django.contrib.auth.models import Group, User
admin.site.unregister(Group)
admin.site.unregister(User)
+13
View File
@@ -0,0 +1,13 @@
from django.contrib import admin
from apps.task.models import CompetitionTask
@admin.register(CompetitionTask)
class CompetitionTaskAdmin(admin.ModelAdmin):
list_display = ("title", "type", "points")
class CompetitionTaskInline(admin.StackedInline):
model = CompetitionTask
extra = 0
+1
View File
@@ -4,3 +4,4 @@ from django.apps import AppConfig
class CompetitionsConfig(AppConfig):
name = "apps.task"
label = "task"
verbose_name = "Задания"
@@ -0,0 +1,45 @@
# Generated by Django 5.1.6 on 2025-03-01 12:21
import apps.task.models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('competition', '0002_competition_tasks_alter_competition_participants_and_more'),
('task', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='competitiontask',
options={'verbose_name': 'задание', 'verbose_name_plural': 'задания'},
),
migrations.AlterField(
model_name='competitiontask',
name='competition',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='competition.competition', verbose_name='соревнование'),
),
migrations.AlterField(
model_name='competitiontask',
name='correct_answer_file',
field=models.FileField(blank=True, null=True, upload_to=apps.task.models.CompetitionTask.answer_file_upload_to, verbose_name='правильный ответ'),
),
migrations.AlterField(
model_name='competitiontask',
name='criteries',
field=models.JSONField(blank=True, null=True, verbose_name='критерии проверки'),
),
migrations.AlterField(
model_name='competitiontask',
name='points',
field=models.IntegerField(blank=True, null=True, verbose_name='баллы за задание'),
),
migrations.AlterField(
model_name='competitiontask',
name='type',
field=models.CharField(choices=[('input', 'Input'), ('checker', 'Checker'), ('review', 'Review')], max_length=8, verbose_name='тип задания'),
),
]
+42 -9
View File
@@ -10,33 +10,66 @@ from apps.user.models import User
class CompetitionTask(BaseModel):
class CompetitionTaskType(models.TextChoices):
INPUT = "input"
CHECKER = "checker"
REVIEW = "review"
INPUT = "input", "Ввод правильного ответа"
CHECKER = "checker", "Вывод кода"
REVIEW = "review", "Ручная"
def answer_file_upload_to(instance, filename) -> str:
return f"/tasks/{instance.id}/answer/{uuid4()}/filename"
competition = models.ForeignKey(Competition, on_delete=models.CASCADE)
title = models.TextField(verbose_name="заголовок", max_length=50)
title = models.CharField(verbose_name="заголовок", max_length=50)
description = models.TextField(verbose_name="описание", max_length=300)
type = models.CharField(choices=CompetitionTaskType, max_length=8)
max_attemps = models.PositiveSmallIntegerField()
type = models.CharField(
choices=CompetitionTaskType, max_length=8, verbose_name="тип проверки"
)
# only when "input" or "checker" type
correct_answer_file = models.FileField(
upload_to=answer_file_upload_to, null=True, blank=True
upload_to=answer_file_upload_to,
null=True,
blank=True,
verbose_name="файл с правильным ответом",
)
points = models.IntegerField(
null=True, blank=True, verbose_name="баллы за задание"
)
points = models.IntegerField(null=True, blank=True)
# only when "checker" type
answer_file_path = models.TextField(null=True, blank=True)
answer_file_path = models.TextField(
null=True,
blank=True,
verbose_name="куда сохранять решения",
default="stdout",
)
# only when "review" type
criteries = models.JSONField(blank=True, null=True)
# todo make it more humanize
criteries = models.JSONField(
blank=True,
null=True,
verbose_name="критерии",
default=lambda: [
{
"name": "CHANGE ME",
"slug": "CHANGE ME",
"max_value": 0,
"min_value": 0,
}
],
)
def clean(self):
ContestTaskCriteriesValidator()(self)
def __str__(self):
return self.title
class Meta:
verbose_name = "задание"
verbose_name_plural = "задания"
class CompetetionTaskSumbission(BaseModel):
class StatusChoices(models.TextChoices):
+1 -1
View File
@@ -227,7 +227,7 @@ FIRST_DAY_OF_WEEK = 1
FORMAT_MODULE_PATH = None
LANGUAGE_CODE = env("DJANGO_LANGUAGE_CODE", default="en-us")
LANGUAGE_CODE = env("DJANGO_LANGUAGE_CODE", default="ru-ru")
LANGUAGES = [("en", _("English")), ("ru", _("Russian"))]
+1
View File
@@ -20,6 +20,7 @@ dependencies = [
"psycopg2-binary>=2.9.10",
"pydantic>=2.10.5",
"pyjwt>=2.10.1",
"python-gettext>=5.0",
"python-json-logger>=3.2.1",
"pytz>=2024.2",
"redis>=5.2.1",