Merge branches 'master' and 'master' of gitlab.prodcontest.ru:team-15/project

This commit is contained in:
ITQ
2025-03-02 17:08:05 +03:00
85 changed files with 2065 additions and 422 deletions
@@ -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': 'ачивки',
},
),
]
@@ -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='тип'),
),
]
@@ -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
+1 -1
View File
@@ -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 -1
View File
@@ -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
+10 -12
View File
@@ -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='проверяющий'),
),
]
+9 -8
View File
@@ -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):
+29 -1
View File
@@ -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)),
@@ -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',
),
]
@@ -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='описание'),
),
]
+80 -37
View File
@@ -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
+1
View File
@@ -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",)
+1 -1
View File
@@ -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='ачивки пользователя'),
),
]
+7
View File
@@ -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)