mirror of
https://gitlab.com/megazordpobeda/DataRush.git
synced 2026-05-23 05:07:10 +00:00
Merge branches 'master' and 'master' of gitlab.prodcontest.ru:team-15/project
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from apps.achievement.models import Achievement
|
||||
|
||||
|
||||
@admin.register(Achievement)
|
||||
class AchievementAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"id",
|
||||
"name",
|
||||
)
|
||||
search_fields = (
|
||||
"name",
|
||||
"description",
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AchievementConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "apps.achievement"
|
||||
verbose_name = "Ачивки"
|
||||
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-02 12:09
|
||||
|
||||
import apps.achievement.models
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Achievement',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=30, unique=True, verbose_name='название')),
|
||||
('description', models.TextField(verbose_name='описание')),
|
||||
('icon', models.FileField(upload_to=apps.achievement.models.Achievement.image_url_upload_to, verbose_name='иконка достижения')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'ачивка',
|
||||
'verbose_name_plural': 'ачивки',
|
||||
},
|
||||
),
|
||||
]
|
||||
+23
@@ -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='тип'),
|
||||
),
|
||||
]
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-02 13:36
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('achievement', '0003_remove_achievement_need_count_and_more'), ('achievement', '0004_alter_achievement_slug')]
|
||||
|
||||
dependencies = [
|
||||
('achievement', '0002_achievement_need_count_achievement_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='achievement',
|
||||
name='need_count',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='achievement',
|
||||
name='type',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='achievement',
|
||||
name='slug',
|
||||
field=models.SlugField(unique=True, verbose_name='слаг'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
from django.db import models
|
||||
|
||||
from apps.core.models import BaseModel
|
||||
|
||||
|
||||
class Achievement(BaseModel):
|
||||
class AchievementType(models.TextChoices):
|
||||
CORRECT_TASKS = "correct_tasks", "Выполненные задания"
|
||||
|
||||
def image_url_upload_to(instance, filename):
|
||||
return f"achievements/{instance.id}/icon/{filename}"
|
||||
|
||||
name = models.CharField(
|
||||
max_length=30, verbose_name="название", unique=True
|
||||
)
|
||||
description = models.TextField(verbose_name="описание")
|
||||
icon = models.FileField(
|
||||
verbose_name="иконка достижения",
|
||||
upload_to=image_url_upload_to,
|
||||
)
|
||||
|
||||
slug = models.SlugField(verbose_name="слаг", unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "ачивка"
|
||||
verbose_name_plural = "ачивки"
|
||||
@@ -7,6 +7,7 @@ from apps.task.admin import CompetitionTaskInline
|
||||
@admin.register(Competition)
|
||||
class CompetitionAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"id",
|
||||
"title",
|
||||
"end_date",
|
||||
"type",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-02 06:13
|
||||
# Generated by Django 5.1.6 on 2025-03-02 10:28
|
||||
|
||||
import apps.competition.models
|
||||
import datetime
|
||||
|
||||
@@ -15,7 +15,7 @@ class Competition(BaseModel):
|
||||
SOLO = "solo", "Индивидуальный"
|
||||
|
||||
def image_url_upload_to(instance, filename):
|
||||
return f"/competitions/{instance.id}/image"
|
||||
return f"competitions/{instance.id}/image/{filename}"
|
||||
|
||||
title = models.CharField(max_length=100, verbose_name="название")
|
||||
description = models.TextField(verbose_name="описание")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
admin.site.unregister(Group)
|
||||
|
||||
@@ -8,7 +8,7 @@ from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.competition.models import Competition, State
|
||||
from apps.review.models import Review, Reviewer
|
||||
from apps.review.models import Reviewer
|
||||
from apps.task.models import CompetitionTask, CompetitionTaskSubmission
|
||||
from apps.user.models import User, UserRole
|
||||
|
||||
@@ -20,8 +20,8 @@ class Command(BaseCommand):
|
||||
self.stdout.write("Starting data generation...")
|
||||
users = self.create_users(5)
|
||||
competitions = self.create_competitions(2, users)
|
||||
self.reviewers = self.create_reviewers(2)
|
||||
tasks = self.create_tasks(competitions)
|
||||
self.reviewers = self.create_reviewers(1)
|
||||
self.create_submissions(tasks, users)
|
||||
self.create_states(competitions, users)
|
||||
self.stdout.write("Data generation completed.")
|
||||
@@ -99,17 +99,25 @@ class Command(BaseCommand):
|
||||
title = f"Task {i} for {comp.title}"
|
||||
description = f"Task description for task {i} in {comp.title}"
|
||||
task = CompetitionTask.objects.create(
|
||||
in_competition_position=i,
|
||||
competition=comp,
|
||||
title=title,
|
||||
description=description,
|
||||
type=task_type,
|
||||
points=random.randint(1, 10),
|
||||
submission_reviewers_count=random.randint(2, 10),
|
||||
max_attempts=random.randint(1, 10),
|
||||
)
|
||||
tasks.append(task)
|
||||
self.stdout.write(f"Created task: {title} (type: {task_type})")
|
||||
self.add_reviewers_to_task(tasks)
|
||||
return tasks
|
||||
|
||||
def add_reviewers_to_task(self, tasks):
|
||||
for task in tasks:
|
||||
task.reviewers.set(self.reviewers)
|
||||
task.save()
|
||||
|
||||
def create_submissions(self, tasks, users):
|
||||
for task in tasks:
|
||||
# Each task will get between 1 and 3 submissions
|
||||
@@ -133,15 +141,6 @@ class Command(BaseCommand):
|
||||
self.stdout.write(
|
||||
f"Created submission for task '{task.title}' by user '{user.username}'"
|
||||
)
|
||||
self.add_reviewers(submission)
|
||||
|
||||
def add_reviewers(self, submission):
|
||||
for reviewer in self.reviewers:
|
||||
if random.choice([True, False]):
|
||||
Review.objects.create(
|
||||
submission=submission,
|
||||
reviewer=reviewer,
|
||||
)
|
||||
|
||||
def create_states(self, competitions, users):
|
||||
# For each competition, create a State for some of its participants
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from apps.review.models import Review, Reviewer
|
||||
from apps.review.models import Reviewer
|
||||
|
||||
|
||||
@admin.register(Reviewer)
|
||||
class ReviewAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "surname",)
|
||||
search_fields = ("name", "surname",)
|
||||
|
||||
|
||||
@admin.register(Review)
|
||||
class ReviewAdmin(admin.ModelAdmin):
|
||||
list_display = ("id", "reviewer", "submission",)
|
||||
search_fields = ("id", "reviewer__id", "reviewer__name", "reviewer__surname",
|
||||
"submission__id", "submission__content")
|
||||
list_filter = ("submission__plagiarism_checked", "submission__status",)
|
||||
class ReviewersAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"name",
|
||||
"surname",
|
||||
)
|
||||
search_fields = (
|
||||
"name",
|
||||
"surname",
|
||||
)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-02 09:31
|
||||
# Generated by Django 5.1.6 on 2025-03-02 10:28
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
@@ -10,10 +9,21 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('task', '0003_remove_competitiontask_attachments'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Review',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('evaluation', models.JSONField(blank=True, default=list, null=True, verbose_name='выполнение')),
|
||||
('state', models.CharField(choices=[('not_checked', 'Not Checked'), ('checking', 'Checking'), ('checked', 'Checked')], default='not_checked', max_length=11, verbose_name='состояние')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'проверка',
|
||||
'verbose_name_plural': 'проверки',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Reviewer',
|
||||
fields=[
|
||||
@@ -27,18 +37,4 @@ class Migration(migrations.Migration):
|
||||
'verbose_name_plural': 'проверяющие',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Review',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('evaluation', models.JSONField(blank=True, default=list, null=True, verbose_name='выполнение')),
|
||||
('state', models.CharField(choices=[('not_checked', 'Not Checked'), ('checking', 'Checking'), ('checked', 'Checked')], default='not_checked', max_length=11, verbose_name='состояние')),
|
||||
('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='task.competitiontasksubmission', verbose_name='посылка')),
|
||||
('reviewer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='review.reviewer', verbose_name='проверяющий')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'проверка',
|
||||
'verbose_name_plural': 'проверки',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-02 10:28
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('review', '0001_initial'),
|
||||
('task', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='review',
|
||||
name='submission',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='task.competitiontasksubmission', verbose_name='посылка'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='review',
|
||||
name='reviewer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='review.reviewer', verbose_name='проверяющий'),
|
||||
),
|
||||
]
|
||||
@@ -1,7 +1,6 @@
|
||||
from django.db import models
|
||||
|
||||
from apps.core.models import BaseModel
|
||||
from apps.task.models import CompetitionTaskSubmission
|
||||
|
||||
|
||||
class Reviewer(BaseModel):
|
||||
@@ -25,22 +24,24 @@ class ReviewStatusChoices(models.TextChoices):
|
||||
|
||||
|
||||
class Review(BaseModel):
|
||||
reviewer = models.ForeignKey(Reviewer, on_delete=models.CASCADE,
|
||||
verbose_name="проверяющий")
|
||||
reviewer = models.ForeignKey(
|
||||
Reviewer, on_delete=models.CASCADE, verbose_name="проверяющий"
|
||||
)
|
||||
submission = models.ForeignKey(
|
||||
CompetitionTaskSubmission,
|
||||
"task.CompetitionTaskSubmission",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="reviews",
|
||||
verbose_name="посылка"
|
||||
verbose_name="посылка",
|
||||
)
|
||||
|
||||
evaluation = models.JSONField(default=list, null=True, blank=True,
|
||||
verbose_name="выполнение")
|
||||
evaluation = models.JSONField(
|
||||
default=list, null=True, blank=True, verbose_name="выполнение"
|
||||
)
|
||||
state = models.CharField(
|
||||
choices=ReviewStatusChoices.choices,
|
||||
default=ReviewStatusChoices.NOT_CHECKED.value,
|
||||
max_length=11,
|
||||
verbose_name="состояние"
|
||||
verbose_name="состояние",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from apps.task.models import CompetitionTask, CompetitionTaskAttachment
|
||||
from apps.task.models import (
|
||||
CompetitionTask,
|
||||
CompetitionTaskAttachment,
|
||||
CompetitionTaskSubmission,
|
||||
)
|
||||
|
||||
|
||||
class CompletionAttachmentInline(admin.StackedInline):
|
||||
@@ -11,6 +15,30 @@ class CompletionAttachmentInline(admin.StackedInline):
|
||||
@admin.register(CompetitionTask)
|
||||
class CompetitionTaskAdmin(admin.ModelAdmin):
|
||||
list_display = ("title", "type", "points")
|
||||
filter_horizontal = ("reviewers",)
|
||||
|
||||
|
||||
@admin.register(CompetitionTaskSubmission)
|
||||
class CompetitionTaskSubmissionAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"task",
|
||||
"user",
|
||||
"status",
|
||||
)
|
||||
search_fields = (
|
||||
"task__id",
|
||||
"task__title",
|
||||
"user__username",
|
||||
"user__email",
|
||||
)
|
||||
filter = ("plagiarism_checked",)
|
||||
ordering = ["-timestamp"]
|
||||
|
||||
def has_add_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
|
||||
class CompetitionTaskInline(admin.StackedInline):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-02 06:13
|
||||
# Generated by Django 5.1.6 on 2025-03-02 10:28
|
||||
|
||||
import apps.task.models
|
||||
import django.db.models.deletion
|
||||
@@ -13,6 +13,7 @@ class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('competition', '0001_initial'),
|
||||
('review', '0001_initial'),
|
||||
('user', '0001_initial'),
|
||||
]
|
||||
|
||||
@@ -30,6 +31,7 @@ class Migration(migrations.Migration):
|
||||
('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='куда сделать вывод программы участнику')),
|
||||
('competition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='competition.competition')),
|
||||
('reviewers', models.ManyToManyField(blank=True, to='review.reviewer')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'задание',
|
||||
@@ -40,10 +42,10 @@ class Migration(migrations.Migration):
|
||||
name='CompetitionTaskAttachment',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('file', models.FileField(upload_to=apps.task.models.CompetitionTaskAttachment.file_upload_at)),
|
||||
('bind_at', models.FilePathField()),
|
||||
('public', models.BooleanField(default=False)),
|
||||
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='task.competitiontask')),
|
||||
('file', models.FileField(upload_to=apps.task.models.CompetitionTaskAttachment.file_upload_at, verbose_name='файл')),
|
||||
('bind_at', models.FilePathField(verbose_name='путь сохранения')),
|
||||
('public', models.BooleanField(default=False, verbose_name='публичный')),
|
||||
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='task.competitiontask', verbose_name='задание')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
@@ -67,7 +69,7 @@ class Migration(migrations.Migration):
|
||||
name='CompetitionTaskSubmission',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('status', models.CharField(choices=[('sent', 'Sent'), ('checking', 'Checking'), ('checked', 'Checked')], default='sent', max_length=8)),
|
||||
('status', models.CharField(choices=[('sent', 'Sent'), ('checking', 'Checking'), ('checked', 'Checked')], default='sent', max_length=8, verbose_name='статус')),
|
||||
('content', models.FileField(upload_to=apps.task.models.CompetitionTaskSubmission.submission_content_upload_to)),
|
||||
('stdout', models.FileField(blank=True, null=True, upload_to=apps.task.models.CompetitionTaskSubmission.submission_stdout_upload_to)),
|
||||
('result', models.JSONField(blank=True, default=None, null=True)),
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-02 12:09
|
||||
|
||||
import apps.task.models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('review', '0002_initial'),
|
||||
('task', '0001_initial'),
|
||||
('user', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='competitiontasksubmission',
|
||||
options={'verbose_name': 'посылка', 'verbose_name_plural': 'посылки'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontask',
|
||||
name='reviewers',
|
||||
field=models.ManyToManyField(blank=True, help_text='Справа отображаются действующие проверяющие, слева - доступные для выбора. Для перемещения можно кликнуть 2 раза по проверяющему', to='review.reviewer', verbose_name='ревьюверы'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontasksubmission',
|
||||
name='checked_at',
|
||||
field=models.DateTimeField(blank=True, null=True, verbose_name='дата проверки'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontasksubmission',
|
||||
name='content',
|
||||
field=models.FileField(upload_to=apps.task.models.CompetitionTaskSubmission.submission_content_upload_to, verbose_name='содержание посылки'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontasksubmission',
|
||||
name='earned_points',
|
||||
field=models.IntegerField(blank=True, null=True, verbose_name='баллы за задание'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontasksubmission',
|
||||
name='plagiarism_checked',
|
||||
field=models.BooleanField(default=False, verbose_name='проверено на плагиат'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontasksubmission',
|
||||
name='result',
|
||||
field=models.JSONField(blank=True, default=None, null=True, verbose_name='результат проверки'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontasksubmission',
|
||||
name='stdout',
|
||||
field=models.FileField(blank=True, help_text='Используется только при проверке чекером', null=True, upload_to=apps.task.models.CompetitionTaskSubmission.submission_stdout_upload_to, verbose_name='вывод программы'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontasksubmission',
|
||||
name='task',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='task.competitiontask', verbose_name='задание'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontasksubmission',
|
||||
name='timestamp',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='дата отправки'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontasksubmission',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.user', verbose_name='пользователь'),
|
||||
),
|
||||
]
|
||||
@@ -1,40 +0,0 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-02 08:50
|
||||
|
||||
import apps.task.models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('task', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='competitiontask',
|
||||
name='attachments',
|
||||
field=models.ManyToManyField(blank=True, related_name='tasks_attachments', to='task.competitiontaskattachment'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontaskattachment',
|
||||
name='bind_at',
|
||||
field=models.FilePathField(verbose_name='путь сохранения'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontaskattachment',
|
||||
name='file',
|
||||
field=models.FileField(upload_to=apps.task.models.CompetitionTaskAttachment.file_upload_at, verbose_name='файл'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontaskattachment',
|
||||
name='public',
|
||||
field=models.BooleanField(default=False, verbose_name='публичный'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='competitiontaskattachment',
|
||||
name='task',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='task.competitiontask', verbose_name='задание'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-02 12:23
|
||||
|
||||
import tinymce.models
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('task', '0002_alter_competitiontasksubmission_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='competitiontask',
|
||||
name='description',
|
||||
field=tinymce.models.HTMLField(verbose_name='описание'),
|
||||
),
|
||||
]
|
||||
@@ -1,17 +0,0 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-02 09:31
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('task', '0002_competitiontask_attachments_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='competitiontask',
|
||||
name='attachments',
|
||||
),
|
||||
]
|
||||
+24
@@ -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='описание'),
|
||||
),
|
||||
]
|
||||
@@ -1,10 +1,12 @@
|
||||
from uuid import uuid4
|
||||
|
||||
from django.db import models
|
||||
from tinymce.models import HTMLField
|
||||
from django.db.models import Count, Q
|
||||
from martor.models import MartorField
|
||||
|
||||
from apps.competition.models import Competition
|
||||
from apps.core.models import BaseModel
|
||||
from apps.review.models import Review, Reviewer, ReviewStatusChoices
|
||||
from apps.user.models import User
|
||||
|
||||
|
||||
@@ -15,14 +17,14 @@ class CompetitionTask(BaseModel):
|
||||
REVIEW = "review", "Ручная"
|
||||
|
||||
def answer_file_upload_to(instance, filename) -> str:
|
||||
return f"/tasks/{instance.id}/answer/{uuid4()}/filename"
|
||||
return f"tasks/{instance.id}/answer/{uuid4()}/{filename}"
|
||||
|
||||
in_competition_position = models.PositiveSmallIntegerField(
|
||||
null=True, blank=True
|
||||
)
|
||||
competition = models.ForeignKey(Competition, on_delete=models.CASCADE)
|
||||
title = models.CharField(verbose_name="заголовок", max_length=50)
|
||||
description = HTMLField(verbose_name="описание", max_length=300)
|
||||
description = MartorField(verbose_name="описание")
|
||||
max_attempts = models.PositiveSmallIntegerField(null=True, blank=True)
|
||||
type = models.CharField(
|
||||
choices=CompetitionTaskType, max_length=8, verbose_name="тип проверки"
|
||||
@@ -48,6 +50,17 @@ class CompetitionTask(BaseModel):
|
||||
default="stdout",
|
||||
)
|
||||
|
||||
# only when "review" type
|
||||
reviewers = models.ManyToManyField(
|
||||
Reviewer,
|
||||
blank=True,
|
||||
verbose_name="ревьюверы",
|
||||
help_text="Справа отображаются действующие проверяющие, слева - доступные для выбора. Для перемещения можно кликнуть 2 раза по проверяющему",
|
||||
)
|
||||
submission_reviewers_count = models.PositiveSmallIntegerField(
|
||||
default=1, null=True, blank=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
@@ -69,12 +82,12 @@ class CompetitionTaskCriteria(BaseModel):
|
||||
|
||||
class CompetitionTaskAttachment(BaseModel):
|
||||
def file_upload_at(instance, filename):
|
||||
return f"/attachment/{instance.id}/file"
|
||||
return f"attachment/{instance.id}/file/{filename}"
|
||||
|
||||
task = models.ForeignKey(CompetitionTask, on_delete=models.CASCADE,
|
||||
verbose_name="задание")
|
||||
file = models.FileField(upload_to=file_upload_at,
|
||||
verbose_name="файл")
|
||||
task = models.ForeignKey(
|
||||
CompetitionTask, on_delete=models.CASCADE, verbose_name="задание"
|
||||
)
|
||||
file = models.FileField(upload_to=file_upload_at, verbose_name="файл")
|
||||
bind_at = models.FilePathField(verbose_name="путь сохранения")
|
||||
public = models.BooleanField(default=False, verbose_name="публичный")
|
||||
|
||||
@@ -86,60 +99,90 @@ class CompetitionTaskSubmission(BaseModel):
|
||||
CHECKED = "checked"
|
||||
|
||||
def submission_content_upload_to(instance, filename) -> str:
|
||||
return f"submissions/{instance.id}/content"
|
||||
return f"submissions/{instance.id}/content/{filename}"
|
||||
|
||||
def submission_stdout_upload_to(instance, filename) -> str:
|
||||
return f"/submissions/{instance.id}/stdout"
|
||||
return f"submissions/{instance.id}/stdout/{filename}"
|
||||
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
task = models.ForeignKey(CompetitionTask, on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(
|
||||
User, on_delete=models.CASCADE, verbose_name="пользователь"
|
||||
)
|
||||
task = models.ForeignKey(
|
||||
CompetitionTask, on_delete=models.CASCADE, verbose_name="задание"
|
||||
)
|
||||
|
||||
status = models.CharField(
|
||||
choices=StatusChoices.choices,
|
||||
default=StatusChoices.SENT,
|
||||
max_length=8,
|
||||
verbose_name="статус",
|
||||
)
|
||||
|
||||
# code or text or file
|
||||
content = models.FileField(upload_to=submission_content_upload_to)
|
||||
content = models.FileField(
|
||||
upload_to=submission_content_upload_to,
|
||||
verbose_name="содержание посылки",
|
||||
)
|
||||
|
||||
# only if task type is checker
|
||||
stdout = models.FileField(
|
||||
upload_to=submission_stdout_upload_to, null=True, blank=True
|
||||
upload_to=submission_stdout_upload_to,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="вывод программы",
|
||||
help_text="Используется только при проверке чекером",
|
||||
)
|
||||
|
||||
# depends on task type:
|
||||
# - input: {"correct": boolean}
|
||||
# - file: {"total_points": integer, "by_criteria": ["criteria_name": integer]}
|
||||
# - code: {"correct": boolean}
|
||||
result = models.JSONField(default=None, null=True, blank=True)
|
||||
result = models.JSONField(
|
||||
default=None, null=True, blank=True, verbose_name="результат проверки"
|
||||
)
|
||||
# just more readable result representation, maybe will be calcuated somehow more complex depends on criteria
|
||||
earned_points = models.IntegerField(null=True, blank=True)
|
||||
earned_points = models.IntegerField(
|
||||
null=True, blank=True, verbose_name="баллы за задание"
|
||||
)
|
||||
|
||||
checked_at = models.DateTimeField(null=True, blank=True)
|
||||
plagiarism_checked = models.BooleanField(default=False)
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
checked_at = models.DateTimeField(
|
||||
null=True, blank=True, verbose_name="дата проверки"
|
||||
)
|
||||
plagiarism_checked = models.BooleanField(
|
||||
default=False, verbose_name="проверено на плагиат"
|
||||
)
|
||||
timestamp = models.DateTimeField(
|
||||
auto_now_add=True, verbose_name="дата отправки"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "посылка"
|
||||
verbose_name_plural = "посылки"
|
||||
|
||||
def __str__(self):
|
||||
return str(self.id)
|
||||
|
||||
def send_on_review(self):
|
||||
if not self.task.reviewers.exists():
|
||||
return
|
||||
|
||||
reviewer = (
|
||||
self.task.reviewers.annotate(
|
||||
pending_count=Count(
|
||||
"review",
|
||||
filter=Q(
|
||||
review__state__in=[
|
||||
ReviewStatusChoices.NOT_CHECKED,
|
||||
ReviewStatusChoices.CHECKING,
|
||||
]
|
||||
),
|
||||
)
|
||||
reviewers_count = self.task.submission_reviewers_count
|
||||
reviewers = self.task.reviewers.annotate(
|
||||
pending_count=Count(
|
||||
"review",
|
||||
filter=Q(
|
||||
review__state__in=[
|
||||
ReviewStatusChoices.NOT_CHECKED,
|
||||
ReviewStatusChoices.CHECKING,
|
||||
]
|
||||
),
|
||||
)
|
||||
).order_by("pending_count")[
|
||||
:reviewers_count
|
||||
] # да это медленно работает и чо
|
||||
|
||||
for reviewer in reviewers:
|
||||
Review.objects.create(
|
||||
reviewer=reviewer,
|
||||
submission=self,
|
||||
)
|
||||
.order_by("pending_count")
|
||||
.first()
|
||||
)
|
||||
review = Review.objects.create(
|
||||
reviewer=reviewer,
|
||||
submission=self,
|
||||
)
|
||||
|
||||
@@ -28,5 +28,3 @@ with open("file.txt") as f:
|
||||
print(result)
|
||||
self.assertTrue(result["success"])
|
||||
self.assertTrue(result["match"])
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-02 06:13
|
||||
# Generated by Django 5.1.6 on 2025-03-02 10:28
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@@ -7,3 +7,4 @@ from apps.user.models import User
|
||||
class UserAdmin(admin.ModelAdmin):
|
||||
list_display = ("email", "username")
|
||||
search_fields = ("id", "email", "username")
|
||||
filter_horizontal = ("achievements",)
|
||||
|
||||
@@ -5,4 +5,4 @@ class UsersConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "apps.user"
|
||||
label = "user"
|
||||
verbose_name = "Пользователи"
|
||||
verbose_name = "Пользователи (веб)"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-02 00:16
|
||||
# Generated by Django 5.1.6 on 2025-03-02 10:28
|
||||
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
@@ -19,6 +19,7 @@ class Migration(migrations.Migration):
|
||||
('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)),
|
||||
],
|
||||
options={
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-02 12:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('achievement', '0001_initial'),
|
||||
('user', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='achievements',
|
||||
field=models.ManyToManyField(blank=True, to='achievement.achievement', verbose_name='ачивки пользователя'),
|
||||
),
|
||||
]
|
||||
@@ -1,6 +1,7 @@
|
||||
from django.contrib.auth.hashers import check_password, make_password
|
||||
from django.db import models
|
||||
|
||||
from apps.achievement.models import Achievement
|
||||
from apps.core.models import BaseModel
|
||||
|
||||
|
||||
@@ -14,6 +15,12 @@ class User(BaseModel):
|
||||
username = models.SlugField(unique=True, verbose_name="юзернейм")
|
||||
password = models.TextField(verbose_name="пароль")
|
||||
|
||||
created_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
achievements = models.ManyToManyField(
|
||||
Achievement, blank=True, verbose_name="ачивки пользователя"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def make_password(password: str):
|
||||
return make_password(password)
|
||||
|
||||
Reference in New Issue
Block a user